-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #151 from Sid-80/user-profile
feat: user profile
- Loading branch information
Showing
5 changed files
with
334 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
"use client"; | ||
import { FILE } from "../../page"; | ||
import { useState } from "react"; | ||
import { Loader2, Trash2, ChevronsUpDown } from "lucide-react"; | ||
import moment from "moment"; | ||
import Image from "next/image"; | ||
import { useRouter } from "next/navigation"; | ||
import { 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 { Id } from "../../../../../convex/_generated/dataModel"; | ||
import { api } from "../../../../../convex/_generated/api"; | ||
|
||
export interface Team { | ||
createdBy: string; | ||
teamName: string; | ||
_creationTime: number; | ||
_id: string; | ||
fileCount?:number; | ||
} | ||
|
||
export default function FileList({ | ||
fileList, | ||
teamList, | ||
}: { | ||
fileList?: FILE[]; | ||
teamList: Team[]; | ||
}) { | ||
const router = useRouter(); | ||
const [sortConfig, setSortConfig] = useState<{ | ||
key: keyof FILE; | ||
direction: string; | ||
} | null>(null); | ||
|
||
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 deleteFile = useMutation(api.files.deleteFile); | ||
const deleteFunc = async (e: any, id: string) => { | ||
e.stopPropagation(); | ||
await deleteFile({ _id: id as Id<"files"> }); | ||
window.location.reload(); | ||
}; | ||
|
||
const requestSort = (key: keyof FILE) => { | ||
let direction = "ascending"; | ||
if ( | ||
sortConfig && | ||
sortConfig.key === key && | ||
sortConfig.direction === "ascending" | ||
) { | ||
direction = "descending"; | ||
} | ||
setSortConfig({ key, direction }); | ||
}; | ||
|
||
return ( | ||
<div className="overflow-x-auto w-full"> | ||
<table className="min-w-full divide-y-2 overflow-hidden text-sm"> | ||
<thead className="ltr:text-left rtl:text-right"> | ||
<tr> | ||
<td | ||
className="whitespace-nowrap px-8 py-2 font-medium cursor-pointer" | ||
onClick={() => requestSort("fileName")} | ||
> | ||
File Name <ChevronsUpDown className="inline-block ml-2" /> | ||
</td> | ||
<td | ||
className="whitespace-nowrap px-8 py-2 font-medium cursor-pointer" | ||
onClick={() => requestSort("_creationTime")} | ||
> | ||
Created At <ChevronsUpDown className="inline-block ml-2" /> | ||
</td> | ||
<td className="whitespace-nowrap px-8 py-2 font-medium">Edited</td> | ||
<td className="whitespace-nowrap px-8 py-2 font-medium">Team</td> | ||
</tr> | ||
</thead> | ||
<tbody className="divide-y divide-stone-600"> | ||
{!fileList && ( | ||
<tr className="relative h-16"> | ||
<td className="whitespace-nowrap w-full absolute px-4 py-2 mt-5 text-center font-medium flex-center"> | ||
<Loader2 className="animate-spin mr-3" size={20} /> Loading... | ||
Please wait | ||
</td> | ||
</tr> | ||
)} | ||
{fileList && !safeFileList.length && ( | ||
<tr className="relative h-16"> | ||
<td className="whitespace-nowrap text-secondary-foreground w-full absolute px-4 py-2 mt-5 text-center font-medium"> | ||
No files found | ||
</td> | ||
</tr> | ||
)} | ||
{(sortedFiles.length > 0 ? sortedFiles : safeFileList).map( | ||
(file: FILE, index: number) => ( | ||
<tr key={index} className="odd:bg-muted/50 cursor-pointer"> | ||
<td | ||
className="whitespace-nowrap px-8 py-2 font-medium" | ||
onClick={() => router.push("/workspace/" + file._id)} | ||
> | ||
{file.fileName} | ||
</td> | ||
<td | ||
className="whitespace-nowrap px-8 py-2 text-muted-foreground" | ||
onClick={() => router.push("/workspace/" + file._id)} | ||
> | ||
{moment(file._creationTime).format("DD MMM YYYY")} | ||
</td> | ||
<td | ||
className="whitespace-nowrap px-8 py-2 text-muted-foreground" | ||
onClick={() => router.push("/workspace/" + file._id)} | ||
> | ||
{moment(file._creationTime).format("DD MMM YYYY")} | ||
</td> | ||
<td className="whitespace-nowrap px-8 py-2 text-muted-foreground"> | ||
{teamList.map((team, index) => ( | ||
<p key={index}>{team.teamName}</p> | ||
))} | ||
</td> | ||
<td className="whitespace-nowrap px-8 py-2 text-muted-foreground"> | ||
<AlertDialog> | ||
<AlertDialogTrigger> | ||
<Button variant={"destructive"} size={"icon"}> | ||
<Trash2 className="h-4 w-4" /> | ||
</Button> | ||
</AlertDialogTrigger> | ||
<AlertDialogContent> | ||
<AlertDialogHeader> | ||
<AlertDialogTitle> | ||
Are you absolutely sure? | ||
</AlertDialogTitle> | ||
<AlertDialogDescription> | ||
This action cannot be undone. This will permanently | ||
delete your file and remove your data from our | ||
servers. | ||
</AlertDialogDescription> | ||
</AlertDialogHeader> | ||
<AlertDialogFooter> | ||
<AlertDialogCancel>Cancel</AlertDialogCancel> | ||
<AlertDialogAction | ||
onClick={(e) => deleteFunc(e, file._id)} | ||
> | ||
Continue | ||
</AlertDialogAction> | ||
</AlertDialogFooter> | ||
</AlertDialogContent> | ||
</AlertDialog> | ||
</td> | ||
</tr> | ||
) | ||
)} | ||
</tbody> | ||
</table> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { | ||
Carousel, | ||
CarouselContent, | ||
CarouselItem, | ||
CarouselNext, | ||
CarouselPrevious, | ||
} from "@/components/ui/carousel"; | ||
import { Team } from "./FileList"; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardFooter, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import moment from "moment"; | ||
|
||
export default function TeamList({ teamList }: { teamList: Team[] }) { | ||
return ( | ||
<div className="flex flex-col items-center justify-center px-10 w-full"> | ||
<Carousel className="w-full"> | ||
<CarouselPrevious /> | ||
<CarouselContent> | ||
<CarouselItem className="basis-1/3"> | ||
{teamList.map((team, index) => ( | ||
<Card key={index}> | ||
<CardHeader className="">{team.teamName}</CardHeader> | ||
<CardContent> | ||
<CardDescription> | ||
<h1> | ||
Created At :{" "} | ||
{moment(team._creationTime).format("DD MMM YYYY")} | ||
</h1> | ||
<h1> | ||
Files :{" "} | ||
{team.fileCount} | ||
</h1> | ||
</CardDescription> | ||
</CardContent> | ||
</Card> | ||
))} | ||
</CarouselItem> | ||
</CarouselContent> | ||
<CarouselNext /> | ||
</Carousel> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Image from 'next/image' | ||
import React from 'react' | ||
|
||
type Props={ | ||
image:any; | ||
} | ||
|
||
export default function UserImage({image}:Props) { | ||
return ( | ||
<div className="flex items-center justify-center w-full"> | ||
<Image | ||
src={image || "https://picsum.photos/50"} | ||
alt="user" | ||
width={100} | ||
height={100} | ||
className="rounded-full" | ||
/> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
"use client"; | ||
import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs"; | ||
import { ArrowBigLeft } from "lucide-react"; | ||
import Link from "next/link"; | ||
import UserImage from "./_components/UserImage"; | ||
import FileList, { Team } from "./_components/FileList"; | ||
import { FileListContext } from "@/app/_context/FilesListContext"; | ||
import React, { useContext, useEffect, useState } from "react"; | ||
import { useConvex } from "convex/react"; | ||
import { api } from "../../../../convex/_generated/api"; | ||
import TeamList from "./_components/TeamList"; | ||
|
||
export default function Page() { | ||
const { user }: any = useKindeBrowserClient(); | ||
|
||
const convex = useConvex(); | ||
|
||
const { fileList_, setFileList_ } = useContext(FileListContext); | ||
const [fileList, setFileList] = useState<any>(); | ||
|
||
useEffect(() => { | ||
fileList_ && setFileList(fileList_); | ||
}, [fileList_]); | ||
|
||
const [teamList, setTeamList] = useState<Team[]>([]); | ||
const [teamListWithCount, setTeamListCount] = useState<Team[]>([]); | ||
|
||
useEffect(() => { | ||
const getFileData = async () => { | ||
const result = await convex.query(api.teams.getTeam, { | ||
email: user.email, | ||
}); | ||
setTeamList(result); | ||
}; | ||
if (user) { | ||
getFileData(); | ||
} | ||
}, [user, fileList]); | ||
|
||
useEffect(() => { | ||
if (teamList.length > 0 && fileList.length > 0) { | ||
const fileCounts = fileList.reduce( | ||
(acc: Record<string, number>, file: any) => { | ||
acc[file.teamId] = (acc[file.teamId] || 0) + 1; | ||
return acc; | ||
}, | ||
{} | ||
); | ||
|
||
const updatedTeams = teamList.map((team) => ({ | ||
...team, | ||
fileCount: fileCounts[team._id] || 0, | ||
})); | ||
|
||
setTeamListCount(updatedTeams); | ||
} | ||
}, [fileList, teamList]); | ||
|
||
return ( | ||
<div className="flex relative flex-col gap-5 px-20 pt-10 flex-1 items-start justify-center overflow-y-auto overflow-x-hidden"> | ||
<Link href={"/dashboard"} className=" absolute z-20 top-0 left-0 m-5"> | ||
<ArrowBigLeft className="w-10 h-10" /> | ||
</Link> | ||
<UserImage image={user?.picture} /> | ||
<div className="flex flex-col gap-2 w-full justify-center text-center"> | ||
<h1 className=" text-xl font-semibold"> | ||
{user?.given_name} {user?.family_name} | ||
</h1> | ||
<p className=" text-gray-400 text-lg">{user?.email}</p> | ||
</div> | ||
|
||
<div className="flex-1 w-full flex flex-col gap-5 py-2 items-center justify-center"> | ||
<h1 className=" px-5 text-start w-full text-xl font-semibold">Teams </h1> | ||
<TeamList teamList={teamListWithCount} /> | ||
</div> | ||
|
||
<div className="flex-1 w-full flex flex-col gap-5 py-2 items-center justify-center"> | ||
<h1 className=" px-5 text-start w-full text-xl font-semibold">Files </h1> | ||
<FileList fileList={fileList || null} teamList={teamList} /> | ||
</div> | ||
</div> | ||
); | ||
} |