From 707f632d34166de233c723f0907c992d8347383b Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 13 Mar 2026 11:39:46 -0500 Subject: [PATCH] admin board --- src/app/api/admin/users/route.ts | 62 +++++++++++++++ src/components/AdminDashboard.tsx | 127 +++++++++++++++++++++++++++++- 2 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 src/app/api/admin/users/route.ts diff --git a/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts new file mode 100644 index 0000000..ff32eab --- /dev/null +++ b/src/app/api/admin/users/route.ts @@ -0,0 +1,62 @@ +import { NextResponse } from "next/server"; +export const dynamic = "force-dynamic"; +export const runtime = "nodejs"; + +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; + +// GET /api/admin/users - List all users +export async function GET() { + const session = await getServerSession(authOptions); + + if (!session || session.user.role !== "ADMIN") { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const users = await prisma.user.findMany({ + select: { + id: true, + name: true, + email: true, + image: true, + role: true, + reports: { + orderBy: { date: "desc" }, + take: 1, + select: { date: true, status: true }, + }, + }, + orderBy: { name: "asc" }, + }); + + return NextResponse.json(users); +} + +// PATCH /api/admin/users - Update a user's role +export async function PATCH(req: Request) { + const session = await getServerSession(authOptions); + + if (!session || session.user.role !== "ADMIN") { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { userId, role } = await req.json(); + + if (!userId || !["EMPLOYEE", "ADMIN"].includes(role)) { + return NextResponse.json({ error: "Invalid request" }, { status: 400 }); + } + + // Prevent admins from demoting themselves + if (userId === session.user.id && role === "EMPLOYEE") { + return NextResponse.json({ error: "You cannot remove your own admin privileges" }, { status: 403 }); + } + + const updated = await prisma.user.update({ + where: { id: userId }, + data: { role }, + select: { id: true, name: true, email: true, role: true }, + }); + + return NextResponse.json(updated); +} diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 395acf1..b8ca5bd 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -1,16 +1,19 @@ "use client"; import { useState, useEffect } from "react"; -import { Search, ChevronDown, ChevronUp, ExternalLink, FileText, User, Calendar, Settings, ListChecks, Save } from "lucide-react"; +import { Search, ChevronDown, ChevronUp, ExternalLink, FileText, User, Users, Calendar, Settings, ListChecks, Save, ShieldCheck, ShieldOff } from "lucide-react"; export default function AdminDashboard() { const [reports, setReports] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [expandedId, setExpandedId] = useState(null); - const [tab, setTab] = useState<"REPORTS" | "SETTINGS">("REPORTS"); + const [tab, setTab] = useState<"REPORTS" | "USERS" | "SETTINGS">("REPORTS"); const [folderId, setFolderId] = useState(""); const [saving, setSaving] = useState(false); + const [users, setUsers] = useState([]); + const [usersLoading, setUsersLoading] = useState(false); + const [togglingId, setTogglingId] = useState(null); useEffect(() => { fetchReports(); @@ -45,6 +48,41 @@ export default function AdminDashboard() { } }; + const fetchUsers = async () => { + setUsersLoading(true); + try { + const res = await fetch("/api/admin/users"); + const data = await res.json(); + setUsers(data); + } catch (error) { + console.error("Failed to fetch users"); + } finally { + setUsersLoading(false); + } + }; + + const toggleRole = async (userId: string, currentRole: string) => { + setTogglingId(userId); + const newRole = currentRole === "ADMIN" ? "EMPLOYEE" : "ADMIN"; + try { + const res = await fetch("/api/admin/users", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId, role: newRole }), + }); + const data = await res.json(); + if (data.error) { + alert(data.error); + } else { + setUsers(users.map(u => u.id === userId ? { ...u, role: newRole } : u)); + } + } catch (error) { + alert("Failed to update role"); + } finally { + setTogglingId(null); + } + }; + const fetchReports = async () => { try { const res = await fetch("/api/reports"); @@ -76,7 +114,15 @@ export default function AdminDashboard() { > Reports - + + + ); + })} + {users.length === 0 && ( +

No users found.

+ )} + + )} + ) : (
-- 2.49.1