From 9b1555dd2d7dc518228bac1ec8a8955953598e3c Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Fri, 28 Jun 2024 00:49:19 +0530 Subject: [PATCH 01/40] feat: UI of read and write --- src/app/dashboard/page.tsx | 4 +++ src/components/shared/MemberModal.tsx | 36 ++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index ce0b790..ee6cd19 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -26,6 +26,10 @@ export interface FILE { whiteboard: string; _id: string; _creationTime: number; + read?:boolean; + write?:boolean; + writtenBy: string[]; + readBy: string[]; } function Dashboard() { diff --git a/src/components/shared/MemberModal.tsx b/src/components/shared/MemberModal.tsx index c8ac6f3..fdfa3e4 100644 --- a/src/components/shared/MemberModal.tsx +++ b/src/components/shared/MemberModal.tsx @@ -12,7 +12,9 @@ import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; import { useSelector } from "react-redux"; import { RootState } from "@/app/store"; import { FileListContext } from "@/app/_context/FilesListContext"; -import { useState,useContext,useEffect } from "react"; +import { useState, useContext, useEffect } from "react"; +import { FILE } from "@/app/dashboard/page"; +import { Badge } from "../ui/badge"; type Props = { image: string; @@ -30,6 +32,18 @@ export default function MemberModal({ email, }: Props) { const teamName = useSelector((state: RootState) => state.team.teamName); + const [fileData, setFileList] = useState([]); + const { fileList_ } = useContext(FileListContext); + + useEffect(() => { + if (fileList_) { + const nonArchivedFiles = fileList_.filter( + (file: { archive: boolean }) => !file.archive + ); + setFileList(nonArchivedFiles); + } + }, [fileList_]); + return ( + { +
+
+

File Name

+

File Access

+
+ {fileData.map((file: FILE, index) => ( +
+

{file.fileName}

+
+ { file.readBy?.includes(email) && Read} + { file.writtenBy?.includes(email) && Write} +
+
+ ))} +
+ } Email : {email} From fad379264db32042a92b992dcc0cadf9cc5facfd Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Fri, 28 Jun 2024 18:03:29 +0530 Subject: [PATCH 02/40] fix: build --- src/app/dashboard/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index ee6cd19..97d5bbc 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -26,8 +26,8 @@ export interface FILE { whiteboard: string; _id: string; _creationTime: number; - read?:boolean; - write?:boolean; + read:boolean; + write:boolean; writtenBy: string[]; readBy: string[]; } From 62ec222e54c4c8afe1b0b489b8200b7163a9c774 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Fri, 28 Jun 2024 19:36:36 +0530 Subject: [PATCH 03/40] feat: api for team members --- src/app/api/teams/get/route.ts | 16 ----------- src/app/api/teams/members/[id]/route.ts | 28 +++++++++++++++++++ src/app/dashboard/_components/SideNav.tsx | 1 + .../_components/SideNavBottomSection.tsx | 27 +++++++++++++++--- .../profile/_components/TeamList.tsx | 1 - src/app/dashboard/team/page.tsx | 6 ++++ src/components/shared/MemberCarousel.tsx | 7 +++++ 7 files changed, 65 insertions(+), 21 deletions(-) delete mode 100644 src/app/api/teams/get/route.ts create mode 100644 src/app/api/teams/members/[id]/route.ts create mode 100644 src/app/dashboard/team/page.tsx create mode 100644 src/components/shared/MemberCarousel.tsx diff --git a/src/app/api/teams/get/route.ts b/src/app/api/teams/get/route.ts deleted file mode 100644 index e5d5b23..0000000 --- a/src/app/api/teams/get/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextResponse } from "next/server"; -import { api } from "../../../../../convex/_generated/api"; -import { ConvexHttpClient } from "convex/browser"; - -export const GET = async () => { - try { - - const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); - - const teamInfo = await client.query(api.teams.getAllTeam); - - return NextResponse.json(teamInfo); - } catch (err) { - console.log(err) - } -}; diff --git a/src/app/api/teams/members/[id]/route.ts b/src/app/api/teams/members/[id]/route.ts new file mode 100644 index 0000000..ccd9cd0 --- /dev/null +++ b/src/app/api/teams/members/[id]/route.ts @@ -0,0 +1,28 @@ +import { ConvexHttpClient } from "convex/browser"; +import { api } from "../../../../../../convex/_generated/api"; +import { Id } from "../../../../../../convex/_generated/dataModel"; + +export async function GET( + request: Request, + { params }: { params: { id: string } } +) { + const {id} = params; + + + if (!id) return new Response('Parameters missing!!',{status: 401}); + + const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + + const teamInfo = await client.query(api.teams.getTeamById,{_id:id as Id<"teams">}); + + const memberDataPromises = teamInfo.teamMembers.map((mem:string) => + client.query(api.user.getUser, { email: mem }) + ); + + const results = await Promise.all(memberDataPromises); + + const memberData = results.flatMap((result) => result || []); + + return Response.json({memberData}); + +} diff --git a/src/app/dashboard/_components/SideNav.tsx b/src/app/dashboard/_components/SideNav.tsx index 17e1083..4f392d9 100644 --- a/src/app/dashboard/_components/SideNav.tsx +++ b/src/app/dashboard/_components/SideNav.tsx @@ -111,6 +111,7 @@ function SideNav() {
([]); const [fileInput, setFileInput] = useState(""); @@ -115,6 +123,17 @@ function SideNavBottomSection({ onFileCreate, totalFiles, activeTeam }: any) { ))} + {user?.email === activeTeam?.createdBy && +

+ + Team Settings +

+ } + {/* Add New File Button */} diff --git a/src/app/dashboard/profile/_components/TeamList.tsx b/src/app/dashboard/profile/_components/TeamList.tsx index 204db08..356611a 100644 --- a/src/app/dashboard/profile/_components/TeamList.tsx +++ b/src/app/dashboard/profile/_components/TeamList.tsx @@ -15,7 +15,6 @@ import { import moment from "moment"; import { useConvex } from "convex/react"; import { api } from "../../../../../convex/_generated/api"; -import { useState } from "react"; type Props = { teamList: Team[]; diff --git a/src/app/dashboard/team/page.tsx b/src/app/dashboard/team/page.tsx new file mode 100644 index 0000000..07c1556 --- /dev/null +++ b/src/app/dashboard/team/page.tsx @@ -0,0 +1,6 @@ + +export default function Page() { + return ( +
Page
+ ) +} \ No newline at end of file diff --git a/src/components/shared/MemberCarousel.tsx b/src/components/shared/MemberCarousel.tsx new file mode 100644 index 0000000..fc5b58a --- /dev/null +++ b/src/components/shared/MemberCarousel.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +export default function MemberCarousel() { + return ( +
MemberCarousel
+ ) +} \ No newline at end of file From 5d81ff523364143a78a9ace2a381c15f2d880bae Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Sun, 30 Jun 2024 17:06:04 +0530 Subject: [PATCH 04/40] feat/376 --- .../dashboard/team/_components/FileList.tsx | 394 ++++++++++++++++++ src/app/dashboard/team/page.tsx | 54 ++- src/components/shared/MemberCarousel.tsx | 82 +++- src/components/shared/MemberModal.tsx | 1 + src/lib/API-URLs.ts | 3 +- 5 files changed, 525 insertions(+), 9 deletions(-) create mode 100644 src/app/dashboard/team/_components/FileList.tsx diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx new file mode 100644 index 0000000..87d5ecc --- /dev/null +++ b/src/app/dashboard/team/_components/FileList.tsx @@ -0,0 +1,394 @@ +import { useState, useEffect } from "react"; +import { + Loader2, + Trash2, + ChevronsUpDown, + ArchiveIcon, + ArchiveRestore, + Clock, + Edit, + CheckCircle2, + EyeIcon, + Edit3Icon, +} from "lucide-react"; +import moment from "moment"; +import Image from "next/image"; +import { usePathname, useRouter } from "next/navigation"; +import { useConvex, useMutation } from "convex/react"; +import { Button } from "@/components/ui/button"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Badge } from "@/components/ui/badge"; + +export interface FILE { + archive: boolean; + createdBy: string; + document: string; + fileName: string; + teamId: string; + whiteboard: string; + _id: string; + _creationTime: number; + private: boolean; + read: boolean; + write: boolean; + readBy: string[]; + writtenBy: string[]; +} + +const ActionDialog = ({ + buttonIcon: ButtonIcon, + dialogTitle, + dialogDescription, + onAction, + buttonVariant = "secondary", + isSubmitted, + successTitle, +}: { + buttonIcon: typeof ArchiveIcon; + dialogTitle: string; + dialogDescription: string; + onAction: (e: any) => void; + buttonVariant?: + | "secondary" + | "link" + | "default" + | "destructive" + | "outline" + | "ghost" + | null; + isSubmitted: boolean; + successTitle: string; +}) => ( + + + + + + {!isSubmitted && ( + <> + + {dialogTitle} + {dialogDescription} + + + Cancel + + + + )} + {isSubmitted && ( + <> + + +

{successTitle}

+
+
+ + { + window.location.reload(); + }} + > + Continue + + + + )} +
+
+); + +const FileRow = ({ + file, + router, + user +}: { + file: FILE; + user:any; + router: ReturnType; +}) => ( + + router.push("/workspace/" + file._id)} + > + {file.fileName} + + router.push("/workspace/" + file._id)} + > + {file.createdBy} + + router.push("/workspace/" + file._id)} + > + {file.readBy && file.readBy.includes(user.email) && Read} + {file.writtenBy && file.writtenBy.includes(user.email) && Write} + {!file.readBy && !file.writtenBy && No Access} + + + + + {/* + {pathname === "/dashboard" && ( + onArchive(e, file._id)} + /> + )} + {pathname === "/dashboard/archive" && ( + onUnarchive(e, file._id)} + buttonVariant="destructive" + /> + )} + onDelete(e, file._id)} + buttonVariant="destructive" + /> */} + + +); + +function FileList({ + fileList, + user +}: { + fileList?: FILE[]; + user:any; +}) { + const router = useRouter(); + const convex = useConvex(); + const [sortConfig, setSortConfig] = useState<{ + key: keyof FILE; + direction: string; + } | null>(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) + + const sortedFiles = [...safeFileList]; + if (sortConfig !== null) { + sortedFiles.sort((a, b) => { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === "ascending" ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === "ascending" ? 1 : -1; + } + return 0; + }); + } + + const requestSort = (key: keyof FILE) => { + let direction = "ascending"; + if ( + sortConfig && + sortConfig.key === key && + sortConfig.direction === "ascending" + ) { + direction = "descending"; + } + setSortConfig({ key, direction }); + }; + + useEffect(() => { + const handleResize = () => { + setIsSmallScreen(window.innerWidth < 768); + }; + handleResize(); + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + return ( +
+
+ {!isSmallScreen ? ( + + + + + + + + + + {!fileList && ( + + + + )} + {fileList && !safeFileList.length && ( + + + + )} + {(sortedFiles.length > 0 ? sortedFiles : safeFileList).map( + (file, index) => ( + + ) + )} + +
requestSort("fileName")} + > + File Name + + Author + + Access +
+ {" "} + Loading... Please wait +
+ No files found +
+ ) : ( +
+
+
requestSort("fileName")} + > + File Name +
+
requestSort("_creationTime")} + > + Created At +
+
+ {sortedFiles.map((file, index) => ( +
router.push("/workspace/" + file._id)} + key={index} + className={`border p-4 mb-4 rounded ${index % 2 === 0 ? "bg-muted/50" : ""}`} + > +
+ {file.fileName} +
+ {/* + {pathname === "/dashboard" && ( + archiveFunc(e, file._id)} + /> + )} + {pathname === "/dashboard/archive" && ( + unarchiveFunc(e, file._id)} + buttonVariant="destructive" + /> + )} + deleteFunc(e, file._id)} + buttonVariant="destructive" + /> */} +
+
+
+
+
+ + {moment(file._creationTime).format("YYYY-MM-DD")} +
+
+ + {moment(file._creationTime).format("YYYY-MM-DD")} +
+
+ {/* */} +
+ +
+ {/* user */} +
+
+ ))} +
+ )} +
+
+ ); +} + +export default FileList; \ No newline at end of file diff --git a/src/app/dashboard/team/page.tsx b/src/app/dashboard/team/page.tsx index 07c1556..55c08d1 100644 --- a/src/app/dashboard/team/page.tsx +++ b/src/app/dashboard/team/page.tsx @@ -1,6 +1,54 @@ +"use client"; +import { RootState } from "@/app/store"; +import MemberCarousel, { USER } from "@/components/shared/MemberCarousel"; +import axiosInstance from "@/config/AxiosInstance"; +import { getTeamMembersData } from "@/lib/API-URLs"; +import { useContext, useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import FileList from "./_components/FileList"; +import { FileListContext } from "@/app/_context/FilesListContext"; export default function Page() { + const teamId = useSelector((state: RootState) => state.team.teamId); + const [teamMembersData, setData] = useState(null); + const [focusedUser, setFocusedUser] = useState(null); + const [fileList, setFileList] = useState(); + const { fileList_ } = useContext(FileListContext); + + useEffect(() => { + if (fileList_) { + setFileList(fileList_); + } + }, [fileList_]); + + useEffect(() => { + const getData = async () => { + try { + const res = await axiosInstance.get(`${getTeamMembersData}/${teamId}`); + console.log(res.data); + setData(res.data.memberData); + } catch (err) { + console.log(err); + } + }; + if (teamId) { + getData(); + } + }, [teamId]); + return ( -
Page
- ) -} \ No newline at end of file +
+

Team Settings

+ + {teamMembersData !== null && ( +
+ +
+ )} + +
+ {focusedUser !== null && } +
+
+ ); +} diff --git a/src/components/shared/MemberCarousel.tsx b/src/components/shared/MemberCarousel.tsx index fc5b58a..494a0f6 100644 --- a/src/components/shared/MemberCarousel.tsx +++ b/src/components/shared/MemberCarousel.tsx @@ -1,7 +1,79 @@ -import React from 'react' +"use client"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; +import { + Card, + CardContent, + CardDescription, + CardHeader, +} from "@/components/ui/card"; +import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; +import { Button } from "../ui/button"; +import { Trash2 } from "lucide-react"; +import { SetStateAction, useState } from "react"; + + export type USER = { + name: string; + _id: string; + email: string; + image: string; +}; + +type Props = { + teamMembersData: USER[]; + focusedUser:USER | null; + setFocusedUser:React.Dispatch>; +}; + +export default function MemberCarousel({ teamMembersData,focusedUser,setFocusedUser }: Props) { -export default function MemberCarousel() { return ( -
MemberCarousel
- ) -} \ No newline at end of file +
+ + + + {teamMembersData !== null && + teamMembersData.map((user: USER, index) => ( + setFocusedUser(user)} + key={index} + className="w-full group cursor-pointer sm:basis-1/2 md:basis-1/2" + > + + + + + + {user.name.charAt(0)} + + + {user.name} + + + +

Email : {user.email}

+ +
+
+
+
+ ))} +
+ +
+
+ ); +} diff --git a/src/components/shared/MemberModal.tsx b/src/components/shared/MemberModal.tsx index fdfa3e4..0290ec7 100644 --- a/src/components/shared/MemberModal.tsx +++ b/src/components/shared/MemberModal.tsx @@ -47,6 +47,7 @@ export default function MemberModal({ return ( diff --git a/src/lib/API-URLs.ts b/src/lib/API-URLs.ts index 1abae95..dd55a95 100644 --- a/src/lib/API-URLs.ts +++ b/src/lib/API-URLs.ts @@ -1,2 +1,3 @@ export const changeToPrivateUrl = "/api/files/private" -export const changeToPublicUrl = "/api/files/public" \ No newline at end of file +export const changeToPublicUrl = "/api/files/public" +export const getTeamMembersData = "/api/teams/members"; \ No newline at end of file From 185dc9da6b06a1f593937322fc89a7b47bd05829 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Mon, 1 Jul 2024 15:37:28 +0530 Subject: [PATCH 05/40] feat/382 --- src/app/api/teams/members/[id]/route.ts | 48 +++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/app/api/teams/members/[id]/route.ts b/src/app/api/teams/members/[id]/route.ts index ccd9cd0..7e75e69 100644 --- a/src/app/api/teams/members/[id]/route.ts +++ b/src/app/api/teams/members/[id]/route.ts @@ -6,16 +6,17 @@ export async function GET( request: Request, { params }: { params: { id: string } } ) { - const {id} = params; - - - if (!id) return new Response('Parameters missing!!',{status: 401}); - - const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); - - const teamInfo = await client.query(api.teams.getTeamById,{_id:id as Id<"teams">}); - - const memberDataPromises = teamInfo.teamMembers.map((mem:string) => + const { id } = params; + + if (!id) return new Response("Parameters missing!!", { status: 401 }); + + const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + + const teamInfo = await client.query(api.teams.getTeamById, { + _id: id as Id<"teams">, + }); + + const memberDataPromises = teamInfo.teamMembers.map((mem: string) => client.query(api.user.getUser, { email: mem }) ); @@ -23,6 +24,31 @@ export async function GET( const memberData = results.flatMap((result) => result || []); - return Response.json({memberData}); + return Response.json({ memberData }); +} + +export async function DELETE( + request: Request, + { params }: { params: { id: string } } +) { + const { id } = params; + + const {email} = await request.json(); + + if (!id) return new Response("Parameters missing!!", { status: 401 }); + + const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + + const teamInfo = await client.query(api.teams.getTeamById,{_id:id as Id<"teams">}); + + if(!teamInfo.teamMembers.includes(email)) return new Response("Not the member of team!!", { status: 404 }); + + if(teamInfo.createdBy === email) return new Response("Owner of the team!!", { status: 404 }); + + const updatedTeamMembers = teamInfo.teamMembers.filter((writer: any) => writer !== email); + + await client.mutation(api.teams.addMember, { _id: id as Id<"teams">, memberArray:updatedTeamMembers }); + + return new Response("Member removed!!", { status: 200 }); } From 90b1299fbd48daa15e44794939914dc910123276 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Mon, 1 Jul 2024 16:44:40 +0530 Subject: [PATCH 06/40] feat/384 --- src/app/api/teams/members/[id]/route.ts | 5 +- .../_components/SideNavTopSection.tsx | 2 + src/components/shared/DeleteTeamMember.tsx | 88 +++++++++++++++++++ src/components/shared/MemberCarousel.tsx | 13 +-- src/lib/API-URLs.ts | 3 +- 5 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 src/components/shared/DeleteTeamMember.tsx diff --git a/src/app/api/teams/members/[id]/route.ts b/src/app/api/teams/members/[id]/route.ts index 7e75e69..e2c5fb7 100644 --- a/src/app/api/teams/members/[id]/route.ts +++ b/src/app/api/teams/members/[id]/route.ts @@ -21,13 +21,14 @@ export async function GET( ); const results = await Promise.all(memberDataPromises); + console.log(results) const memberData = results.flatMap((result) => result || []); return Response.json({ memberData }); } -export async function DELETE( +export async function PUT( request: Request, { params }: { params: { id: string } } ) { @@ -49,6 +50,6 @@ export async function DELETE( await client.mutation(api.teams.addMember, { _id: id as Id<"teams">, memberArray:updatedTeamMembers }); - return new Response("Member removed!!", { status: 200 }); + return new Response("Member removd!!", { status: 200 }); } diff --git a/src/app/dashboard/_components/SideNavTopSection.tsx b/src/app/dashboard/_components/SideNavTopSection.tsx index 113b02d..0afd70f 100644 --- a/src/app/dashboard/_components/SideNavTopSection.tsx +++ b/src/app/dashboard/_components/SideNavTopSection.tsx @@ -48,6 +48,8 @@ function SideNavTopSection({ user, setActiveTeamInfo }: any) { const [teamMembersData, setTeamData] = useState([]); const [ActiveTeamMembers, setActiveTeamMembers] = useState([]); + console.log(activeTeam) + useEffect(() => { user && getTeamList(); }, [user]); diff --git a/src/components/shared/DeleteTeamMember.tsx b/src/components/shared/DeleteTeamMember.tsx new file mode 100644 index 0000000..7c81342 --- /dev/null +++ b/src/components/shared/DeleteTeamMember.tsx @@ -0,0 +1,88 @@ +"use client"; +import React, { useState } from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; +import { Button } from "../ui/button"; +import { CheckCircle2, Trash2 } from "lucide-react"; +import axiosInstance from "@/config/AxiosInstance"; +import { deleteTeamMemberUrl } from "@/lib/API-URLs"; +import { useSelector } from "react-redux"; +import { RootState } from "@/app/store"; +import { useRouter } from "next/navigation"; + +type Props = { + email: string; +}; + +export default function DeleteTeamMember({ email }: Props) { + const [isSubmitted, setIsSubmitted] = useState(false); + const router = useRouter(); + const teamId = useSelector((state: RootState) => state.team.teamId); + + const DeleteHandler = async () => { + try { + axiosInstance.put(`${deleteTeamMemberUrl}/${teamId}`, { email }) + .then((res)=>{ + if(res.status === 200) setIsSubmitted(true); + }) + } catch (err) { + console.log(err); + } + }; + + return ( + + + + + + {!isSubmitted && ( + <> + + Are you absolutely sure? + + This action cannot be undone. This will remove the member from + the team and files created by the member will be permanently + deleted. + + + + Cancel + + + + )} + {isSubmitted && ( + <> + + +

Team Member Removed Successfully!!

{" "} + +
+
+ + { + router.push('/dashboard') + }} + > + Continue + + + + )} +
+
+ ); +} diff --git a/src/components/shared/MemberCarousel.tsx b/src/components/shared/MemberCarousel.tsx index 494a0f6..3ee2c51 100644 --- a/src/components/shared/MemberCarousel.tsx +++ b/src/components/shared/MemberCarousel.tsx @@ -13,9 +13,8 @@ import { CardHeader, } from "@/components/ui/card"; import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; -import { Button } from "../ui/button"; -import { Trash2 } from "lucide-react"; -import { SetStateAction, useState } from "react"; +import { SetStateAction } from "react"; +import DeleteTeamMember from "./DeleteTeamMember"; export type USER = { name: string; @@ -31,7 +30,6 @@ type Props = { }; export default function MemberCarousel({ teamMembersData,focusedUser,setFocusedUser }: Props) { - return (
@@ -60,12 +58,7 @@ export default function MemberCarousel({ teamMembersData,focusedUser,setFocusedU

Email : {user.email}

- +
diff --git a/src/lib/API-URLs.ts b/src/lib/API-URLs.ts index dd55a95..b7f52e7 100644 --- a/src/lib/API-URLs.ts +++ b/src/lib/API-URLs.ts @@ -1,3 +1,4 @@ export const changeToPrivateUrl = "/api/files/private" export const changeToPublicUrl = "/api/files/public" -export const getTeamMembersData = "/api/teams/members"; \ No newline at end of file +export const getTeamMembersData = "/api/teams/members"; +export const deleteTeamMemberUrl = "/api/teams/members"; \ No newline at end of file From ac415db6d43cf4f7af87edeaf068c85c680ce9bc Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Mon, 1 Jul 2024 17:20:51 +0530 Subject: [PATCH 07/40] feat/387 --- src/app/api/teams/members/[id]/route.ts | 1 - src/components/shared/DeleteTeamMember.tsx | 31 +++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/app/api/teams/members/[id]/route.ts b/src/app/api/teams/members/[id]/route.ts index e2c5fb7..99488fa 100644 --- a/src/app/api/teams/members/[id]/route.ts +++ b/src/app/api/teams/members/[id]/route.ts @@ -21,7 +21,6 @@ export async function GET( ); const results = await Promise.all(memberDataPromises); - console.log(results) const memberData = results.flatMap((result) => result || []); diff --git a/src/components/shared/DeleteTeamMember.tsx b/src/components/shared/DeleteTeamMember.tsx index 7c81342..196dcfb 100644 --- a/src/components/shared/DeleteTeamMember.tsx +++ b/src/components/shared/DeleteTeamMember.tsx @@ -27,15 +27,21 @@ export default function DeleteTeamMember({ email }: Props) { const [isSubmitted, setIsSubmitted] = useState(false); const router = useRouter(); const teamId = useSelector((state: RootState) => state.team.teamId); + const [isError,setIsError] = useState(false); + const [errorMsg, setErrorMsg] = useState(""); const DeleteHandler = async () => { try { axiosInstance.put(`${deleteTeamMemberUrl}/${teamId}`, { email }) .then((res)=>{ if(res.status === 200) setIsSubmitted(true); + }).catch((err)=>{ + setIsError(true); + setErrorMsg(err.response.data) }) - } catch (err) { - console.log(err); + } catch (err:any) { + setIsError(true); + setErrorMsg(err.response.data) } }; @@ -47,7 +53,7 @@ export default function DeleteTeamMember({ email }: Props) { - {!isSubmitted && ( + {!isSubmitted && !isError && ( <> Are you absolutely sure? @@ -82,6 +88,25 @@ export default function DeleteTeamMember({ email }: Props) { )} + {isError && ( + <> + + +

{errorMsg}

{" "} + +
+
+ + { + router.push('/dashboard') + }} + > + Continue + + + + )}
); From 5d739e05ce09d217742d4bd07b5941efae53e0c0 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Mon, 1 Jul 2024 23:36:21 +0530 Subject: [PATCH 08/40] feat/390 --- .../dashboard/team/_components/FileList.tsx | 117 ++++++------------ src/app/dashboard/team/page.tsx | 2 +- src/components/shared/MemberCarousel.tsx | 10 +- 3 files changed, 41 insertions(+), 88 deletions(-) diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx index 87d5ecc..98532a6 100644 --- a/src/app/dashboard/team/_components/FileList.tsx +++ b/src/app/dashboard/team/_components/FileList.tsx @@ -113,10 +113,10 @@ const ActionDialog = ({ const FileRow = ({ file, router, - user + user, }: { file: FILE; - user:any; + user: any; router: ReturnType; }) => ( @@ -137,12 +137,18 @@ const FileRow = ({ onClick={() => router.push("/workspace/" + file._id)} > {file.readBy && file.readBy.includes(user.email) && Read} - {file.writtenBy && file.writtenBy.includes(user.email) && Write} + {file.writtenBy && file.writtenBy.includes(user.email) && ( + Write + )} {!file.readBy && !file.writtenBy && No Access} - - + + {/* {pathname === "/dashboard" && ( ); -function FileList({ - fileList, - user -}: { - fileList?: FILE[]; - user:any; -}) { +function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) { const router = useRouter(); const convex = useConvex(); const [sortConfig, setSortConfig] = useState<{ @@ -193,11 +193,10 @@ function FileList({ } | null>(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) + console.log(fileList); const sortedFiles = [...safeFileList]; if (sortConfig !== null) { @@ -276,10 +275,11 @@ function FileList({ {(sortedFiles.length > 0 ? sortedFiles : safeFileList).map( (file, index) => ( + user={user} + key={index} + file={file} + router={router} + /> ) )} @@ -293,12 +293,7 @@ function FileList({ > File Name
-
requestSort("_creationTime")} - > - Created At -
+
Access
{sortedFiles.map((file, index) => (
{file.fileName}
- {/* - {pathname === "/dashboard" && ( - archiveFunc(e, file._id)} - /> - )} - {pathname === "/dashboard/archive" && ( - unarchiveFunc(e, file._id)} - buttonVariant="destructive" - /> - )} - deleteFunc(e, file._id)} - buttonVariant="destructive" - /> */} + +
-
- - {moment(file._creationTime).format("YYYY-MM-DD")} -
-
- - {moment(file._creationTime).format("YYYY-MM-DD")} -
+ {file.readBy && file.readBy.includes(user.email) && ( + Read + )} + {file.writtenBy && file.writtenBy.includes(user.email) && ( + Write + )} + {!file.readBy && !file.writtenBy && ( + No Access + )}
- {/* */}
@@ -391,4 +344,4 @@ function FileList({ ); } -export default FileList; \ No newline at end of file +export default FileList; diff --git a/src/app/dashboard/team/page.tsx b/src/app/dashboard/team/page.tsx index 55c08d1..c24269b 100644 --- a/src/app/dashboard/team/page.tsx +++ b/src/app/dashboard/team/page.tsx @@ -38,7 +38,7 @@ export default function Page() { return (
-

Team Settings

+

Team Settings

{teamMembersData !== null && (
diff --git a/src/components/shared/MemberCarousel.tsx b/src/components/shared/MemberCarousel.tsx index 3ee2c51..cf24afc 100644 --- a/src/components/shared/MemberCarousel.tsx +++ b/src/components/shared/MemberCarousel.tsx @@ -31,7 +31,7 @@ type Props = { export default function MemberCarousel({ teamMembersData,focusedUser,setFocusedUser }: Props) { return ( -
+
@@ -40,13 +40,13 @@ export default function MemberCarousel({ teamMembersData,focusedUser,setFocusedU setFocusedUser(user)} key={index} - className="w-full group cursor-pointer sm:basis-1/2 md:basis-1/2" + className="w-full group cursor-pointer xl:basis-1/2" > - + @@ -56,8 +56,8 @@ export default function MemberCarousel({ teamMembersData,focusedUser,setFocusedU {user.name} - -

Email : {user.email}

+ +

Email : {user.email}

From 34127d8903c3738a399830b1de7760e179970afa Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Mon, 1 Jul 2024 23:55:01 +0530 Subject: [PATCH 09/40] feat/392 --- .../dashboard/team/_components/FileList.tsx | 51 ++------------- src/components/shared/ReadAccessModal.tsx | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+), 47 deletions(-) create mode 100644 src/components/shared/ReadAccessModal.tsx diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx index 98532a6..4926406 100644 --- a/src/app/dashboard/team/_components/FileList.tsx +++ b/src/app/dashboard/team/_components/FileList.tsx @@ -1,20 +1,13 @@ import { useState, useEffect } from "react"; import { Loader2, - Trash2, ChevronsUpDown, ArchiveIcon, - ArchiveRestore, - Clock, - Edit, CheckCircle2, - EyeIcon, Edit3Icon, } from "lucide-react"; -import moment from "moment"; -import Image from "next/image"; import { usePathname, useRouter } from "next/navigation"; -import { useConvex, useMutation } from "convex/react"; +import { useConvex } from "convex/react"; import { Button } from "@/components/ui/button"; import { AlertDialog, @@ -28,6 +21,7 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; +import ReadAccessModal from "@/components/shared/ReadAccessModal"; export interface FILE { archive: boolean; @@ -143,43 +137,10 @@ const FileRow = ({ {!file.readBy && !file.writtenBy && No Access} - + - {/* - {pathname === "/dashboard" && ( - onArchive(e, file._id)} - /> - )} - {pathname === "/dashboard/archive" && ( - onUnarchive(e, file._id)} - buttonVariant="destructive" - /> - )} - onDelete(e, file._id)} - buttonVariant="destructive" - /> */} ); @@ -196,8 +157,6 @@ function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) { const safeFileList = Array.isArray(fileList) ? fileList : []; const pathname = usePathname(); - console.log(fileList); - const sortedFiles = [...safeFileList]; if (sortConfig !== null) { sortedFiles.sort((a, b) => { @@ -304,9 +263,7 @@ function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) {
{file.fileName}
- + diff --git a/src/components/shared/ReadAccessModal.tsx b/src/components/shared/ReadAccessModal.tsx new file mode 100644 index 0000000..afa8987 --- /dev/null +++ b/src/components/shared/ReadAccessModal.tsx @@ -0,0 +1,63 @@ +"use client"; +import { EyeIcon } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { Button } from "../ui/button"; +import { useState } from "react"; + +export default function ReadAccessModal() { + const [isSubmitted, setIsSubmitted] = useState(false); + const [open, setOpen] = useState(false); + + const SubmitHandler = () => { + setIsSubmitted(true); + }; + + return ( + + + + + + {!isSubmitted && ( + <> + + +

Read File Access

+
+
+ + This will give the read file access to the member!! + +
+ + +
+ + )} + + {isSubmitted && ( + <> + + Read File Access granted!! + + + + + + )} +
+
+ ); +} From 717278f67199420bbc5906c71d7cff14355baf9e Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 00:04:06 +0530 Subject: [PATCH 10/40] feat/394 --- .../dashboard/team/_components/FileList.tsx | 9 +-- src/components/shared/ReadAccessModal.tsx | 8 ++- src/components/shared/WriteAccessModal.tsx | 69 +++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/components/shared/WriteAccessModal.tsx diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx index 4926406..3b13408 100644 --- a/src/app/dashboard/team/_components/FileList.tsx +++ b/src/app/dashboard/team/_components/FileList.tsx @@ -22,6 +22,7 @@ import { } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import ReadAccessModal from "@/components/shared/ReadAccessModal"; +import WriteAccessModal from "@/components/shared/WriteAccessModal"; export interface FILE { archive: boolean; @@ -138,9 +139,7 @@ const FileRow = ({ - + ); @@ -264,9 +263,7 @@ function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) { {file.fileName}
- +
diff --git a/src/components/shared/ReadAccessModal.tsx b/src/components/shared/ReadAccessModal.tsx index afa8987..e6d0f53 100644 --- a/src/components/shared/ReadAccessModal.tsx +++ b/src/components/shared/ReadAccessModal.tsx @@ -21,7 +21,13 @@ export default function ReadAccessModal() { }; return ( - + { + setOpen(!open); + setIsSubmitted(false); + }} + > + + + {!isSubmitted && ( + <> + + +

Write File Access

+
+
+ + This will give the write access to the member!! + +
+ + +
+ + )} + + {isSubmitted && ( + <> + + Write File Access granted!! + + + + + + )} +
+
+ ); +} From d12587ff1562edc86f3749066b1a609d2a741e97 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 00:23:43 +0530 Subject: [PATCH 11/40] feat/396 --- .../dashboard/archive/_components/Navbar.tsx | 2 +- src/app/dashboard/team/page.tsx | 40 +++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/app/dashboard/archive/_components/Navbar.tsx b/src/app/dashboard/archive/_components/Navbar.tsx index 4b3780e..6031b64 100644 --- a/src/app/dashboard/archive/_components/Navbar.tsx +++ b/src/app/dashboard/archive/_components/Navbar.tsx @@ -29,7 +29,7 @@ export default function Navbar() { - +

Archive

diff --git a/src/app/dashboard/team/page.tsx b/src/app/dashboard/team/page.tsx index c24269b..92b3278 100644 --- a/src/app/dashboard/team/page.tsx +++ b/src/app/dashboard/team/page.tsx @@ -4,9 +4,10 @@ import MemberCarousel, { USER } from "@/components/shared/MemberCarousel"; import axiosInstance from "@/config/AxiosInstance"; import { getTeamMembersData } from "@/lib/API-URLs"; import { useContext, useEffect, useState } from "react"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import FileList from "./_components/FileList"; import { FileListContext } from "@/app/_context/FilesListContext"; +import { toggleClose } from "@/app/Redux/Menu/menuSlice"; export default function Page() { const teamId = useSelector((state: RootState) => state.team.teamId); @@ -14,6 +15,8 @@ export default function Page() { const [focusedUser, setFocusedUser] = useState(null); const [fileList, setFileList] = useState(); const { fileList_ } = useContext(FileListContext); + const count = useSelector((state: RootState) => state.counter.value); + const dispatch = useDispatch(); useEffect(() => { if (fileList_) { @@ -38,16 +41,45 @@ export default function Page() { return (
-

Team Settings

+
+ {!count && ( + + )} +

+ Team Settings +

+
{teamMembersData !== null && (
- +
)}
- {focusedUser !== null && } + {focusedUser !== null && ( + + )}
); From 31a6c47afa810328e7c85a90a7c1f4226ce20615 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 00:32:01 +0530 Subject: [PATCH 12/40] feat/396 --- src/app/dashboard/profile/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/dashboard/profile/page.tsx b/src/app/dashboard/profile/page.tsx index 910bfe2..50cd845 100644 --- a/src/app/dashboard/profile/page.tsx +++ b/src/app/dashboard/profile/page.tsx @@ -107,7 +107,7 @@ export default function Page() { - + From 32a9229a91d14bf985bf63c6e3fc17e7a99c1051 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 14:41:29 +0530 Subject: [PATCH 13/40] feat: read --- src/app/api/files/read/route.ts | 39 ++++++++++++++++- .../_components/SideNavTopSection.tsx | 17 +++----- .../dashboard/team/_components/FileList.tsx | 7 ++-- src/components/shared/ReadAccessModal.tsx | 42 +++++++++++++++++-- src/lib/API-URLs.ts | 3 +- 5 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/app/api/files/read/route.ts b/src/app/api/files/read/route.ts index 95da066..33f45d4 100644 --- a/src/app/api/files/read/route.ts +++ b/src/app/api/files/read/route.ts @@ -1,6 +1,42 @@ import { api } from "../../../../../convex/_generated/api"; import { ConvexHttpClient } from "convex/browser"; +import { Id } from "../../../../../convex/_generated/dataModel"; +// Give user read access +export const POST = async (req: Request) => { + try { + const { teamId, email, memberEmail, readBy, fileId } = await req.json(); + + console.log(teamId, email, memberEmail, readBy, fileId); + + if (!teamId || !memberEmail || !email || !fileId) + return new Response("Parameters missing!!", { status: 401 }); + + const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + + const teamInfo = await client.query(api.teams.getTeamById, { _id: teamId as Id<"teams">}); + + + if (!teamInfo.teamMembers.includes(memberEmail)) { + return new Response("User is not member of the team", { status: 400 }); + } + + if (teamInfo.createdBy !== email) { + return new Response("Only owner can make changes!!", { status: 400 }); + } + + const updatedReadBy = readBy.push(memberEmail); + + const res = await client.mutation(api.files.updateRead, { _id: fileId, readBy:updatedReadBy as string[] }); + console.log(res) + console.log(teamInfo) + return new Response("Changed to Public!!", { status: 200 }); + } catch (err) { + return new Response(`Error: ${err}`, {status:500}) + } +}; + +// Remove read access from the user export const PUT = async (req: Request) => { try { const { teamId, email, memberEmail, readBy, fileId } = await req.json(); @@ -28,6 +64,7 @@ export const PUT = async (req: Request) => { return new Response("Changed to Public!!", { status: 200 }); } catch (err) { - console.log(err); + return new Response(`Error: ${err}`, {status:500}) + } }; \ No newline at end of file diff --git a/src/app/dashboard/_components/SideNavTopSection.tsx b/src/app/dashboard/_components/SideNavTopSection.tsx index 0afd70f..5f1e592 100644 --- a/src/app/dashboard/_components/SideNavTopSection.tsx +++ b/src/app/dashboard/_components/SideNavTopSection.tsx @@ -17,6 +17,8 @@ import { setTeamInfo } from "@/app/Redux/Team/team-slice"; import RenameTeamModal from "@/components/shared/RenameTeamModal"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import MembersList from "@/components/shared/MembersList"; +import axiosInstance from "@/config/AxiosInstance"; +import { getTeamMembersData } from "@/lib/API-URLs"; export interface TEAM { createdBy: string; @@ -48,8 +50,6 @@ function SideNavTopSection({ user, setActiveTeamInfo }: any) { const [teamMembersData, setTeamData] = useState([]); const [ActiveTeamMembers, setActiveTeamMembers] = useState([]); - console.log(activeTeam) - useEffect(() => { user && getTeamList(); }, [user]); @@ -69,22 +69,17 @@ function SideNavTopSection({ user, setActiveTeamInfo }: any) { useEffect(() => { const getData = async () => { if (ActiveTeamMembers) { - const memberDataPromises = ActiveTeamMembers.map((mem) => - convex.query(api.user.getUser, { email: mem }) - ); - - const results = await Promise.all(memberDataPromises); - - const memberData = results.flatMap((result) => result || []); + const res = await axiosInstance.get(`${getTeamMembersData}/${activeTeam?._id}`); + console.log(res.data); - setTeamData(memberData); + setTeamData(res.data.memberData); } }; if (teamList && activeTeam) { getData(); } - }, [ActiveTeamMembers]); + }, [ActiveTeamMembers, activeTeam]); useEffect(() => { activeTeam ? setActiveTeamInfo(activeTeam) : null; diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx index 3b13408..d7b5852 100644 --- a/src/app/dashboard/team/_components/FileList.tsx +++ b/src/app/dashboard/team/_components/FileList.tsx @@ -138,7 +138,7 @@ const FileRow = ({ {!file.readBy && !file.writtenBy && No Access} - + @@ -255,14 +255,13 @@ function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) {
{sortedFiles.map((file, index) => (
router.push("/workspace/" + file._id)} key={index} className={`border p-4 mb-4 rounded ${index % 2 === 0 ? "bg-muted/50" : ""}`} >
- {file.fileName} + router.push("/workspace/" + file._id)} className="font-bold text-xl">{file.fileName}
- +
diff --git a/src/components/shared/ReadAccessModal.tsx b/src/components/shared/ReadAccessModal.tsx index e6d0f53..65596da 100644 --- a/src/components/shared/ReadAccessModal.tsx +++ b/src/components/shared/ReadAccessModal.tsx @@ -11,13 +11,48 @@ import { } from "../ui/dialog"; import { Button } from "../ui/button"; import { useState } from "react"; +import { FILE } from "@/app/dashboard/team/_components/FileList"; +import { USER } from "./MemberCarousel"; +import axiosInstance from "@/config/AxiosInstance"; +import { updateReadAccessUrl } from "@/lib/API-URLs"; +import { useSelector } from "react-redux"; +import { RootState } from "@/app/store"; +import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs"; -export default function ReadAccessModal() { +type Props = { + file:FILE; + focusedUser:USER; +} + +export default function ReadAccessModal({file,focusedUser}:Props) { const [isSubmitted, setIsSubmitted] = useState(false); const [open, setOpen] = useState(false); + const teamId = useSelector((state:RootState) => state.team.teamId); + const { user }: any = useKindeBrowserClient(); + console.log(focusedUser) - const SubmitHandler = () => { + const SubmitHandler = async() => { setIsSubmitted(true); + + if(file.readBy && file.readBy.includes(focusedUser.email)){ + console.log("added!!") + }else{ + try { + await axiosInstance.post(`${updateReadAccessUrl}`,{ + teamId, + email:user.email, + memberEmail:focusedUser.email, + readBy: (file.readBy !== undefined ? file.readBy : []) , + fileId:file._id + }) + .then((res)=>{ + if(res.status === 200) setIsSubmitted(true) + }) + } catch (err) { + console.log(err) + } + } + }; return ( @@ -42,7 +77,8 @@ export default function ReadAccessModal() { - This will give the read file access to the member!! + { (!file?.readBy?.includes(focusedUser.email) && "This will give the read file access to the member!!")} + {file.readBy && file.readBy.includes(focusedUser.email) && "This will remove the read file access from the member!!"}
)} -
+
{focusedUser !== null && ( )} diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx index d7b5852..d0c5442 100644 --- a/src/app/dashboard/team/_components/FileList.tsx +++ b/src/app/dashboard/team/_components/FileList.tsx @@ -131,10 +131,13 @@ const FileRow = ({ className="whitespace-nowrap px-4 py-2 text-muted-foreground" onClick={() => router.push("/workspace/" + file._id)} > - {file.readBy && file.readBy.includes(user.email) && Read} - {file.writtenBy && file.writtenBy.includes(user.email) && ( - Write - )} + {file.readBy + ? file.readBy.includes(user.email.toString()) && Read + : ""} + {file.writtenBy + ? file.writtenBy && + file.writtenBy.includes(user.email) && Write + : ""} {!file.readBy && !file.writtenBy && No Access} @@ -193,6 +196,8 @@ function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) { }; }, []); + console.log(fileList) + return (
@@ -259,7 +264,12 @@ function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) { className={`border p-4 mb-4 rounded ${index % 2 === 0 ? "bg-muted/50" : ""}`} >
- router.push("/workspace/" + file._id)} className="font-bold text-xl">{file.fileName} + router.push("/workspace/" + file._id)} + className="font-bold text-xl" + > + {file.fileName} +
diff --git a/src/app/teams/settings/[id]/page.tsx b/src/app/teams/settings/[id]/page.tsx new file mode 100644 index 0000000..3a13292 --- /dev/null +++ b/src/app/teams/settings/[id]/page.tsx @@ -0,0 +1,74 @@ +"use client"; +import MemberCarousel, { USER } from "@/components/shared/MemberCarousel"; +import axiosInstance from "@/config/AxiosInstance"; +import { getTeamMembersData } from "@/lib/API-URLs"; +import FileList, { FILE } from "../_components/FileList"; +import { useConvex } from "convex/react"; +import { api } from "../../../../../convex/_generated/api"; +import { ArrowBigLeft } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; + +export default function Page({ params }: { params: { id: string } }) { + const teamId = params.id; + const convex = useConvex(); + const [teamMembersData, setData] = useState(null); + const [focusedUser, setFocusedUser] = useState(null); + const [fileList, setFileList] = useState([]); + const [updated,setIsUpdated] = useState(true); + + useEffect(() => { + const getData = async () => { + const res = await convex.query(api.files.getFiles, { teamId: teamId }); + setFileList(res); + setIsUpdated(false); + }; + + if (teamId && updated) { + getData(); + } + }, [teamId,updated]); + + useEffect(() => { + const getData = async () => { + try { + const res = await axiosInstance.get(`${getTeamMembersData}/${teamId}`); + setData(res.data.memberData); + } catch (err) { + console.log(err); + } + }; + if (teamId) { + getData(); + } + }, [teamId]); + + return ( +
+
+ + + +

+ Team Settings +

+
+ + {teamMembersData !== null && ( +
+ +
+ )} + +
+ {focusedUser !== null && ( + + )} +
+
+ ); +} diff --git a/src/app/teams/settings/_components/FileList.tsx b/src/app/teams/settings/_components/FileList.tsx new file mode 100644 index 0000000..514a9ca --- /dev/null +++ b/src/app/teams/settings/_components/FileList.tsx @@ -0,0 +1,264 @@ +import { useState, useEffect, SetStateAction } from "react"; +import { + Loader2, + ChevronsUpDown, + ArchiveIcon, + CheckCircle2, + Edit3Icon, +} from "lucide-react"; +import { usePathname, useRouter } from "next/navigation"; +import { useConvex } from "convex/react"; +import { Button } from "@/components/ui/button"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Badge } from "@/components/ui/badge"; +import ReadAccessModal from "@/components/shared/ReadAccessModal"; +import WriteAccessModal from "@/components/shared/WriteAccessModal"; + +export interface FILE { + archive: boolean; + createdBy: string; + document: string; + fileName: string; + teamId: string; + whiteboard: string; + _id: string; + _creationTime: number; + private: boolean; + read: boolean; + write: boolean; + readBy: string[]; + writtenBy: string[]; +} + +const FileRow = ({ + file, + router, + user, + teamId, + setIsUpdated +}: { + file: FILE; + user: any; + router: ReturnType; + setIsUpdated:React.Dispatch>; + teamId: string; +}) => ( + + router.push("/workspace/" + file._id)} + > + {file.fileName} + + router.push("/workspace/" + file._id)} + > + {file.createdBy} + + router.push("/workspace/" + file._id)} + > + {file.readBy + ? file.readBy.includes(user.email.toString()) && Read + : ""} + {file.writtenBy + ? file.writtenBy && + file.writtenBy.includes(user.email) && Write + : ""} + {!file.readBy.includes(user.email) && + !file.writtenBy.includes(user.email) && No Access} + + + + + + +); + +function FileList({ + fileList, + user, + teamId, + setIsUpdated +}: { + fileList?: FILE[]; + user: any; + teamId: string; + setIsUpdated:React.Dispatch>; +}) { + const router = useRouter(); + const convex = useConvex(); + const [sortConfig, setSortConfig] = useState<{ + key: keyof FILE; + direction: string; + } | null>(null); + const [isSmallScreen, setIsSmallScreen] = useState(false); + const safeFileList = Array.isArray(fileList) ? fileList : []; + + const sortedFiles = [...safeFileList]; + if (sortConfig !== null) { + sortedFiles.sort((a, b) => { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === "ascending" ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === "ascending" ? 1 : -1; + } + return 0; + }); + } + + const requestSort = (key: keyof FILE) => { + let direction = "ascending"; + if ( + sortConfig && + sortConfig.key === key && + sortConfig.direction === "ascending" + ) { + direction = "descending"; + } + setSortConfig({ key, direction }); + }; + + useEffect(() => { + const handleResize = () => { + setIsSmallScreen(window.innerWidth < 768); + }; + handleResize(); + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + return ( +
+
+ {!isSmallScreen ? ( + + + + + + + + + + {!fileList && ( + + + + )} + {fileList && !safeFileList.length && ( + + + + )} + {(sortedFiles.length > 0 ? sortedFiles : safeFileList).map( + (file, index) => ( + + ) + )} + +
requestSort("fileName")} + > + File Name + + Author + + Access +
+ {" "} + Loading... Please wait +
+ No files found +
+ ) : ( +
+
+
requestSort("fileName")} + > + File Name +
+
Access
+
+ {sortedFiles.map((file, index) => ( +
+
+ router.push("/workspace/" + file._id)} + className="font-bold text-xl" + > + {file.fileName} + +
+ + +
+
+
+
+ {file.readBy && file.readBy.includes(user.email) && ( + Read + )} + {file.writtenBy && file.writtenBy.includes(user.email) && ( + Write + )} + {!file.readBy.includes(user.email) && + !file.writtenBy.includes(user.email) && ( + No Access + )} +
+
+ +
+ {/* user */} +
+
+ ))} +
+ )} +
+
+ ); +} + +export default FileList; diff --git a/src/components/shared/MemberModal.tsx b/src/components/shared/MemberModal.tsx index 0290ec7..7da232b 100644 --- a/src/components/shared/MemberModal.tsx +++ b/src/components/shared/MemberModal.tsx @@ -86,8 +86,12 @@ export default function MemberModal({

{file.fileName}

- { file.readBy?.includes(email) && Read} - { file.writtenBy?.includes(email) && Write} + {file.readBy && file.readBy.includes(email) && ( + Read + )} + {file.writtenBy && file.writtenBy.includes(email) && ( + Write + )}
))} diff --git a/src/components/shared/ReadAccessModal.tsx b/src/components/shared/ReadAccessModal.tsx index 65596da..4c980bf 100644 --- a/src/components/shared/ReadAccessModal.tsx +++ b/src/components/shared/ReadAccessModal.tsx @@ -1,85 +1,92 @@ "use client"; import { EyeIcon } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "../ui/dialog"; import { Button } from "../ui/button"; -import { useState } from "react"; +import { SetStateAction, useState } from "react"; import { FILE } from "@/app/dashboard/team/_components/FileList"; import { USER } from "./MemberCarousel"; import axiosInstance from "@/config/AxiosInstance"; import { updateReadAccessUrl } from "@/lib/API-URLs"; -import { useSelector } from "react-redux"; -import { RootState } from "@/app/store"; import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; type Props = { - file:FILE; - focusedUser:USER; -} + file: FILE; + setIsUpdated: React.Dispatch>; + focusedUser: USER; + teamId: string; +}; -export default function ReadAccessModal({file,focusedUser}:Props) { +export default function ReadAccessModal({ + file, + focusedUser, + teamId, + setIsUpdated, +}: Props) { const [isSubmitted, setIsSubmitted] = useState(false); const [open, setOpen] = useState(false); - const teamId = useSelector((state:RootState) => state.team.teamId); const { user }: any = useKindeBrowserClient(); - console.log(focusedUser) - const SubmitHandler = async() => { - setIsSubmitted(true); - - if(file.readBy && file.readBy.includes(focusedUser.email)){ - console.log("added!!") - }else{ + const SubmitHandler = async () => { + if (file.readBy && file.readBy.includes(focusedUser.email)) { try { - await axiosInstance.post(`${updateReadAccessUrl}`,{ + const res = await axiosInstance.put(`${updateReadAccessUrl}`, { teamId, - email:user.email, - memberEmail:focusedUser.email, - readBy: (file.readBy !== undefined ? file.readBy : []) , - fileId:file._id - }) - .then((res)=>{ - if(res.status === 200) setIsSubmitted(true) - }) + email: user.email, + memberEmail: focusedUser.email, + readBy: file.readBy !== undefined ? file.readBy : [], + fileId: file._id, + }); + if (res.status === 200) setIsSubmitted(true); } catch (err) { - console.log(err) + console.log(err); + } + } else { + try { + const res = await axiosInstance.post(`${updateReadAccessUrl}`, { + teamId, + email: user.email, + memberEmail: focusedUser.email, + readBy: file.readBy !== undefined ? file.readBy : [], + fileId: file._id, + }); + if (res.status === 200) setIsSubmitted(true); + } catch (err) { + console.log(err); } } - }; return ( - { - setOpen(!open); - setIsSubmitted(false); - }} - > - + + - - + + {!isSubmitted && ( <> - - + +

Read File Access

-
-
- - { (!file?.readBy?.includes(focusedUser.email) && "This will give the read file access to the member!!")} - {file.readBy && file.readBy.includes(focusedUser.email) && "This will remove the read file access from the member!!"} - + + + + {!file?.readBy?.includes(focusedUser.email) && + "This will give the read file access to the member!!"} + {file.readBy && + file.readBy.includes(focusedUser.email) && + "This will remove the read file access from the member!!"} +
- + + + {!file?.readBy?.includes(focusedUser.email) && + "Read File Access Granted!!"} + {file.readBy && + file.readBy.includes(focusedUser.email) && + "Read File Access Removed!!"} + + + + { + setIsUpdated(true); + setIsSubmitted(false) + }} + > + Close + + )} - -
+ + ); } From 7bb8fbe279eac19f299c822ec20ed8fd1d20643a Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 18:15:29 +0530 Subject: [PATCH 17/40] fix: build --- src/app/dashboard/team/[id]/page.tsx | 86 ----- .../dashboard/team/_components/FileList.tsx | 310 ------------------ 2 files changed, 396 deletions(-) delete mode 100644 src/app/dashboard/team/[id]/page.tsx delete mode 100644 src/app/dashboard/team/_components/FileList.tsx diff --git a/src/app/dashboard/team/[id]/page.tsx b/src/app/dashboard/team/[id]/page.tsx deleted file mode 100644 index 294e9f0..0000000 --- a/src/app/dashboard/team/[id]/page.tsx +++ /dev/null @@ -1,86 +0,0 @@ -"use client"; -import { RootState } from "@/app/store"; -import MemberCarousel, { USER } from "@/components/shared/MemberCarousel"; -import axiosInstance from "@/config/AxiosInstance"; -import { getTeamMembersData } from "@/lib/API-URLs"; -import { useContext, useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import FileList from "../_components/FileList"; -import { FileListContext } from "@/app/_context/FilesListContext"; -import { toggleClose } from "@/app/Redux/Menu/menuSlice"; - -export default function Page({ params }: { params: { id: string } }) { - const teamId = params.id; - const [teamMembersData, setData] = useState(null); - const [focusedUser, setFocusedUser] = useState(null); - const [fileList, setFileList] = useState(); - const { fileList_ } = useContext(FileListContext); - const count = useSelector((state: RootState) => state.counter.value); - const dispatch = useDispatch(); - - useEffect(() => { - if (fileList_) { - setFileList(fileList_); - } - }, [fileList_]); - - useEffect(() => { - const getData = async () => { - try { - const res = await axiosInstance.get(`${getTeamMembersData}/${teamId}`); - console.log(res.data); - setData(res.data.memberData); - } catch (err) { - console.log(err); - } - }; - if (teamId) { - getData(); - } - }, [teamId]); - - return ( -
-
- {!count && ( - - )} -

- Team Settings -

-
- - {teamMembersData !== null && ( -
- -
- )} - -
- {focusedUser !== null && ( - - )} -
-
- ); -} diff --git a/src/app/dashboard/team/_components/FileList.tsx b/src/app/dashboard/team/_components/FileList.tsx deleted file mode 100644 index d0c5442..0000000 --- a/src/app/dashboard/team/_components/FileList.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import { useState, useEffect } from "react"; -import { - Loader2, - ChevronsUpDown, - ArchiveIcon, - CheckCircle2, - Edit3Icon, -} from "lucide-react"; -import { usePathname, useRouter } from "next/navigation"; -import { useConvex } from "convex/react"; -import { Button } from "@/components/ui/button"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Badge } from "@/components/ui/badge"; -import ReadAccessModal from "@/components/shared/ReadAccessModal"; -import WriteAccessModal from "@/components/shared/WriteAccessModal"; - -export interface FILE { - archive: boolean; - createdBy: string; - document: string; - fileName: string; - teamId: string; - whiteboard: string; - _id: string; - _creationTime: number; - private: boolean; - read: boolean; - write: boolean; - readBy: string[]; - writtenBy: string[]; -} - -const ActionDialog = ({ - buttonIcon: ButtonIcon, - dialogTitle, - dialogDescription, - onAction, - buttonVariant = "secondary", - isSubmitted, - successTitle, -}: { - buttonIcon: typeof ArchiveIcon; - dialogTitle: string; - dialogDescription: string; - onAction: (e: any) => void; - buttonVariant?: - | "secondary" - | "link" - | "default" - | "destructive" - | "outline" - | "ghost" - | null; - isSubmitted: boolean; - successTitle: string; -}) => ( - - - - - - {!isSubmitted && ( - <> - - {dialogTitle} - {dialogDescription} - - - Cancel - - - - )} - {isSubmitted && ( - <> - - -

{successTitle}

-
-
- - { - window.location.reload(); - }} - > - Continue - - - - )} -
-
-); - -const FileRow = ({ - file, - router, - user, -}: { - file: FILE; - user: any; - router: ReturnType; -}) => ( - - router.push("/workspace/" + file._id)} - > - {file.fileName} - - router.push("/workspace/" + file._id)} - > - {file.createdBy} - - router.push("/workspace/" + file._id)} - > - {file.readBy - ? file.readBy.includes(user.email.toString()) && Read - : ""} - {file.writtenBy - ? file.writtenBy && - file.writtenBy.includes(user.email) && Write - : ""} - {!file.readBy && !file.writtenBy && No Access} - - - - - - -); - -function FileList({ fileList, user }: { fileList?: FILE[]; user: any }) { - const router = useRouter(); - const convex = useConvex(); - const [sortConfig, setSortConfig] = useState<{ - key: keyof FILE; - direction: string; - } | null>(null); - const [isSmallScreen, setIsSmallScreen] = useState(false); - const [isSubmitted, setIsSubmitted] = useState(false); - const safeFileList = Array.isArray(fileList) ? fileList : []; - const pathname = usePathname(); - - const sortedFiles = [...safeFileList]; - if (sortConfig !== null) { - sortedFiles.sort((a, b) => { - if (a[sortConfig.key] < b[sortConfig.key]) { - return sortConfig.direction === "ascending" ? -1 : 1; - } - if (a[sortConfig.key] > b[sortConfig.key]) { - return sortConfig.direction === "ascending" ? 1 : -1; - } - return 0; - }); - } - - const requestSort = (key: keyof FILE) => { - let direction = "ascending"; - if ( - sortConfig && - sortConfig.key === key && - sortConfig.direction === "ascending" - ) { - direction = "descending"; - } - setSortConfig({ key, direction }); - }; - - useEffect(() => { - const handleResize = () => { - setIsSmallScreen(window.innerWidth < 768); - }; - handleResize(); - window.addEventListener("resize", handleResize); - - return () => { - window.removeEventListener("resize", handleResize); - }; - }, []); - - console.log(fileList) - - return ( -
-
- {!isSmallScreen ? ( - - - - - - - - - - {!fileList && ( - - - - )} - {fileList && !safeFileList.length && ( - - - - )} - {(sortedFiles.length > 0 ? sortedFiles : safeFileList).map( - (file, index) => ( - - ) - )} - -
requestSort("fileName")} - > - File Name - - Author - - Access -
- {" "} - Loading... Please wait -
- No files found -
- ) : ( -
-
-
requestSort("fileName")} - > - File Name -
-
Access
-
- {sortedFiles.map((file, index) => ( -
-
- router.push("/workspace/" + file._id)} - className="font-bold text-xl" - > - {file.fileName} - -
- - -
-
-
-
- {file.readBy && file.readBy.includes(user.email) && ( - Read - )} - {file.writtenBy && file.writtenBy.includes(user.email) && ( - Write - )} - {!file.readBy && !file.writtenBy && ( - No Access - )} -
-
- -
- {/* user */} -
-
- ))} -
- )} -
-
- ); -} - -export default FileList; From 55e73a360b5a8f5fdfb5e3da6a40ceafab678fca Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 18:16:44 +0530 Subject: [PATCH 18/40] fix: build --- src/components/shared/ReadAccessModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shared/ReadAccessModal.tsx b/src/components/shared/ReadAccessModal.tsx index 4c980bf..fed04df 100644 --- a/src/components/shared/ReadAccessModal.tsx +++ b/src/components/shared/ReadAccessModal.tsx @@ -2,7 +2,6 @@ import { EyeIcon } from "lucide-react"; import { Button } from "../ui/button"; import { SetStateAction, useState } from "react"; -import { FILE } from "@/app/dashboard/team/_components/FileList"; import { USER } from "./MemberCarousel"; import axiosInstance from "@/config/AxiosInstance"; import { updateReadAccessUrl } from "@/lib/API-URLs"; @@ -17,6 +16,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "../ui/alert-dialog"; +import { FILE } from "@/app/teams/settings/_components/FileList"; type Props = { file: FILE; From b8c8ad7f9cbb012235202f73e74c6ee348841cd0 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 18:30:19 +0530 Subject: [PATCH 19/40] feat/404 --- convex/files.tsx | 6 +---- src/app/api/files/write/route.ts | 45 +++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/convex/files.tsx b/convex/files.tsx index b7beb5d..ccfba0e 100644 --- a/convex/files.tsx +++ b/convex/files.tsx @@ -180,11 +180,7 @@ export const changeToPublic = mutation({ }); export const updateWrite = mutation({ - args: { - _id: v.id("files"), - writtenBy: v.array(v.string()) - }, - handler: async (ctx, args) => { + handler: async (ctx, args:{_id : Id<"files">, writtenBy:string[]}) => { const { _id,writtenBy } = args; const res = await ctx.db.patch(_id, { writtenBy, write:true, read:true }); return res; diff --git a/src/app/api/files/write/route.ts b/src/app/api/files/write/route.ts index 8b057b2..ece9398 100644 --- a/src/app/api/files/write/route.ts +++ b/src/app/api/files/write/route.ts @@ -1,11 +1,46 @@ import { api } from "../../../../../convex/_generated/api"; import { ConvexHttpClient } from "convex/browser"; +import { Id } from "../../../../../convex/_generated/dataModel"; +// Give write read access +export const POST = async (req: Request) => { + try { + const { teamId, email, memberEmail, writtenBy, fileId } = await req.json(); + + if (!teamId || !memberEmail || !email || !fileId || !writtenBy) + return new Response("Parameters missing!!", { status: 401 }); + + const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + + const teamInfo = await client.query(api.teams.getTeamById, { _id: teamId as Id<"teams">}); + + if (!teamInfo.teamMembers.includes(memberEmail)) { + return new Response("User is not member of the team", { status: 400 }); + } + + if (teamInfo.createdBy !== email) { + return new Response("Only owner can make changes!!", { status: 400 }); + } + + writtenBy.push(memberEmail); + + await client.mutation(api.files.updateWrite, { _id: fileId as Id<"files">, writtenBy:writtenBy }); + + return new Response("Read Access given!!", { status: 200 }); + } catch (err) { + + return new Response(`Error: ${err}`, {status:500}) + + } +}; + + +// Remove write access from the user export const PUT = async (req: Request) => { try { const { teamId, email, memberEmail, writtenBy, fileId } = await req.json(); - if (!teamId || !memberEmail || !email || !fileId) + if (!teamId || !memberEmail || !email || !fileId || !writtenBy) return new Response("Parameters missing!!", { status: 401 }); const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); @@ -20,9 +55,13 @@ export const PUT = async (req: Request) => { return new Response("Only owner can make changes!!", { status: 400 }); } - await client.mutation(api.files.updateWrite, { _id: fileId, writtenBy: [...writtenBy,memberEmail] }); + const updatedWrittenBy = Array.isArray(writtenBy) + ? writtenBy.filter(writer => writer !== memberEmail) + : []; + + await client.mutation(api.files.updateWrite, { _id: fileId, writtenBy:updatedWrittenBy }); - return new Response("Changed to Public!!", { status: 200 }); + return new Response("Read access removed!!", { status: 200 }); } catch (err) { console.log(err); } From fcc57ad22c6bc148f92dd5dd13767ef9ffd6f42e Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 19:07:57 +0530 Subject: [PATCH 20/40] feat: write functionality --- src/app/api/files/write/route.ts | 11 +- .../teams/settings/_components/FileList.tsx | 31 +++- src/components/shared/WriteAccessModal.tsx | 139 +++++++++++++----- src/lib/API-URLs.ts | 3 +- 4 files changed, 132 insertions(+), 52 deletions(-) diff --git a/src/app/api/files/write/route.ts b/src/app/api/files/write/route.ts index ece9398..9471fe2 100644 --- a/src/app/api/files/write/route.ts +++ b/src/app/api/files/write/route.ts @@ -5,9 +5,9 @@ import { Id } from "../../../../../convex/_generated/dataModel"; // Give write read access export const POST = async (req: Request) => { try { - const { teamId, email, memberEmail, writtenBy, fileId } = await req.json(); + const { teamId, email, memberEmail, writtenBy, readBy, fileId } = await req.json(); - if (!teamId || !memberEmail || !email || !fileId || !writtenBy) + if (!teamId || !memberEmail || !email || !fileId || !writtenBy || !readBy) return new Response("Parameters missing!!", { status: 401 }); const client = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); @@ -21,9 +21,14 @@ export const POST = async (req: Request) => { if (teamInfo.createdBy !== email) { return new Response("Only owner can make changes!!", { status: 400 }); } + + if(!readBy.includes(memberEmail)){ + readBy.push(memberEmail) + } writtenBy.push(memberEmail); - + + await client.mutation(api.files.updateRead, { _id: fileId as Id<"files">, readBy:readBy }); await client.mutation(api.files.updateWrite, { _id: fileId as Id<"files">, writtenBy:writtenBy }); return new Response("Read Access given!!", { status: 200 }); diff --git a/src/app/teams/settings/_components/FileList.tsx b/src/app/teams/settings/_components/FileList.tsx index 514a9ca..a1af404 100644 --- a/src/app/teams/settings/_components/FileList.tsx +++ b/src/app/teams/settings/_components/FileList.tsx @@ -45,12 +45,12 @@ const FileRow = ({ router, user, teamId, - setIsUpdated + setIsUpdated, }: { file: FILE; user: any; router: ReturnType; - setIsUpdated:React.Dispatch>; + setIsUpdated: React.Dispatch>; teamId: string; }) => ( @@ -81,8 +81,18 @@ const FileRow = ({ !file.writtenBy.includes(user.email) && No Access} - - + + ); @@ -91,12 +101,12 @@ function FileList({ fileList, user, teamId, - setIsUpdated + setIsUpdated, }: { fileList?: FILE[]; user: any; teamId: string; - setIsUpdated:React.Dispatch>; + setIsUpdated: React.Dispatch>; }) { const router = useRouter(); const convex = useConvex(); @@ -184,7 +194,7 @@ function FileList({ {(sortedFiles.length > 0 ? sortedFiles : safeFileList).map( (file, index) => ( - +
diff --git a/src/components/shared/WriteAccessModal.tsx b/src/components/shared/WriteAccessModal.tsx index 23020f3..ebe0194 100644 --- a/src/components/shared/WriteAccessModal.tsx +++ b/src/components/shared/WriteAccessModal.tsx @@ -1,49 +1,95 @@ "use client"; -import { Edit3Icon, EyeIcon } from "lucide-react"; +import { Edit3Icon } from "lucide-react"; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "../ui/dialog"; + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; import { Button } from "../ui/button"; -import { useState } from "react"; +import { SetStateAction, useState } from "react"; +import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs"; +import { FILE } from "@/app/teams/settings/_components/FileList"; +import { USER } from "./MemberCarousel"; +import axiosInstance from "@/config/AxiosInstance"; +import { updateWriteAccessUrl } from "@/lib/API-URLs"; -export default function WriteAccessModal() { +type Props = { + file: FILE; + setIsUpdated: React.Dispatch>; + focusedUser: USER; + teamId: string; +}; + +export default function WriteAccessModal({ + file, + focusedUser, + teamId, + setIsUpdated, +}:Props) { const [isSubmitted, setIsSubmitted] = useState(false); const [open, setOpen] = useState(false); + const { user }: any = useKindeBrowserClient(); + + console.log(user) - const SubmitHandler = () => { - setIsSubmitted(true); + const SubmitHandler = async () => { + if (file.writtenBy && file.writtenBy.includes(focusedUser.email)) { + try { + const res = await axiosInstance.put(`${updateWriteAccessUrl}`, { + teamId, + email: user.email, + memberEmail: focusedUser.email, + writtenBy: file.writtenBy !== undefined ? file.writtenBy : [], + fileId: file._id, + }); + if (res.status === 200) setIsSubmitted(true); + } catch (err) { + console.log(err); + } + } else { + try { + const res = await axiosInstance.post(`${updateWriteAccessUrl}`, { + teamId, + email: user.email, + memberEmail: focusedUser.email, + writtenBy: file.writtenBy !== undefined ? file.writtenBy : [], + fileId: file._id, + readBy:file.readBy + }); + if (res.status === 200) setIsSubmitted(true); + } catch (err) { + console.log(err); + } + } }; return ( - { - setOpen(!open); - setIsSubmitted(false); - }} - > - - - - + + {!isSubmitted && ( <> - - + +

Write File Access

-
-
- - This will give the write access to the member!! - + + + + {!file?.writtenBy?.includes(focusedUser.email) && + "This will give the write file access to the member!!"} + {file.writtenBy && + file.writtenBy.includes(focusedUser.email) && + "This will remove the write file access from the member!!"} +
- + + + {!file?.writtenBy?.includes(focusedUser.email) && + "Write File Access Granted!!"} + {file.writtenBy && + file.writtenBy.includes(focusedUser.email) && + "Write File Access Removed!!"} + + + + { + setIsUpdated(true); + setIsSubmitted(false) + }} + > + Close + + )} - -
+ + ); } diff --git a/src/lib/API-URLs.ts b/src/lib/API-URLs.ts index aab4ccd..1144ade 100644 --- a/src/lib/API-URLs.ts +++ b/src/lib/API-URLs.ts @@ -2,4 +2,5 @@ export const changeToPrivateUrl = "/api/files/private" export const changeToPublicUrl = "/api/files/public" export const getTeamMembersData = "/api/teams/members"; export const deleteTeamMemberUrl = "/api/teams/members"; -export const updateReadAccessUrl = "/api/files/read"; \ No newline at end of file +export const updateReadAccessUrl = "/api/files/read"; +export const updateWriteAccessUrl = "/api/files/write"; \ No newline at end of file From fe2724408ce61b30d64f013382a1f9e2d8966c1e Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Tue, 2 Jul 2024 22:57:33 +0530 Subject: [PATCH 21/40] feat/407 --- src/app/workspace/[fileId]/page.tsx | 37 ++++++++++++++++++---- src/app/workspace/_components/Editor.tsx | 4 +-- src/components/shared/PrivateAlert.tsx | 34 ++++++++++++++++++++ src/components/shared/WriteAccessModal.tsx | 2 -- 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 src/components/shared/PrivateAlert.tsx diff --git a/src/app/workspace/[fileId]/page.tsx b/src/app/workspace/[fileId]/page.tsx index 97898e8..94fed09 100644 --- a/src/app/workspace/[fileId]/page.tsx +++ b/src/app/workspace/[fileId]/page.tsx @@ -4,10 +4,13 @@ import WorkspaceHeader from "../_components/WorkspaceHeader"; import Editor from "../_components/Editor"; import { useConvex } from "convex/react"; import { api } from "../../../../convex/_generated/api"; -import { FILE } from "../../dashboard/_components/FileList"; import Canvas from "../_components/Canvas"; import dynamic from 'next/dynamic'; import EditorJS, { OutputData } from "@editorjs/editorjs"; +import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs"; +import Loader from "@/components/shared/Loader"; +import { FILE } from "@/app/teams/settings/_components/FileList"; +import PrivateAlert from "@/components/shared/PrivateAlert"; // Dynamic imports for server-side libraries const jsPDFPromise = import('jspdf'); @@ -16,10 +19,14 @@ const excalidrawPromise = import('@excalidraw/excalidraw'); function Workspace({ params }: any) { const [triggerSave, setTriggerSave] = useState(false); const convex = useConvex(); - const [fileData, setFileData] = useState(); + const [fileData, setFileData] = useState(null); const [fullScreen, setFullScreen] = useState(false); const editorRef = useRef(null); const canvasRef = useRef(null); + const { user, isLoading, isAuthenticated } = useKindeBrowserClient(); + + const [isError,setError] = useState(false); + const [errorMsg,setErrorMsg] = useState(""); useEffect(() => { if (params.fileId) { @@ -27,6 +34,20 @@ function Workspace({ params }: any) { } }, [params.fileId]); + useEffect(()=>{ + if(fileData && !isLoading && fileData.private){ + if(user){ + if((fileData.readBy && !fileData.readBy.includes(user.email!)) || fileData.createdBy !== user.email){ + setError(true); + setErrorMsg("Invalid Access! Request owner to give read or write access!") + } + }else{ + setError(true); + setErrorMsg("Invalid Access! Request owner to make file public!!"); + } + } + },[isLoading,fileData]) + const getFileData = async () => { const result = await convex.query(api.files.getFileById, { _id: params.fileId, @@ -193,7 +214,9 @@ function Workspace({ params }: any) { return lines; }; - return ( + if(isLoading && fileData === null) return + + return !isError ? (
setTriggerSave(!triggerSave)} @@ -209,7 +232,7 @@ function Workspace({ params }: any) { ref={editorRef as MutableRefObject} onSaveTrigger={triggerSave} fileId={params.fileId} - fileData={fileData} + fileData={fileData!} />
@@ -217,12 +240,14 @@ function Workspace({ params }: any) { ref={canvasRef as MutableRefObject} onSaveTrigger={triggerSave} fileId={params.fileId} - fileData={fileData} + fileData={fileData!} />
- ); + ) : ( + + ) } export default Workspace; diff --git a/src/app/workspace/_components/Editor.tsx b/src/app/workspace/_components/Editor.tsx index 57f0a62..ef95338 100644 --- a/src/app/workspace/_components/Editor.tsx +++ b/src/app/workspace/_components/Editor.tsx @@ -72,7 +72,7 @@ const Editor = forwardRef((props: EditorProps, ref) => { } return () => { - if (editorInstanceRef.current) { + if (editorInstanceRef.current && editorInstanceRef.current.destroy) { editorInstanceRef.current.destroy(); editorInstanceRef.current = null; } @@ -84,7 +84,7 @@ const Editor = forwardRef((props: EditorProps, ref) => { }, [props.onSaveTrigger]); const initEditor = () => { - if (editorInstanceRef.current) { + if (editorInstanceRef.current && editorInstanceRef.current.destroy) { editorInstanceRef.current.destroy(); } diff --git a/src/components/shared/PrivateAlert.tsx b/src/components/shared/PrivateAlert.tsx new file mode 100644 index 0000000..45dadf9 --- /dev/null +++ b/src/components/shared/PrivateAlert.tsx @@ -0,0 +1,34 @@ +"use client"; +import { useState } from "react"; +import { + AlertDialog, + AlertDialogContent, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; +import { Button } from "../ui/button"; +import Link from "next/link"; + +type Props = { + errorMsg: string; +}; + +export default function PrivateAlert({ errorMsg }: Props) { + const [open, setOpen] = useState(true); + return ( + + + + + {errorMsg} + +
+ +
+
+
+ ); +} diff --git a/src/components/shared/WriteAccessModal.tsx b/src/components/shared/WriteAccessModal.tsx index ebe0194..5809d3b 100644 --- a/src/components/shared/WriteAccessModal.tsx +++ b/src/components/shared/WriteAccessModal.tsx @@ -35,8 +35,6 @@ export default function WriteAccessModal({ const [open, setOpen] = useState(false); const { user }: any = useKindeBrowserClient(); - console.log(user) - const SubmitHandler = async () => { if (file.writtenBy && file.writtenBy.includes(focusedUser.email)) { try { From 032085d3d99e164a4613d4576a908fe03561c140 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Wed, 3 Jul 2024 14:34:43 +0530 Subject: [PATCH 22/40] feat: responsiveness of user settings --- .../dashboard/settings/_components/SettingsForm.tsx | 2 +- src/app/dashboard/settings/page.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/dashboard/settings/_components/SettingsForm.tsx b/src/app/dashboard/settings/_components/SettingsForm.tsx index 389df20..d4dfdb0 100644 --- a/src/app/dashboard/settings/_components/SettingsForm.tsx +++ b/src/app/dashboard/settings/_components/SettingsForm.tsx @@ -62,7 +62,7 @@ export function SettingsForm({ savedData,image }: Props) { onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-6 flex flex-col items-center justify-center" > -
+
Email diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx index 4d3569f..58a39e7 100644 --- a/src/app/dashboard/settings/page.tsx +++ b/src/app/dashboard/settings/page.tsx @@ -36,17 +36,17 @@ export default function Page() { {user && savedData && (
- -
+ +
- + {user?.given_name?.charAt(0)} @@ -57,7 +57,7 @@ export default function Page() {

{savedData.name}

{savedData.email}

-
+
- {ContributorsData.map((data, index) => ( + {contributorsData.map((data, index) => (
{data.name}
-

{data.name}

+

{data.login}


+
+ Contributions : {data.contributions}
))} From 0b7bbb8cdf9b48bcb99b3c82071901be68e20b5b Mon Sep 17 00:00:00 2001 From: Ayushmaanagarwal1121 Date: Thu, 4 Jul 2024 12:34:55 +0530 Subject: [PATCH 27/40] Fixed Deployement Error --- src/app/contributors/page.tsx | 69 ++++-------------------- src/components/shared/Contributors.tsx | 74 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 60 deletions(-) create mode 100644 src/components/shared/Contributors.tsx diff --git a/src/app/contributors/page.tsx b/src/app/contributors/page.tsx index 0a2c69d..7ea7a39 100644 --- a/src/app/contributors/page.tsx +++ b/src/app/contributors/page.tsx @@ -1,39 +1,18 @@ -"use client" import Image from "next/image"; +import type { Metadata } from "next"; import Header from "@/components/shared/Header"; import { Creators } from "@/components/shared/Creators"; import { GlobeDemo } from "@/components/shared/GlobeCard"; import Footer from "@/components/shared/Footer"; import ScrollToTopButton from "@/components/shared/ScrollUp"; -import { useEffect, useState } from "react"; +import Contributors from "@/components/shared/Contributors"; -interface Contributor { - html_url: string; - login: string; - avatar_url: string; - contributions: number; -} - -const page: React.FC = () => { - const [contributorsData, setContributorsData] = useState([]); - async function fetchContributors(pageNumber = 1) { - - const url = `https://api.github.com/repos/subhadeeproy3902/BloxAI/contributors?page=1&per_page=100`; - const response = await fetch(url); - if (!response.ok) { - throw new Error( - `Failed to fetch contributors data. Status code: ${response.status}` - ); - } - - const contributorsData1 = await response.json(); - console.log(contributorsData1) - setContributorsData(contributorsData1) - } - useEffect(()=>{ +export const metadata: Metadata = { + title: "Blox AI | Contributors", + description: "Meet the team and Contributors of Blox AI.", +}; - fetchContributors() - },[]) +const page = () => { return ( <>
@@ -48,38 +27,8 @@ const page: React.FC = () => {
-
- -
-

- Meet Our Contributors -

-
-
- {contributorsData.map((data, index) => ( - -
- {data.login} -
-
-

{data.login}


-
- Contributions : {data.contributions} -
-
- ))} -
+ +
diff --git a/src/components/shared/Contributors.tsx b/src/components/shared/Contributors.tsx new file mode 100644 index 0000000..3e906e9 --- /dev/null +++ b/src/components/shared/Contributors.tsx @@ -0,0 +1,74 @@ +"use client" +import React from 'react' +import Image from "next/image"; +import Header from "@/components/shared/Header"; +import { Creators } from "@/components/shared/Creators"; +import { GlobeDemo } from "@/components/shared/GlobeCard"; +import Footer from "@/components/shared/Footer"; +import ScrollToTopButton from "@/components/shared/ScrollUp"; +import { useEffect, useState } from "react"; + +interface Contributor { + html_url: string; + login: string; + avatar_url: string; + contributions: number; +} +export default function Contributors(){ + const [contributorsData, setContributorsData] = useState([]); + async function fetchContributors(pageNumber = 1) { + + const url = `https://api.github.com/repos/subhadeeproy3902/BloxAI/contributors?page=1&per_page=100`; + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Failed to fetch contributors data. Status code: ${response.status}` + ); + } + + const contributorsData1 = await response.json(); + console.log(contributorsData1) + setContributorsData(contributorsData1) + } + useEffect(()=>{ + + fetchContributors() + },[]) + return ( + <> +
+ +
+

+ Meet Our Contributors +

+
+
+ {contributorsData.map((data, index) => ( + +
+ {data.login} +
+
+

{data.login}


+
+ Contributions : {data.contributions} +
+
+ ))} +
+ + + ) +} From 0f673fc10729fb4fde060c37babe0df37c6ede23 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Thu, 4 Jul 2024 18:40:02 +0530 Subject: [PATCH 28/40] feat/414 --- package.json | 2 + public/google.svg | 2 + src/app/change/page.tsx | 7 ++ src/app/signin/page.tsx | 44 ++++++++ src/app/signup/page.tsx | 44 ++++++++ src/components/shared/SigninForm.tsx | 110 ++++++++++++++++++ src/components/shared/SignupForm.tsx | 160 +++++++++++++++++++++++++++ 7 files changed, 369 insertions(+) create mode 100644 public/google.svg create mode 100644 src/app/change/page.tsx create mode 100644 src/app/signin/page.tsx create mode 100644 src/app/signup/page.tsx create mode 100644 src/components/shared/SigninForm.tsx create mode 100644 src/components/shared/SignupForm.tsx diff --git a/package.json b/package.json index af7512e..f8936c3 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,9 @@ "langchain": "^0.2.4", "lucide-react": "^0.378.0", "moment": "^2.30.1", + "mongodb": "^6.8.0", "next": "^14.2.3", + "next-auth": "^4.24.7", "next-pwa": "^5.6.0", "next-themes": "^0.3.0", "react": "^18.3.1", diff --git a/public/google.svg b/public/google.svg new file mode 100644 index 0000000..616a50d --- /dev/null +++ b/public/google.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/app/change/page.tsx b/src/app/change/page.tsx new file mode 100644 index 0000000..8542239 --- /dev/null +++ b/src/app/change/page.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +export default function Page() { + return ( +
page
+ ) +} \ No newline at end of file diff --git a/src/app/signin/page.tsx b/src/app/signin/page.tsx new file mode 100644 index 0000000..796a78c --- /dev/null +++ b/src/app/signin/page.tsx @@ -0,0 +1,44 @@ +import Image from "next/image"; +import heroImg from "@/app/assets/651593780abfac438bc371ae_Group 573.webp"; +import { SigninForm } from "@/components/shared/SigninForm"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import Link from "next/link"; + +export default function Page() { + return ( +
+
+ + logo + +
+ +
+ + + Login + + + + + +
+
+ hero +
+
+ ); +} diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx new file mode 100644 index 0000000..8f58b98 --- /dev/null +++ b/src/app/signup/page.tsx @@ -0,0 +1,44 @@ +import Image from "next/image"; +import heroImg from "@/app/assets/651593780abfac438bc371ae_Group 573.webp"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { SignupForm } from "@/components/shared/SignupForm"; +import Link from "next/link"; + +export default function Page() { + return ( +
+
+ + logo + +
+ +
+ + + Register + + + + + +
+
+ hero +
+
+ ); +} diff --git a/src/components/shared/SigninForm.tsx b/src/components/shared/SigninForm.tsx new file mode 100644 index 0000000..4ca838f --- /dev/null +++ b/src/components/shared/SigninForm.tsx @@ -0,0 +1,110 @@ +"use client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { LogInIcon } from "lucide-react"; +import Link from "next/link"; +import { Separator } from "../ui/separator"; +import Image from "next/image"; + +const FormSchema = z.object({ + email: z.string().email().min(1, { + message: "Email required!", + }), + password: z.string().min(8, { + message: "Minimum Length should be 8", + }), +}); + +type Props = {}; + +export function SigninForm({}: Props) { + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + email: "", + password: "", + }, + }); + + async function onSubmit(data: z.infer) {} + + return ( +
+ + ( + + Email + + + + + + )} + /> + ( + +
+ Password + + Forgot password?{" "} + +
+ + + + +
+ )} + /> +
+

+ +
+ +
+ + + +
+ +
+ +

+ Don't have an account?{" "} + + Sign up + +

+
+ + + ); +} diff --git a/src/components/shared/SignupForm.tsx b/src/components/shared/SignupForm.tsx new file mode 100644 index 0000000..5f8762a --- /dev/null +++ b/src/components/shared/SignupForm.tsx @@ -0,0 +1,160 @@ +"use client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { LogInIcon } from "lucide-react"; +import Link from "next/link"; +import Image from "next/image"; +import { Separator } from "../ui/separator"; + +const FormSchema = z + .object({ + firstName: z.string().min(3, { + message: "Minimum Length should be 3", + }), + lastName: z.string().min(8, { + message: "Minimum Length should be 8", + }), + email: z.string().email().min(1, { + message: "Email required!", + }), + password: z.string().min(8, { + message: "Minimum Length should be 8", + }), + confirmPwd: z.string().min(8, { + message: "Minimum Length should be 8", + }), + }) + .refine((data) => data.password === data.confirmPwd, { + message: "Passwords don't match", + path: ["confirmPwd"], + }); + +type Props = {}; + +export function SignupForm({}: Props) { + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + firstName: "", + lastName: "", + email: "", + password: "", + confirmPwd: "", + }, + }); + + async function onSubmit(data: z.infer) { + console.log(data); + } + + return ( +
+ + ( + + First Name + + + + + + )} + /> + ( + + Last Name + + + + + + )} + /> + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + ( + + Confirm Password + + + + + + )} + /> +
+

+ +
+ +
+ + + +
+ +
+ +

+ Already have an account?{" "} + + Sign in + +

+
+ + + ); +} From 68dbfd8e80601ca9927faf39fef8ae99500939ef Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Thu, 4 Jul 2024 20:11:00 +0530 Subject: [PATCH 29/40] fix-build --- src/components/shared/SigninForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shared/SigninForm.tsx b/src/components/shared/SigninForm.tsx index 4ca838f..93f482b 100644 --- a/src/components/shared/SigninForm.tsx +++ b/src/components/shared/SigninForm.tsx @@ -98,7 +98,7 @@ export function SigninForm({}: Props) {

- Don't have an account?{" "} + Don't have an account?{" "} Sign up From 79d3051e215cbf77f0f37a56373fbdc067fd5690 Mon Sep 17 00:00:00 2001 From: chitraa-jain <164042739+chitraa-cj@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:03:11 +0530 Subject: [PATCH 30/40] version page added --- src/app/version/page.tsx | 80 ++++++++++++++++++ src/app/version/pageee.tsx | 148 +++++++++++++++++++++++++++++++++ src/app/version/version.tsx | 51 ++++++++++++ src/app/version/versionData.ts | 25 ++++++ 4 files changed, 304 insertions(+) create mode 100644 src/app/version/page.tsx create mode 100644 src/app/version/pageee.tsx create mode 100644 src/app/version/version.tsx create mode 100644 src/app/version/versionData.ts diff --git a/src/app/version/page.tsx b/src/app/version/page.tsx new file mode 100644 index 0000000..f615ccd --- /dev/null +++ b/src/app/version/page.tsx @@ -0,0 +1,80 @@ +"use client"; +import React, { useEffect, useRef, useState } from 'react'; +import VersionCard from './version'; +import { versionData } from './versionData'; +import Header from '@/components/shared/Header'; +import Footer from '@/components/shared/Footer'; + +const gradientClasses = [ + 'custom-gradient-3', + 'custom-gradient-4', + 'custom-gradient-1', + 'custom-gradient-2', +]; + +const Timeline: React.FC = () => { + const [activeIndex, setActiveIndex] = useState(-1); + const cardRefs = useRef<(HTMLDivElement | null)[]>([]); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const index = Number(entry.target.getAttribute('data-index')); + if (entry.isIntersecting) { + setActiveIndex(index); + } else if (activeIndex === index) { + setActiveIndex(-1); + } + }); + }, + { threshold: 0.5 } + ); + + cardRefs.current.forEach((ref) => { + if (ref) { + observer.observe(ref); + } + }); + + return () => { + observer.disconnect(); + }; + }, [activeIndex]); + + return ( + <> +

+
+
+

What's new?

+
+
+
+
+ {versionData.map((version, index) => ( +
(cardRefs.current[index] = el)} + data-index={index} + > +
+
+ +
+
+ ))} +
+
+