Skip to content

Commit

Permalink
Merge pull request #151 from Sid-80/user-profile
Browse files Browse the repository at this point in the history
feat: user profile
  • Loading branch information
subhadeeproy3902 authored May 29, 2024
2 parents cd79dc2 + 9e4b6e9 commit fb5dc83
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Search, Send } from "lucide-react";
import Image from "next/image";
import { toggleClose } from "../Redux/Menu/menuSlice";
import { useSelector, useDispatch } from "react-redux";
import Link from "next/link";
export interface FILE {
archive: boolean;
createdBt: string;
Expand Down Expand Up @@ -89,13 +90,15 @@ function Dashboard() {
</div>
<div className="flex gap-2 items-center mx-2">
<ThemeTogglebutton />
<Link href={`/dashboard/profile`}>
<Image
src={user?.picture || "https://picsum.photos/50"}
alt="user"
width={30}
height={30}
className="rounded-full"
/>
/>
</Link>
</div>
<Button>
<Send className="h-4 w-4" /> Invite
Expand Down
178 changes: 178 additions & 0 deletions src/app/dashboard/profile/_components/FileList.tsx
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>
);
}
49 changes: 49 additions & 0 deletions src/app/dashboard/profile/_components/TeamList.tsx
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>
);
}
20 changes: 20 additions & 0 deletions src/app/dashboard/profile/_components/UserImage.tsx
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>
)
}
83 changes: 83 additions & 0 deletions src/app/dashboard/profile/page.tsx
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>
);
}

0 comments on commit fb5dc83

Please sign in to comment.