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 (
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 (
+ <>
+
+
+
+ >
+ );
+}
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 (
+ <>
+
+
+
+ >
+ );
}
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;
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"
/>
+
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 && }
+
+ )}
+
+ );
+}
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,
+}
| |