diff --git a/src/App.tsx b/src/App.tsx
index 5fdb92ca..de92886b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -33,7 +33,10 @@ import NotFound from "./pages/NotFound";
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 +74,10 @@ function AppContent() {
} />
} />
} />
- } />
+ } />
+ } />
+ } />
+ } />
} />
} />
} />
diff --git a/src/components/layout/AdminLayout.tsx b/src/components/layout/AdminLayout.tsx
new file mode 100644
index 00000000..5fa4fa0b
--- /dev/null
+++ b/src/components/layout/AdminLayout.tsx
@@ -0,0 +1,62 @@
+import { ReactNode, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { SidebarProvider } from '@/components/ui/sidebar';
+import { AdminSidebar } from './AdminSidebar';
+import { AdminTopBar } from './AdminTopBar';
+import { useAuth } from '@/hooks/useAuth';
+import { useUserRole } from '@/hooks/useUserRole';
+
+interface AdminLayoutProps {
+ children: ReactNode;
+ onRefresh?: () => void;
+ isRefreshing?: boolean;
+}
+
+export function AdminLayout({ children, onRefresh, isRefreshing }: AdminLayoutProps) {
+ 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 admin panel...
+
+
+ );
+ }
+
+ if (!user || !isModerator()) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/components/layout/AdminSidebar.tsx b/src/components/layout/AdminSidebar.tsx
new file mode 100644
index 00000000..1f9e73c5
--- /dev/null
+++ b/src/components/layout/AdminSidebar.tsx
@@ -0,0 +1,148 @@
+import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft } from 'lucide-react';
+import { NavLink, useLocation } from 'react-router-dom';
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarFooter,
+ SidebarHeader,
+ useSidebar,
+} from '@/components/ui/sidebar';
+import { cn } from '@/lib/utils';
+import { useUserRole } from '@/hooks/useUserRole';
+
+const navigationItems = [
+ {
+ title: 'Dashboard',
+ url: '/admin',
+ icon: LayoutDashboard,
+ },
+ {
+ title: 'Moderation Queue',
+ url: '/admin/moderation',
+ icon: FileText,
+ },
+ {
+ title: 'Reports',
+ url: '/admin/reports',
+ icon: Flag,
+ },
+ {
+ title: 'User Management',
+ url: '/admin/users',
+ icon: Users,
+ },
+];
+
+export function AdminSidebar() {
+ const location = useLocation();
+ const { state } = useSidebar();
+ const { isSuperuser } = useUserRole();
+ const isCollapsed = state === 'collapsed';
+
+ const isActive = (path: string) => {
+ if (path === '/admin') {
+ return location.pathname === '/admin';
+ }
+ return location.pathname.startsWith(path);
+ };
+
+ return (
+
+
+
+
+ TW
+
+ {!isCollapsed && (
+
+ ThrillWiki
+ Admin Panel
+
+ )}
+
+
+
+
+
+ Navigation
+
+
+ {navigationItems.map((item) => (
+
+
+
+ cn(
+ 'flex items-center gap-3 rounded-md transition-colors',
+ isActive
+ ? 'bg-primary/10 text-primary font-medium'
+ : 'hover:bg-muted/50'
+ )
+ }
+ >
+
+ {!isCollapsed && {item.title}}
+
+
+
+ ))}
+
+ {isSuperuser() && (
+
+
+
+ cn(
+ 'flex items-center gap-3 rounded-md transition-colors',
+ isActive
+ ? 'bg-primary/10 text-primary font-medium'
+ : 'hover:bg-muted/50'
+ )
+ }
+ >
+
+ {!isCollapsed && Settings}
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {!isCollapsed && Back to ThrillWiki}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/layout/AdminTopBar.tsx b/src/components/layout/AdminTopBar.tsx
new file mode 100644
index 00000000..f182ce35
--- /dev/null
+++ b/src/components/layout/AdminTopBar.tsx
@@ -0,0 +1,38 @@
+import { RefreshCw } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { SidebarTrigger } from '@/components/ui/sidebar';
+import { ThemeToggle } from '@/components/theme/ThemeToggle';
+import { NotificationCenter } from '@/components/notifications/NotificationCenter';
+import { AuthButtons } from '@/components/auth/AuthButtons';
+
+interface AdminTopBarProps {
+ onRefresh?: () => void;
+ isRefreshing?: boolean;
+}
+
+export function AdminTopBar({ onRefresh, isRefreshing }: AdminTopBarProps) {
+ return (
+
+
+
+ {onRefresh && (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx
new file mode 100644
index 00000000..bd62a375
--- /dev/null
+++ b/src/pages/AdminDashboard.tsx
@@ -0,0 +1,116 @@
+import { useRef, useCallback } from 'react';
+import { RefreshCw, FileText, Flag, AlertCircle } from 'lucide-react';
+import { Card, CardContent } from '@/components/ui/card';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { useModerationStats } from '@/hooks/useModerationStats';
+import { useAdminSettings } from '@/hooks/useAdminSettings';
+import { useAuth } from '@/hooks/useAuth';
+import { useUserRole } from '@/hooks/useUserRole';
+
+export default function AdminDashboard() {
+ const { user, loading: authLoading } = useAuth();
+ const { isModerator, loading: roleLoading } = useUserRole();
+
+ // Get admin settings for polling configuration
+ const {
+ getAdminPanelRefreshMode,
+ getAdminPanelPollInterval,
+ } = useAdminSettings();
+
+ const refreshMode = getAdminPanelRefreshMode();
+ const pollInterval = getAdminPanelPollInterval();
+
+ // Use stats hook with configurable polling
+ const { stats, refresh: refreshStats, lastUpdated } = useModerationStats({
+ enabled: !!user && !authLoading && !roleLoading && isModerator(),
+ pollingEnabled: refreshMode === 'auto',
+ pollingInterval: pollInterval,
+ });
+
+ const handleRefresh = useCallback(() => {
+ refreshStats();
+ }, [refreshStats]);
+
+ return (
+
+
+ {/* Refresh status indicator */}
+
+
+ {refreshMode === 'auto' ? (
+ Auto-refresh: every {pollInterval / 1000}s
+ ) : (
+ Manual refresh
+ )}
+ {lastUpdated && (
+ • {lastUpdated.toLocaleTimeString()}
+ )}
+
+
+ {/* Stats Overview */}
+
+
Dashboard Overview
+
+
+
+
+
+
+
+
+
+
+
Pending
+
Submissions
+
+
+
+ {stats.pendingSubmissions}
+
+
+
+
+
+
+
+
+
+
+ {stats.openReports}
+
+
+
+
+
+
+
+
+
+
+ {stats.flaggedContent}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/AdminModeration.tsx b/src/pages/AdminModeration.tsx
new file mode 100644
index 00000000..68783bff
--- /dev/null
+++ b/src/pages/AdminModeration.tsx
@@ -0,0 +1,22 @@
+import { useRef, useCallback } from 'react';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { ModerationQueue, ModerationQueueRef } from '@/components/moderation/ModerationQueue';
+
+export default function AdminModeration() {
+ const moderationQueueRef = useRef(null);
+
+ const handleRefresh = useCallback(() => {
+ moderationQueueRef.current?.refresh();
+ }, []);
+
+ return (
+
+
+
+
Moderation Queue
+
+
+
+
+ );
+}
diff --git a/src/pages/AdminReports.tsx b/src/pages/AdminReports.tsx
new file mode 100644
index 00000000..5c3c3ceb
--- /dev/null
+++ b/src/pages/AdminReports.tsx
@@ -0,0 +1,22 @@
+import { useRef, useCallback } from 'react';
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { ReportsQueue, ReportsQueueRef } from '@/components/moderation/ReportsQueue';
+
+export default function AdminReports() {
+ const reportsQueueRef = useRef(null);
+
+ const handleRefresh = useCallback(() => {
+ reportsQueueRef.current?.refresh();
+ }, []);
+
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx
index 632bdada..0a9997f4 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,20 +28,18 @@ export default function AdminSettings() {
if (roleLoading || isLoading) {
return (
- <>
-
+
- >
+
);
}
if (!user || !isSuperuser()) {
return (
- <>
-
-
+
+
Access Denied
You don't have permission to access admin settings.
@@ -52,15 +50,14 @@ export default function AdminSettings() {
)}
- >
+
);
}
if (!settings || settings.length === 0) {
return (
- <>
-
-
+
+
No Settings Found
@@ -73,7 +70,7 @@ export default function AdminSettings() {
)}
- >
+
);
}
@@ -431,11 +428,10 @@ export default function AdminSettings() {
};
return (
- <>
-
-
+
+
-
Admin Settings
+
Admin Settings
Configure system-wide settings and preferences with easy-to-use controls
@@ -598,6 +594,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..30a1fd5a
--- /dev/null
+++ b/src/pages/AdminUsers.tsx
@@ -0,0 +1,15 @@
+import { AdminLayout } from '@/components/layout/AdminLayout';
+import { UserManagement } from '@/components/admin/UserManagement';
+
+export default function AdminUsers() {
+ return (
+
+
+
+
User Management
+
+
+
+
+ );
+}