@@ -364,7 +380,7 @@ export default function HomePage() {
key={item.id}
type="button"
onClick={() => router.push(`/requests/${item.id}`)}
- className="recent-card"
+ className={`recent-card is-${requestCardState(item.statusLabel).key}`}
>
{item.artwork?.poster_url ? (
{group.title}
diff --git a/frontend/app/ui/WorkspaceNavigation.tsx b/frontend/app/ui/WorkspaceNavigation.tsx
new file mode 100644
index 0000000..42b0fc8
--- /dev/null
+++ b/frontend/app/ui/WorkspaceNavigation.tsx
@@ -0,0 +1,92 @@
+'use client'
+
+import { usePathname } from 'next/navigation'
+import { useEffect, useState } from 'react'
+import { authFetch, getApiBase, getToken } from '../lib/auth'
+import BrandingLogo from './BrandingLogo'
+
+type NavigationItem = {
+ href: string
+ label: string
+ shortLabel: string
+ icon: 'dashboard' | 'media' | 'issues' | 'invites' | 'settings'
+ adminOnly?: boolean
+ match: (path: string) => boolean
+}
+
+const NAVIGATION: NavigationItem[] = [
+ { href: '/', label: 'My Requests', shortLabel: 'Requests', icon: 'dashboard', match: (path) => path === '/' || path.startsWith('/requests/') },
+ { href: '/new-requests', label: 'New Request', shortLabel: 'New', icon: 'media', match: (path) => path === '/new-requests' },
+ { href: '/portal/issues', label: 'Issues', shortLabel: 'Issues', icon: 'issues', match: (path) => path.startsWith('/portal/issues') },
+ { href: '/profile/invites', label: 'Invites', shortLabel: 'Invites', icon: 'invites', match: (path) => path.startsWith('/profile/invites') },
+ { href: '/admin', label: 'Configuration', shortLabel: 'Config', icon: 'settings', adminOnly: true, match: (path) => path.startsWith('/admin') },
+]
+
+const HIDDEN_ROUTES = ['/login', '/signup', '/forgot-password', '/reset-password', '/how-it-works']
+
+function NavigationIcon({ name }: { name: NavigationItem['icon'] }) {
+ const paths: Record
= {
+ dashboard: <>>,
+ media: <>>,
+ issues: <>>,
+ invites: <>>,
+ settings: <>>,
+ }
+ return
+}
+
+export default function WorkspaceNavigation() {
+ const pathname = usePathname()
+ const [role, setRole] = useState(null)
+ const [ready, setReady] = useState(false)
+
+ useEffect(() => {
+ const token = getToken()
+ if (!token) {
+ setReady(true)
+ return
+ }
+ authFetch(`${getApiBase()}/auth/me`)
+ .then(async (response) => {
+ if (response.ok) setRole((await response.json())?.role ?? 'user')
+ })
+ .catch(() => undefined)
+ .finally(() => setReady(true))
+ }, [])
+
+ if (!ready || !getToken() || HIDDEN_ROUTES.some((route) => pathname.startsWith(route)) || pathname.startsWith('/admin')) {
+ return null
+ }
+
+ const items = NAVIGATION.filter((item) => !item.adminOnly || role === 'admin')
+
+ return (
+ <>
+
+
+ >
+ )
+}