diff --git a/src/App.tsx b/src/App.tsx
index 5fdb92ca..e3cf5574 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -34,6 +34,10 @@ import Terms from "./pages/Terms";
import Privacy from "./pages/Privacy";
import SubmissionGuidelines from "./pages/SubmissionGuidelines";
import Admin from "./pages/Admin";
+import AdminDashboard from "./pages/AdminDashboard";
+import AdminModeration from "./pages/AdminModeration";
+import AdminReports from "./pages/AdminReports";
+import AdminUsers from "./pages/AdminUsers";
import AdminSettings from "./pages/AdminSettings";
const queryClient = new QueryClient();
@@ -71,7 +75,10 @@ function AppContent() {
} />
} />
} />
- } />
+ } />
+ } />
+ } />
+ } />
} />
} />
} />
diff --git a/src/components/admin/UserManagement.tsx b/src/components/admin/UserManagement.tsx
index d7558783..535e36a9 100644
--- a/src/components/admin/UserManagement.tsx
+++ b/src/components/admin/UserManagement.tsx
@@ -1,12 +1,10 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { ProfileManager } from '@/components/moderation/ProfileManager';
import { UserRoleManager } from '@/components/moderation/UserRoleManager';
-import { Users, Shield, UserCheck, UserX } from 'lucide-react';
+import { Shield, UserCheck } from 'lucide-react';
export function UserManagement() {
- return
-
-
+ return (
+
@@ -20,22 +18,13 @@ export function UserManagement() {
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
;
+
+ );
}
\ No newline at end of file
diff --git a/src/components/layout/AdminLayout.tsx b/src/components/layout/AdminLayout.tsx
new file mode 100644
index 00000000..bafce325
--- /dev/null
+++ b/src/components/layout/AdminLayout.tsx
@@ -0,0 +1,44 @@
+import { ReactNode } from 'react';
+import { SidebarProvider } from '@/components/ui/sidebar';
+import { AdminSidebar } from './AdminSidebar';
+import { AdminTopBar } from './AdminTopBar';
+
+interface AdminLayoutProps {
+ children: ReactNode;
+ onRefresh?: () => void;
+ refreshMode?: 'auto' | 'manual';
+ pollInterval?: number;
+ lastUpdated?: Date;
+ isRefreshing?: boolean;
+}
+
+export function AdminLayout({
+ children,
+ onRefresh,
+ refreshMode,
+ pollInterval,
+ lastUpdated,
+ isRefreshing
+}: AdminLayoutProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/layout/AdminSidebar.tsx b/src/components/layout/AdminSidebar.tsx
new file mode 100644
index 00000000..8ca44f47
--- /dev/null
+++ b/src/components/layout/AdminSidebar.tsx
@@ -0,0 +1,115 @@
+import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft } from 'lucide-react';
+import { NavLink } from 'react-router-dom';
+import { useUserRole } from '@/hooks/useUserRole';
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar,
+} from '@/components/ui/sidebar';
+
+export function AdminSidebar() {
+ const { state } = useSidebar();
+ const { permissions } = useUserRole();
+ const isSuperuser = permissions?.role_level === 'superuser';
+ const collapsed = state === 'collapsed';
+
+ const navItems = [
+ {
+ title: 'Dashboard',
+ url: '/admin',
+ icon: LayoutDashboard,
+ },
+ {
+ title: 'Moderation',
+ url: '/admin/moderation',
+ icon: FileText,
+ },
+ {
+ title: 'Reports',
+ url: '/admin/reports',
+ icon: Flag,
+ },
+ {
+ title: 'Users',
+ url: '/admin/users',
+ icon: Users,
+ },
+ ...(isSuperuser ? [{
+ title: 'Settings',
+ url: '/admin/settings',
+ icon: Settings,
+ }] : []),
+ ];
+
+ return (
+
+
+ {!collapsed && (
+
+
+ 🎢
+
+
+ ThrillWiki
+ Admin Panel
+
+
+ )}
+ {collapsed && (
+
+ 🎢
+
+ )}
+
+
+
+
+ Navigation
+
+
+ {navItems.map((item) => (
+
+
+
+ isActive
+ ? 'bg-accent text-accent-foreground font-medium'
+ : 'hover:bg-accent/50'
+ }
+ >
+
+ {!collapsed && {item.title}}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ {!collapsed && Back to ThrillWiki}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/layout/AdminTopBar.tsx b/src/components/layout/AdminTopBar.tsx
new file mode 100644
index 00000000..012aab16
--- /dev/null
+++ b/src/components/layout/AdminTopBar.tsx
@@ -0,0 +1,71 @@
+import { RefreshCw } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { ThemeToggle } from '@/components/theme/ThemeToggle';
+import { AuthButtons } from '@/components/auth/AuthButtons';
+import { NotificationCenter } from '@/components/notifications/NotificationCenter';
+import { SidebarTrigger } from '@/components/ui/sidebar';
+import { useAuth } from '@/hooks/useAuth';
+
+interface AdminTopBarProps {
+ onRefresh?: () => void;
+ refreshMode?: 'auto' | 'manual';
+ pollInterval?: number;
+ lastUpdated?: Date;
+ isRefreshing?: boolean;
+}
+
+export function AdminTopBar({
+ onRefresh,
+ refreshMode,
+ pollInterval,
+ lastUpdated,
+ isRefreshing
+}: AdminTopBarProps) {
+ const { user } = useAuth();
+
+ return (
+
+ );
+}
diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx
new file mode 100644
index 00000000..ea524237
--- /dev/null
+++ b/src/pages/AdminDashboard.tsx
@@ -0,0 +1,156 @@
+import { useRef, useEffect, useCallback, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { FileText, Flag, AlertCircle } from 'lucide-react';
+import { useUserRole } from '@/hooks/useUserRole';
+import { useAuth } from '@/hooks/useAuth';
+import { Card, CardContent } from '@/components/ui/card';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { useModerationStats } from '@/hooks/useModerationStats';
+import { useAdminSettings } from '@/hooks/useAdminSettings';
+
+export default function AdminDashboard() {
+ const { user, loading: authLoading } = useAuth();
+ const { isModerator, loading: roleLoading } = useUserRole();
+ const navigate = useNavigate();
+ const [isRefreshing, setIsRefreshing] = useState(false);
+
+ const {
+ getAdminPanelRefreshMode,
+ getAdminPanelPollInterval,
+ } = useAdminSettings();
+
+ const refreshMode = getAdminPanelRefreshMode();
+ const pollInterval = getAdminPanelPollInterval();
+
+ const { stats, refresh: refreshStats, lastUpdated } = useModerationStats({
+ enabled: !!user && !authLoading && !roleLoading && isModerator(),
+ pollingEnabled: refreshMode === 'auto',
+ pollingInterval: pollInterval,
+ });
+
+ const handleRefresh = useCallback(async () => {
+ setIsRefreshing(true);
+ await refreshStats();
+ setTimeout(() => setIsRefreshing(false), 500);
+ }, [refreshStats]);
+
+ useEffect(() => {
+ if (!authLoading && !roleLoading) {
+ if (!user) {
+ navigate('/auth');
+ return;
+ }
+
+ if (!isModerator()) {
+ navigate('/');
+ return;
+ }
+ }
+ }, [user, authLoading, roleLoading, navigate, isModerator]);
+
+ if (authLoading || roleLoading) {
+ return (
+
+
+
+
+
Loading admin panel...
+
+
+
+ );
+ }
+
+ if (!user || !isModerator()) {
+ return null;
+ }
+
+ const statCards = [
+ {
+ label: 'Pending Submissions',
+ value: stats.pendingSubmissions,
+ icon: FileText,
+ color: 'amber',
+ link: '/admin/moderation',
+ },
+ {
+ label: 'Open Reports',
+ value: stats.openReports,
+ icon: Flag,
+ color: 'red',
+ link: '/admin/reports',
+ },
+ {
+ label: 'Flagged Content',
+ value: stats.flaggedContent,
+ icon: AlertCircle,
+ color: 'orange',
+ link: '/admin/moderation',
+ },
+ ];
+
+ return (
+
+
+
+
Admin Dashboard
+
+ Overview of moderation activity and pending items
+
+
+
+
+ {statCards.map((card) => {
+ const Icon = card.icon;
+ const colorClasses = {
+ amber: {
+ card: 'hover:border-amber-500/50',
+ bg: 'bg-amber-500/10',
+ icon: 'text-amber-600 dark:text-amber-400',
+ },
+ red: {
+ card: 'hover:border-red-500/50',
+ bg: 'bg-red-500/10',
+ icon: 'text-red-600 dark:text-red-400',
+ },
+ orange: {
+ card: 'hover:border-orange-500/50',
+ bg: 'bg-orange-500/10',
+ icon: 'text-orange-600 dark:text-orange-400',
+ },
+ };
+ const colors = colorClasses[card.color as keyof typeof colorClasses];
+
+ return (
+
navigate(card.link)}
+ >
+
+
+ {card.value}
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/src/pages/AdminModeration.tsx b/src/pages/AdminModeration.tsx
new file mode 100644
index 00000000..11ee2e52
--- /dev/null
+++ b/src/pages/AdminModeration.tsx
@@ -0,0 +1,85 @@
+import { useRef, useEffect, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useUserRole } from '@/hooks/useUserRole';
+import { useAuth } from '@/hooks/useAuth';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { ModerationQueue, ModerationQueueRef } from '@/components/moderation/ModerationQueue';
+import { useAdminSettings } from '@/hooks/useAdminSettings';
+import { useModerationStats } from '@/hooks/useModerationStats';
+
+export default function AdminModeration() {
+ const { user, loading: authLoading } = useAuth();
+ const { isModerator, loading: roleLoading } = useUserRole();
+ const navigate = useNavigate();
+ const moderationQueueRef = useRef(null);
+
+ const {
+ getAdminPanelRefreshMode,
+ getAdminPanelPollInterval,
+ } = useAdminSettings();
+
+ const refreshMode = getAdminPanelRefreshMode();
+ const pollInterval = getAdminPanelPollInterval();
+
+ const { refresh: refreshStats, lastUpdated } = useModerationStats({
+ enabled: !!user && !authLoading && !roleLoading && isModerator(),
+ pollingEnabled: refreshMode === 'auto',
+ pollingInterval: pollInterval,
+ });
+
+ const handleRefresh = useCallback(() => {
+ moderationQueueRef.current?.refresh();
+ refreshStats();
+ }, [refreshStats]);
+
+ useEffect(() => {
+ if (!authLoading && !roleLoading) {
+ if (!user) {
+ navigate('/auth');
+ return;
+ }
+
+ if (!isModerator()) {
+ navigate('/');
+ return;
+ }
+ }
+ }, [user, authLoading, roleLoading, navigate, isModerator]);
+
+ if (authLoading || roleLoading) {
+ return (
+
+
+
+
+
Loading moderation queue...
+
+
+
+ );
+ }
+
+ if (!user || !isModerator()) {
+ return null;
+ }
+
+ return (
+
+
+
+
Moderation Queue
+
+ Review and manage pending content submissions
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/AdminReports.tsx b/src/pages/AdminReports.tsx
new file mode 100644
index 00000000..fac21b12
--- /dev/null
+++ b/src/pages/AdminReports.tsx
@@ -0,0 +1,85 @@
+import { useRef, useEffect, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useUserRole } from '@/hooks/useUserRole';
+import { useAuth } from '@/hooks/useAuth';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { ReportsQueue, ReportsQueueRef } from '@/components/moderation/ReportsQueue';
+import { useAdminSettings } from '@/hooks/useAdminSettings';
+import { useModerationStats } from '@/hooks/useModerationStats';
+
+export default function AdminReports() {
+ const { user, loading: authLoading } = useAuth();
+ const { isModerator, loading: roleLoading } = useUserRole();
+ const navigate = useNavigate();
+ const reportsQueueRef = useRef(null);
+
+ const {
+ getAdminPanelRefreshMode,
+ getAdminPanelPollInterval,
+ } = useAdminSettings();
+
+ const refreshMode = getAdminPanelRefreshMode();
+ const pollInterval = getAdminPanelPollInterval();
+
+ const { refresh: refreshStats, lastUpdated } = useModerationStats({
+ enabled: !!user && !authLoading && !roleLoading && isModerator(),
+ pollingEnabled: refreshMode === 'auto',
+ pollingInterval: pollInterval,
+ });
+
+ const handleRefresh = useCallback(() => {
+ reportsQueueRef.current?.refresh();
+ refreshStats();
+ }, [refreshStats]);
+
+ useEffect(() => {
+ if (!authLoading && !roleLoading) {
+ if (!user) {
+ navigate('/auth');
+ return;
+ }
+
+ if (!isModerator()) {
+ navigate('/');
+ return;
+ }
+ }
+ }, [user, authLoading, roleLoading, navigate, isModerator]);
+
+ if (authLoading || roleLoading) {
+ return (
+
+ );
+ }
+
+ if (!user || !isModerator()) {
+ return null;
+ }
+
+ return (
+
+
+
+
User Reports
+
+ Review and resolve user-submitted reports
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx
index 632bdada..0aa60a6d 100644
--- a/src/pages/AdminSettings.tsx
+++ b/src/pages/AdminSettings.tsx
@@ -7,7 +7,7 @@ import { Switch } from '@/components/ui/switch';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
-import { AdminHeader } from '@/components/layout/AdminHeader';
+import { AdminLayout } from '@/components/layout/AdminLayout';
import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole';
import { useAdminSettings } from '@/hooks/useAdminSettings';
@@ -28,52 +28,45 @@ export default function AdminSettings() {
if (roleLoading || isLoading) {
return (
- <>
-
-
+
+
- >
+
);
}
if (!user || !isSuperuser()) {
return (
- <>
-
-
-
-
Access Denied
-
You don't have permission to access admin settings.
- {error && (
-
- Error details: {error.message}
-
- )}
-
+
+
+
Access Denied
+
You don't have permission to access admin settings.
+ {error && (
+
+ Error details: {error.message}
+
+ )}
- >
+
);
}
if (!settings || settings.length === 0) {
return (
- <>
-
-
-
-
No Settings Found
-
- No admin settings have been configured yet. Please contact your system administrator.
-
- {error && (
-
- Error details: {error.message}
-
- )}
-
+
+
+
No Settings Found
+
+ No admin settings have been configured yet. Please contact your system administrator.
+
+ {error && (
+
+ Error details: {error.message}
+
+ )}
- >
+
);
}
@@ -431,13 +424,12 @@ export default function AdminSettings() {
};
return (
- <>
-
-
-
-
Admin Settings
-
- Configure system-wide settings and preferences with easy-to-use controls
+
+
+
+
Admin Settings
+
+ Configure system-wide settings and preferences
@@ -598,6 +590,6 @@ export default function AdminSettings() {
- >
+
);
}
\ No newline at end of file
diff --git a/src/pages/AdminUsers.tsx b/src/pages/AdminUsers.tsx
new file mode 100644
index 00000000..e95969b5
--- /dev/null
+++ b/src/pages/AdminUsers.tsx
@@ -0,0 +1,58 @@
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useUserRole } from '@/hooks/useUserRole';
+import { useAuth } from '@/hooks/useAuth';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { UserManagement } from '@/components/admin/UserManagement';
+
+export default function AdminUsers() {
+ const { user, loading: authLoading } = useAuth();
+ const { isModerator, loading: roleLoading } = useUserRole();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!authLoading && !roleLoading) {
+ if (!user) {
+ navigate('/auth');
+ return;
+ }
+
+ if (!isModerator()) {
+ navigate('/');
+ return;
+ }
+ }
+ }, [user, authLoading, roleLoading, navigate, isModerator]);
+
+ if (authLoading || roleLoading) {
+ return (
+
+
+
+
+
Loading user management...
+
+
+
+ );
+ }
+
+ if (!user || !isModerator()) {
+ return null;
+ }
+
+ return (
+
+
+
+
User Management
+
+ Manage user profiles, roles, and permissions
+
+
+
+
+
+
+ );
+}