diff --git a/apps/admin/docker-compose.yml b/apps/admin/docker-compose.yml index c11e5e7..3bf5618 100644 --- a/apps/admin/docker-compose.yml +++ b/apps/admin/docker-compose.yml @@ -3,6 +3,8 @@ services: build: context: ../.. dockerfile: apps/admin/Dockerfile.dev + ports: + - "3005:3001" environment: - VITE_SUPABASE_URL=${VITE_SUPABASE_URL} - VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY} diff --git a/apps/admin/index.html b/apps/admin/index.html index bdd9c68..a97911e 100755 --- a/apps/admin/index.html +++ b/apps/admin/index.html @@ -1,5 +1,5 @@ - + diff --git a/apps/admin/public/logo.png b/apps/admin/public/logo.png new file mode 100644 index 0000000..b9366bc Binary files /dev/null and b/apps/admin/public/logo.png differ diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index 8232aa7..c44f59b 100755 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -1,5 +1,5 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; -import { AuthProvider } from '@/contexts/AuthContext'; +import { AuthProvider, PERMISSION_KEYS } from '@/contexts/AuthContext'; import { ProtectedRoute } from '@/components/ProtectedRoute'; import { AppLayout } from '@/components/layout/AppLayout'; import { LoginPage } from '@/pages/LoginPage'; @@ -13,6 +13,8 @@ import { MatchChatPage } from '@/pages/users/MatchChatPage'; import { AuditLogsPage } from '@/pages/AuditLogsPage'; import { AdminsPage } from '@/pages/admins/AdminsPage'; import { RedemptionCodesPage } from '@/pages/RedemptionCodesPage'; +import { FeedbackPage } from '@/pages/FeedbackPage'; +import { UsageInsightsPage } from '@/pages/UsageInsightsPage'; function App() { return ( @@ -34,15 +36,36 @@ function App() { } /> {/* Content management */} - } /> - } /> - } /> + + + + } + /> + + + + } + /> + + + + } + /> - {/* User management (super_admin only) */} + {/* User management */} + } @@ -50,7 +73,7 @@ function App() { + } @@ -58,17 +81,25 @@ function App() { + } /> + + + + } + /> - {/* System (super_admin only) */} + {/* System */} + } @@ -76,7 +107,7 @@ function App() { + } @@ -84,11 +115,19 @@ function App() { + } /> + + + + } + /> {/* Catch all - redirect to dashboard */} diff --git a/apps/admin/src/assets/logo.png b/apps/admin/src/assets/logo.png new file mode 100644 index 0000000..b9366bc Binary files /dev/null and b/apps/admin/src/assets/logo.png differ diff --git a/apps/admin/src/components/ProtectedRoute.tsx b/apps/admin/src/components/ProtectedRoute.tsx index 2af4462..41226aa 100644 --- a/apps/admin/src/components/ProtectedRoute.tsx +++ b/apps/admin/src/components/ProtectedRoute.tsx @@ -1,14 +1,15 @@ import { Navigate } from 'react-router-dom'; -import { useAuth } from '@/contexts/AuthContext'; +import { useAuth, PermissionKey } from '@/contexts/AuthContext'; import { Loader2 } from 'lucide-react'; interface ProtectedRouteProps { children: React.ReactNode; requireSuperAdmin?: boolean; + requiredPermission?: PermissionKey; } -export function ProtectedRoute({ children, requireSuperAdmin = false }: ProtectedRouteProps) { - const { session, loading, isSuperAdmin } = useAuth(); +export function ProtectedRoute({ children, requireSuperAdmin = false, requiredPermission }: ProtectedRouteProps) { + const { session, loading, isSuperAdmin, hasPermission } = useAuth(); if (loading) { return ( @@ -26,5 +27,9 @@ export function ProtectedRoute({ children, requireSuperAdmin = false }: Protecte return ; } + if (requiredPermission && !hasPermission(requiredPermission)) { + return ; + } + return <>{children}; } diff --git a/apps/admin/src/components/layout/AppLayout.tsx b/apps/admin/src/components/layout/AppLayout.tsx index 25ef03e..729b43f 100644 --- a/apps/admin/src/components/layout/AppLayout.tsx +++ b/apps/admin/src/components/layout/AppLayout.tsx @@ -6,19 +6,33 @@ import { Toaster } from 'sonner'; export function AppLayout() { return (
+ {/* Subtle gradient overlay */} +
+ -
+
{/* Header */} -
+
{/* Main content */} -
- +
+
+ +
- +
); } diff --git a/apps/admin/src/components/layout/Breadcrumbs.tsx b/apps/admin/src/components/layout/Breadcrumbs.tsx index 2d2dcaf..45547c0 100644 --- a/apps/admin/src/components/layout/Breadcrumbs.tsx +++ b/apps/admin/src/components/layout/Breadcrumbs.tsx @@ -13,11 +13,17 @@ interface PackInfo { category_name: string | null; } +interface UserInfo { + id: string; + name: string | null; +} + // Generate breadcrumbs based on current path and params function useBreadcrumbs(): { breadcrumbs: BreadcrumbItem[]; loading: boolean } { const location = useLocation(); const params = useParams(); const [packInfo, setPackInfo] = useState(null); + const [userInfo, setUserInfo] = useState(null); const [loading, setLoading] = useState(false); // Fetch pack info when on questions page to get category_id @@ -51,6 +57,37 @@ function useBreadcrumbs(): { breadcrumbs: BreadcrumbItem[]; loading: boolean } { fetchPackInfo(); }, [params.packId, params.categoryId]); + // Fetch user info when on user detail or match chat page + useEffect(() => { + const fetchUserInfo = async () => { + if (params.userId) { + setLoading(true); + try { + const { data } = await supabase + .from('profiles') + .select('id, name') + .eq('id', params.userId) + .single(); + + if (data) { + setUserInfo({ + id: data.id, + name: data.name, + }); + } + } catch (error) { + console.error('Failed to fetch user info for breadcrumbs:', error); + } finally { + setLoading(false); + } + } else { + setUserInfo(null); + } + }; + + fetchUserInfo(); + }, [params.userId]); + const pathSegments = location.pathname.split('/').filter(Boolean); const breadcrumbs: BreadcrumbItem[] = []; @@ -74,6 +111,13 @@ function useBreadcrumbs(): { breadcrumbs: BreadcrumbItem[]; loading: boolean } { breadcrumbs.push({ label: 'Questions', href: currentPath }); } else if (segment === 'users') { breadcrumbs.push({ label: 'Users', href: '/users' }); + // If we have a userId, add the user's name as a breadcrumb + if (params.userId && userInfo) { + breadcrumbs.push({ + label: userInfo.name || 'User', + href: `/users/${userInfo.id}`, + }); + } } else if (segment === 'matches' && params.userId) { breadcrumbs.push({ label: 'Match Chat' }); } else if (segment === 'audit-logs') { @@ -100,26 +144,28 @@ export function Breadcrumbs() { if (breadcrumbs.length === 0 && !loading) return null; return ( -