diff --git a/package.json b/package.json index a8a34f6..0acf971 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/src/app/api/files/create/route.ts b/src/app/api/files/create/route.ts index 2dc1e39..c5fa521 100644 --- a/src/app/api/files/create/route.ts +++ b/src/app/api/files/create/route.ts @@ -11,7 +11,11 @@ export const POST = async (req: Request) => { if (result instanceof NextResponse) { try { - const { fileName, filePrivate } = await req.json(); + const { fileName, filePrivate, teamId } = await req.json(); + + console.log(fileName, filePrivate, teamId) + + if(!fileName || !teamId) return NextResponse.json({ status: 401 }); await mongoDB(); @@ -22,7 +26,8 @@ export const POST = async (req: Request) => { filePrivate, createdBy:user._id, readBy:[user._id], - writtenBy:[user._id] + writtenBy:[user._id], + teamId:teamId }); return NextResponse.json({ status: 200 }); diff --git a/src/app/api/files/get/[id]/route.ts b/src/app/api/files/get/[id]/route.ts new file mode 100644 index 0000000..736da16 --- /dev/null +++ b/src/app/api/files/get/[id]/route.ts @@ -0,0 +1,32 @@ +import { mongoDB } from "@/lib/MongoDB"; +import { AuthMiddleware } from "@/Middleware/AuthMiddleware"; +import FileModel from "@/models/file"; +import { NextResponse } from "next/server"; + +export async function GET( + request: Request, + { params }: { params: { id: string } } + ) { + + const result = await AuthMiddleware(request); + + if (result instanceof NextResponse) { + + try { + + const { id } = params; + + if (!id) return new Response("Parameters missing!!", { status: 401 }); + + await mongoDB(); + + const files = await FileModel.find({teamId:id}).populate("createdBy") + + return NextResponse.json(files,{ status: 200 }); + } catch (err) { + return NextResponse.json(`Err : ${err}`, {status:500}); + } + } else { + return result; + } + } \ No newline at end of file diff --git a/src/app/api/files/getFileById/[id]/route.ts b/src/app/api/files/getFileById/[id]/route.ts new file mode 100644 index 0000000..f712ddd --- /dev/null +++ b/src/app/api/files/getFileById/[id]/route.ts @@ -0,0 +1,31 @@ +import { mongoDB } from "@/lib/MongoDB"; +import { AuthMiddleware } from "@/Middleware/AuthMiddleware"; +import FileModel from "@/models/file"; +import { NextResponse } from "next/server"; + +export async function GET( + request: Request, + { params }: { params: { id: string } } + ) { + + const result = await AuthMiddleware(request); + + if (result instanceof NextResponse) { + + try { + const { id } = params; + + if (!id) return new Response("Parameters missing!!", { status: 401 }); + + await mongoDB(); + + const files = await FileModel.findById({_id:id}).populate("createdBy") + + return NextResponse.json(files,{ status: 200 }); + } catch (err) { + return NextResponse.json(`Err : ${err}`, {status:500}); + } + } else { + return result; + } + } \ No newline at end of file diff --git a/src/app/dashboard/_components/FileList.tsx b/src/app/dashboard/_components/FileList.tsx index dc6f5cc..ebebf31 100644 --- a/src/app/dashboard/_components/FileList.tsx +++ b/src/app/dashboard/_components/FileList.tsx @@ -34,14 +34,20 @@ import FileStatusModal from "@/components/shared/FileStatusModal"; export interface FILE { archive: boolean; - createdBy: string; + createdBy: { + email: string; + firstName: string; + lastName: string; + }; document: string; fileName: string; teamId: string; whiteboard: string; _id: string; _creationTime: number; - private: boolean; + filePrivate: boolean; + readBy:any[]; + writtenBy:any[]; } const ActionDialog = ({ @@ -119,7 +125,6 @@ const FileRow = ({ router, index, isSubmitted, - authorData, user, }: { file: FILE; @@ -131,7 +136,6 @@ const FileRow = ({ router: ReturnType; index: number; isSubmitted: boolean; - authorData: any[]; user: any; }) => ( @@ -157,31 +161,27 @@ const FileRow = ({ className="whitespace-nowrap px-4 py-2 text-muted-foreground" onClick={() => router.push("/workspace/" + file._id)} > - {authorData.map( - (author, index) => - author.email === file.createdBy && ( - - - - {author.name.charAt(0)} - - - ) - )} + + + + {file.createdBy.firstName.charAt(0)} + {file.createdBy.lastName.charAt(0)} + + (null); const [isSmallScreen, setIsSmallScreen] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); - const [authorData, setAuthorData] = useState([]); const safeFileList = Array.isArray(fileList) ? fileList : []; const pathname = usePathname(); - console.log(fileList) - - useEffect(() => { - const getData = async () => { - let listOfCreators: string[] = []; - authorData.forEach((user: any) => { - listOfCreators.push(user.email); - }); - - fileList?.forEach(async (file) => { - if (!listOfCreators.includes(file.createdBy)) { - listOfCreators.push(file.createdBy); - const result = await convex.query(api.user.getUser, { - email: file.createdBy, - }); - setAuthorData([...authorData, result[0]]); - } - }); - }; - if (fileList) { - getData(); - } - }, [fileList]); - const sortedFiles = [...safeFileList]; if (sortConfig !== null) { sortedFiles.sort((a, b) => { @@ -383,7 +358,6 @@ function FileList({ onDelete={deleteFunc} router={router} index={index} - authorData={authorData} /> ) )} @@ -459,23 +433,21 @@ function FileList({ + fileId={file._id} + email={user.email} + privateFIle={file.filePrivate} + successTitle={ + !file.filePrivate + ? "File accessible to members only" + : "File accessible to everyone" + } + dialogTitle={!file.filePrivate ? "Private File" : "Public File"} + dialogDescription={ + !file.filePrivate + ? "Make file accessible to members only" + : "Make file accessible to everyone" + } + />
diff --git a/src/app/dashboard/_components/SideNav.tsx b/src/app/dashboard/_components/SideNav.tsx index 2534d3e..c508fcd 100644 --- a/src/app/dashboard/_components/SideNav.tsx +++ b/src/app/dashboard/_components/SideNav.tsx @@ -9,6 +9,8 @@ import { setClose, setOpen } from "@/app/Redux/Menu/menuSlice"; import { useDispatch, useSelector } from "react-redux"; import { useTheme } from "next-themes"; import { RootState } from "@/config/store"; +import createAxiosInstance from "@/config/AxiosProtectedRoute"; +import { getFileUrl } from "@/lib/API-URLs"; // import { RootState } from "@/app/store"; function SideNav() { @@ -22,8 +24,9 @@ function SideNav() { const dispatch = useDispatch(); const { theme } = useTheme(); const [isSidebarOpen, setIsSidebarOpen] = useState(true); - const email = useSelector((state:RootState)=>state.auth.user.email) + const user = useSelector((state:RootState)=>state.auth.user) const dispatch_nav = useDispatch(); + const axiosInstance = createAxiosInstance(user.accessToken) useEffect(() => { const handleResize = () => { @@ -45,33 +48,11 @@ function SideNav() { useEffect(() => { activeTeam && getFiles(); }, [activeTeam]); - const onFileCreate = (fileName: string) => { - createFile({ - fileName: fileName, - teamId: activeTeam?._id, - createdBy: email, - archive: false, - document: "", - whiteboard: "", - }).then( - (resp) => { - if (resp) { - getFiles(); - toast.success("File created successfully!"); - } - }, - (e) => { - toast.error("Error while creating file"); - } - ); - }; const getFiles = async () => { - const result = await convex.query(api.files.getFiles, { - teamId: activeTeam?._id, - }); - setFileList_(result); - setTotalFiles(result?.length); + const result = await axiosInstance.get(`${getFileUrl}/${activeTeam._id}`); + setFileList_(result.data); + setTotalFiles(result.data?.length); }; return ( @@ -109,8 +90,8 @@ function SideNav() {
diff --git a/src/app/dashboard/_components/SideNavBottomSection.tsx b/src/app/dashboard/_components/SideNavBottomSection.tsx index 4df3dac..ed3f5b2 100644 --- a/src/app/dashboard/_components/SideNavBottomSection.tsx +++ b/src/app/dashboard/_components/SideNavBottomSection.tsx @@ -42,6 +42,10 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { RootState } from "@/config/store"; +import { useSelector } from "react-redux"; +import createAxiosInstance from "@/config/AxiosProtectedRoute"; +import { createFileUrl } from "@/lib/API-URLs"; +import { Switch } from "@/components/ui/switch"; interface TEAM { createdBy: String; @@ -49,7 +53,7 @@ interface TEAM { _id: String; } -function SideNavBottomSection({ onFileCreate, totalFiles, activeTeam }: any) { +function SideNavBottomSection({getFiles, totalFiles, activeTeam }: any) { const pathname = usePathname(); const menuList = [ { @@ -78,13 +82,19 @@ function SideNavBottomSection({ onFileCreate, totalFiles, activeTeam }: any) { }, ]; const router = useRouter() - const { fileList_, setFileList_ } = useContext(FileListContext); + const { fileList_ } = useContext(FileListContext); const [fileList, setFileList] = useState([]); const [fileInput, setFileInput] = useState(""); const [error, setError] = useState(""); - const [isSubmitted, setIsSubmitted] = useState(false); + const email = ((state:RootState) => state.auth.user.email) const deleteTeam = useMutation(api.teams.deleteTeam); + const user = useSelector((state:RootState)=>state.auth.user); + const axiosInstance = createAxiosInstance(user.accessToken); + const [filePrivate,setFileprivate] = useState(false); + const teamId = useSelector((state:RootState)=>state.team.teamId); + const [isSubmitted, setIsSubmitted] = useState(false); + const deleteFunc = async (e: any, id: String) => { e.stopPropagation(); if (activeTeam.teamName === "My Org") { @@ -109,6 +119,24 @@ function SideNavBottomSection({ onFileCreate, totalFiles, activeTeam }: any) { fileList_ && setFileList(fileList_); }, [fileList_]); + const createFileHandler = async() => { + try { + await axiosInstance.post(createFileUrl,{ + fileName:fileInput, + filePrivate, + teamId + }) + .then((res)=>{ + if(res.data){ + getFiles() + toast.success("File created Successfully") + } + }) + } catch (err) { + console.log(err) + } + } + return (
{menuList.map((menu, index) => ( @@ -146,12 +174,19 @@ function SideNavBottomSection({ onFileCreate, totalFiles, activeTeam }: any) { Create New File - + handleFileInput(e.target.value)} /> +
+ setFileprivate(!filePrivate)} + /> +

Private

+
{error}
@@ -159,7 +194,7 @@ function SideNavBottomSection({ onFileCreate, totalFiles, activeTeam }: any) { diff --git a/src/app/workspace/[fileId]/page.tsx b/src/app/workspace/[fileId]/page.tsx index 55d20f3..c7dae40 100644 --- a/src/app/workspace/[fileId]/page.tsx +++ b/src/app/workspace/[fileId]/page.tsx @@ -2,17 +2,17 @@ import React, { useEffect, useState, useRef, MutableRefObject } from "react"; import WorkspaceHeader from "../_components/WorkspaceHeader"; import Editor from "../_components/Editor"; -import { useConvex } from "convex/react"; -import { api } from "../../../../convex/_generated/api"; + import Canvas from "../_components/Canvas"; -import dynamic from 'next/dynamic'; import EditorJS, { OutputData } from "@editorjs/editorjs"; import Loader from "@/components/shared/Loader"; -import { FILE } from "@/app/teams/settings/_components/FileList"; import PrivateAlert from "@/components/shared/PrivateAlert"; import { useSelector } from 'react-redux'; import { RootState } from '@/config/store'; import { useSession } from "next-auth/react"; +import createAxiosInstance from "@/config/AxiosProtectedRoute"; +import { getFileByIdUrl } from "@/lib/API-URLs"; +import { FILE } from "@/app/dashboard/_components/FileList"; // Dynamic imports for server-side libraries const jsPDFPromise = import('jspdf'); @@ -20,14 +20,14 @@ const excalidrawPromise = import('@excalidraw/excalidraw'); function Workspace({ params }: any) { const [triggerSave, setTriggerSave] = useState(false); - const convex = useConvex(); const [fileData, setFileData] = useState(null); const [fullScreen, setFullScreen] = useState(false); const editorRef = useRef(null); const canvasRef = useRef(null); const {data:session,status} = useSession(); const isAuth = useSelector((state:RootState)=>state.auth.user.isAuth) - + const user = useSelector((state:RootState)=>state.auth.user) + const axiosInstance = createAxiosInstance(user.accessToken) const [isError,setError] = useState(false); const [errorMsg,setErrorMsg] = useState(""); @@ -38,9 +38,9 @@ function Workspace({ params }: any) { }, [params.fileId]); useEffect(()=>{ - if(fileData && session && fileData.private){ + if(fileData && session && fileData.filePrivate){ if(isAuth){ - if((fileData.readBy && !fileData.readBy.includes(session.user.email)) || fileData.createdBy !== session.user.email){ + if((fileData.readBy && !fileData.readBy.includes(session.user.id)) || fileData.createdBy.email !== session.user.email){ setError(true); setErrorMsg("Invalid Access! Request owner to give read or write access!") } @@ -52,10 +52,8 @@ function Workspace({ params }: any) { },[fileData]) const getFileData = async () => { - const result = await convex.query(api.files.getFileById, { - _id: params.fileId, - }); - setFileData(result); + const result = await axiosInstance.get(`${getFileByIdUrl}/${params.fileId}`); + setFileData(result.data); }; const saveAsPdf = async () => { diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..bc69cf2 --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/lib/API-URLs.ts b/src/lib/API-URLs.ts index ec151b1..03ecf62 100644 --- a/src/lib/API-URLs.ts +++ b/src/lib/API-URLs.ts @@ -7,4 +7,7 @@ export const updateWriteAccessUrl = "/api/files/write"; export const registerUserUrl = "/api/auth/register"; export const checkHealthUrl = "/api/health"; export const createNewTeamUrl = "/api/teams/create"; -export const getTeamUrl = "/api/teams/get"; \ No newline at end of file +export const getTeamUrl = "/api/teams/get"; +export const createFileUrl = "/api/files/create"; +export const getFileUrl = "/api/files/get"; +export const getFileByIdUrl = "/api/files/getFileById"; \ No newline at end of file diff --git a/src/models/file.ts b/src/models/file.ts index d3ece66..f202458 100644 --- a/src/models/file.ts +++ b/src/models/file.ts @@ -9,6 +9,7 @@ interface File { filePrivate: boolean; writtenBy: any[]; readBy: any[]; + teamId:any; } const FileSchema = new Schema( @@ -21,6 +22,7 @@ const FileSchema = new Schema( filePrivate: { type: Boolean, required: true }, writtenBy: [{ type: Schema.Types.ObjectId, required: true, ref: "User" }], readBy: [{ type: Schema.Types.ObjectId, required: true, ref: "User" }], + teamId:{ type: Schema.Types.ObjectId, required: true, ref: "Team" }, }, { timestamps: true } );