From 1df0f9c59f87338114b91111f94969e3935831f7 Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:21:22 +0530 Subject: [PATCH 1/7] Add session validation and authorization checks in admin layout --- src/app/admin/layout.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index eb9547e..3ce38c0 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -4,6 +4,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import Providers from "@/components/Layout/Provider"; import { AdminNavbar } from "@/components/Admin/Navbar/navbar"; +import { useSession } from "next-auth/react"; const inter = Inter({ subsets: ["latin"] }); @@ -12,6 +13,16 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { + const { data: session } = useSession({ + required: true, + }); + if (!session) { + return
Unauthorized
; + } + + if (session.user.role !== "ADMIN") { + return
Forbidden
; + } return ( From 90e212b76d4d5875df2d3480f3867cb922410a81 Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:21:33 +0530 Subject: [PATCH 2/7] Add Payments page with searchable infinite scroll table --- src/app/admin/payment/page.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/app/admin/payment/page.tsx diff --git a/src/app/admin/payment/page.tsx b/src/app/admin/payment/page.tsx new file mode 100644 index 0000000..5f7888a --- /dev/null +++ b/src/app/admin/payment/page.tsx @@ -0,0 +1,12 @@ +import { SearchableInfiniteScrollTable } from "@/components/searchable-infinite-scroll-table"; +import React from "react"; + +export default async function Payments() { + return ( + <> +
+ +
+ + ); +} From ba76a99ea68cf4cad429b2f133c065dcc50c380f Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:21:46 +0530 Subject: [PATCH 3/7] Refactor Users page to use async/await syntax and improve code formatting --- src/app/admin/users/page.tsx | 57 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index 075768b..e0edd67 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -1,33 +1,34 @@ import UsersList from "@/components/Admin/user-list"; import prisma from "@/server/db"; +import React from "react"; export default async function Users() { - let initialUserData = await prisma.user.findMany({ - select: { - id: true, - name: true, - email: true, - role: true, - image: true, - }, - take: 10, - }); - if (initialUserData === null) { - initialUserData = [ - { - id: "1", - name: "Test name", - email: "testEmail@gmail.com", - role: "PARTICIPANT", - image: "https://i.pravatar.cc/300?img=1", - }, - ]; - } - return ( - <> -
- -
- - ); + let initialUserData = await prisma.user.findMany({ + select: { + id: true, + name: true, + email: true, + role: true, + image: true, + }, + take: 10, + }); + if (initialUserData === null) { + initialUserData = [ + { + id: "1", + name: "Test name", + email: "testEmail@gmail.com", + role: "PARTICIPANT", + image: "https://i.pravatar.cc/300?img=1", + }, + ]; + } + return ( + <> +
+ +
+ + ); } From 0253f0a9e4b8c540afa634f818d3a71c6a2bff24 Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:22:38 +0530 Subject: [PATCH 4/7] Refactor uploadthing core --- src/app/api/uploadthing/core.ts | 35 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index b0c641e..99ceede 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -1,34 +1,33 @@ -import { getServerSideSession } from "@/lib/get-server-session"; +import { getServerSession } from "next-auth"; import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; const f = createUploadthing(); const auth = async (req: Request) => { - const session = await getServerSideSession(); - if (session && session.user) { - return session.user; - } else return null; + const session = await getServerSession(); + + return session?.user; }; export const ourFileRouter = { - imageUploader: f({ image: { maxFileSize: "4MB" } }) - .middleware(async ({ req }) => { - console.log("Middleware for imageUploader", req.url); - const user = await auth(req); + imageUploader: f({ image: { maxFileSize: "4MB" } }) + .middleware(async ({ req }) => { + console.log("Middleware for imageUploader", req.url); + const user = await auth(req); - if (!user) throw new UploadThingError("Unauthorized"); + if (!user) throw new UploadThingError("Unauthorized"); - return { userId: user.id }; - }) - .onUploadComplete(async ({ metadata, file }) => { - // console.log("Upload complete for userId:", metadata.userId); + return { userId: user.id }; + }) + .onUploadComplete(async ({ metadata, file }) => { + // console.log("Upload complete for userId:", metadata.userId); - // console.log("file url", file.url); + // console.log("file url", file.url); - // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback - return { uploadedBy: metadata.userId }; - }), + // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback + return { uploadedBy: metadata.userId }; + }), } satisfies FileRouter; export type OurFileRouter = typeof ourFileRouter; From 0adb93ed32d6c7cfa413b1bbd3d4d878d89e409b Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:22:45 +0530 Subject: [PATCH 5/7] Add searchable infinite scroll table component --- .../searchable-infinite-scroll-table.tsx | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/components/searchable-infinite-scroll-table.tsx diff --git a/src/components/searchable-infinite-scroll-table.tsx b/src/components/searchable-infinite-scroll-table.tsx new file mode 100644 index 0000000..1e0731d --- /dev/null +++ b/src/components/searchable-infinite-scroll-table.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Input } from "@/components/ui/input"; +import { Loader2, Search } from "lucide-react"; + +interface TableData { + name: string; + email: string; + usn: string; + paymentId: string; + contactNumber: string; + amount: number; +} + +function generateMockData(count: number): TableData[] { + return Array.from({ length: count }, (_, i) => ({ + name: `User ${i + 1}`, + email: `user${i + 1}@example.com`, + usn: `USN${(1000 + i).toString().padStart(4, "0")}`, + paymentId: `PAY${(10000 + i).toString().padStart(5, "0")}`, + contactNumber: `+1 ${Math.floor(1000000000 + Math.random() * 9000000000)}`, + amount: Math.floor(10 + Math.random() * 990), + })); +} + +export function SearchableInfiniteScrollTable() { + const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [page, setPage] = useState(1); + const [searchTerm, setSearchTerm] = useState(""); + const loaderRef = useRef(null); + + const loadMoreData = () => { + setIsLoading(true); + setTimeout(() => { + const newData = generateMockData(20); + setData((prevData) => [...prevData, ...newData]); + setPage((prevPage) => prevPage + 1); + setIsLoading(false); + }, 1000); // Simulating API delay + }; + + useEffect(() => { + loadMoreData(); + }, []); + + useEffect(() => { + const filtered = data.filter((item) => + Object.values(item).some((value) => + value.toString().toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + setFilteredData(filtered); + }, [data, searchTerm]); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && !isLoading && searchTerm === "") { + loadMoreData(); + } + }, + { threshold: 1.0 } + ); + + if (loaderRef.current) { + observer.observe(loaderRef.current); + } + + return () => { + if (loaderRef.current) { + // eslint-disable-next-line react-hooks/exhaustive-deps + observer.unobserve(loaderRef.current); + } + observer.disconnect(); + }; + }, [isLoading, searchTerm]); + + const handleSearch = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + }; + + return ( +
+
+ + +
+ + + + Name + Email + USN + Payment ID + Contact Number + Amount + + + + {filteredData.map((item, index) => ( + + {item.name} + {item.email} + {item.usn} + {item.paymentId} + {item.contactNumber} + ${item.amount.toFixed(2)} + + ))} + +
+ {searchTerm === "" && ( +
+ {isLoading && } +
+ )} +
+ ); +} From 846d8b9f9bf30bf93d2da5edd4d4c0ca0bd45398 Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:22:54 +0530 Subject: [PATCH 6/7] Refactor Admin Navbar component and add Payments option --- src/components/Admin/Navbar/navbar.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/Admin/Navbar/navbar.tsx b/src/components/Admin/Navbar/navbar.tsx index 5b7cbce..88307e8 100644 --- a/src/components/Admin/Navbar/navbar.tsx +++ b/src/components/Admin/Navbar/navbar.tsx @@ -3,12 +3,13 @@ import React, { Dispatch, SetStateAction, useState } from "react"; import { IconType } from "react-icons"; import { FiChevronDown, FiChevronsRight, FiUser } from "react-icons/fi"; import { RiCoupon3Line } from "react-icons/ri"; +import { MdPayment } from "react-icons/md"; import { motion } from "framer-motion"; import Link from "next/link"; export const AdminNavbar = () => { return ( -
+
@@ -22,7 +23,7 @@ const Sidebar = () => { return ( { open={open} href="/admin/users" /> +
From ba965c22f9d9f668662ea2804610ad972000b1d0 Mon Sep 17 00:00:00 2001 From: vyshnav Date: Mon, 30 Sep 2024 07:23:03 +0530 Subject: [PATCH 7/7] Refactor table component and add necessary table elements --- src/components/ui/table.tsx | 120 ++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/components/ui/table.tsx diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..c0df655 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,120 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}