From 77317287426e65db6ae6f68acea7ca899f3c569c Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Mon, 10 Nov 2025 04:46:48 +0000 Subject: [PATCH] Move Staff operations dashboard to StaffDashboard.tsx cgen-48cb0fecb11144e4a06831e7945766d8 --- client/pages/StaffDashboard.tsx | 372 ++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 client/pages/StaffDashboard.tsx diff --git a/client/pages/StaffDashboard.tsx b/client/pages/StaffDashboard.tsx new file mode 100644 index 00000000..439a4d0a --- /dev/null +++ b/client/pages/StaffDashboard.tsx @@ -0,0 +1,372 @@ +import React, { useEffect, useMemo, useState } from "react"; +import Layout from "@/components/Layout"; +import { useAuth } from "@/contexts/AuthContext"; +import { useNavigate } from "react-router-dom"; +import { aethexToast } from "@/lib/aethex-toast"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; + +function useHasStaffAccess(roles: string[]) { + return useMemo( + () => + roles.some((r) => + ["owner", "admin", "founder", "staff", "employee"].includes( + r.toLowerCase(), + ), + ), + [roles], + ); +} + +export default function StaffDashboard() { + const { user, roles, loading } = useAuth(); + const navigate = useNavigate(); + const hasAccess = useHasStaffAccess(roles); + + useEffect(() => { + if (loading) return; + if (!user) { + aethexToast.info({ + title: "Sign in required", + description: "Staff area requires authentication", + }); + navigate("/staff/login"); + return; + } + if (!hasAccess) { + aethexToast.error({ + title: "Access denied", + description: "You don't have staff permissions", + }); + navigate("/dashboard"); + } + }, [user, roles, hasAccess, loading, navigate]); + + const [activeTab, setActiveTab] = useState("overview"); + const [openReports, setOpenReports] = useState([]); + const [mentorshipAll, setMentorshipAll] = useState([]); + const [loadingData, setLoadingData] = useState(false); + const [searchQ, setSearchQ] = useState(""); + const [users, setUsers] = useState([]); + + const refresh = async () => { + setLoadingData(true); + try { + const [r1, r2] = await Promise.all([ + fetch("/api/moderation/reports?status=open&limit=100"), + fetch("/api/mentorship/requests/all?limit=50&status=pending"), + ]); + const reports = r1.ok ? await r1.json() : []; + const m = r2.ok ? await r2.json() : []; + setOpenReports(Array.isArray(reports) ? reports : []); + setMentorshipAll(Array.isArray(m) ? m : []); + } catch (e) { + /* ignore */ + } finally { + setLoadingData(false); + } + }; + + useEffect(() => { + if (user && hasAccess) refresh(); + }, [user, hasAccess]); + + const loadUsers = async () => { + try { + const params = new URLSearchParams(); + if (searchQ.trim()) params.set("q", searchQ.trim()); + params.set("limit", "25"); + const resp = await fetch(`/api/staff/users?${params.toString()}`); + const data = resp.ok ? await resp.json() : []; + setUsers(Array.isArray(data) ? data : []); + } catch { + setUsers([]); + } + }; + + const updateReportStatus = async ( + id: string, + status: "resolved" | "ignored" | "open", + ) => { + try { + const resp = await fetch( + `/api/moderation/reports/${encodeURIComponent(id)}/status`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status }), + }, + ); + if (resp.ok) { + aethexToast.success({ + title: "Updated", + description: `Report marked ${status}`, + }); + refresh(); + } + } catch {} + }; + + return ( + +
+
+ + Internal + +

Operations Command

+

+ Staff dashboards, moderation, and internal tools. +

+
+ + + + Overview + Moderation + Mentorship + Users + + + +
+ + + Community Health + + Quick pulse across posts and reports + + + +
+
+ Open reports +
+
+ {openReports.length} +
+
+
+
+ Mentorship requests +
+
+ {mentorshipAll.length} +
+
+
+
+ + + Service Status + APIs and queues + + +
+
+ Admin API +
+ OK +
+
+
+ Notifications +
+ OK +
+
+
+ + + Shortcuts + Common operational links + + + + + + + +
+
+ + + + + Moderation Queue + Flagged content and actions + + + {loadingData && ( +

Loading…

+ )} + {!loadingData && openReports.length === 0 && ( +

+ No items in queue. +

+ )} +
+ {openReports.map((r) => ( +
+
+
+
{r.reason}
+
+ {r.details} +
+
+
+ + +
+
+
+ ))} +
+
+
+
+ + + + + Mentorship Requests + + Review recent mentor/mentee activity + + + + {loadingData && ( +

Loading…

+ )} + {!loadingData && mentorshipAll.length === 0 && ( +

+ No requests to review. +

+ )} +
+ {mentorshipAll.map((req) => ( +
+
+
+
+ {req.mentee?.full_name || req.mentee?.username} →{" "} + {req.mentor?.full_name || req.mentor?.username} +
+
+ {req.message || "No message"} +
+
+
+ + {req.status} + +
+
+
+ ))} +
+ + +
+
+
+ + + + + Users + + Search, roles, and quick actions + + + +
+ setSearchQ(e.target.value)} + /> + +
+
+ {users.length === 0 ? ( +

+ No users found. +

+ ) : ( +
+ {users.map((u) => ( +
+
+
+ {u.full_name || u.username || u.id} +
+
+ {u.username} +
+
+ + {u.user_type || "unknown"} + +
+ ))} +
+ )} +
+
+
+
+
+
+
+ ); +}