diff --git a/apps/web/app/[locale]/(admin)/layout.tsx b/apps/web/app/[locale]/(admin)/layout.tsx index 478a832..a1e0785 100644 --- a/apps/web/app/[locale]/(admin)/layout.tsx +++ b/apps/web/app/[locale]/(admin)/layout.tsx @@ -21,7 +21,7 @@ import { cn } from '@/lib/utils'; export default function AdminLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const router = useRouter(); - const { user, logout } = useAuthStore(); + const { user, isAuthenticated, isInitialized, logout } = useAuthStore(); const [sidebarOpen, setSidebarOpen] = useState(false); const t = useTranslations(); @@ -33,12 +33,20 @@ export default function AdminLayout({ children }: { children: React.ReactNode }) ]; useEffect(() => { + // Once the auth store finished its initial cookie→profile probe: + // - no session → push to /login (don't leave a spinner forever) + // - authenticated but not ADMIN → push to regular dashboard + if (!isInitialized) return; + if (!isAuthenticated) { + router.replace(`/login?next=${encodeURIComponent(pathname)}`); + return; + } if (user && user.role !== 'ADMIN') { router.replace('/dashboard'); } - }, [user, router]); + }, [isInitialized, isAuthenticated, user, router, pathname]); - if (!user) { + if (!isInitialized || !user) { return (
{t('common.loading')}
diff --git a/apps/web/app/[locale]/(dashboard)/layout.tsx b/apps/web/app/[locale]/(dashboard)/layout.tsx index 26e5be3..65b14a9 100644 --- a/apps/web/app/[locale]/(dashboard)/layout.tsx +++ b/apps/web/app/[locale]/(dashboard)/layout.tsx @@ -22,7 +22,8 @@ import { } from 'lucide-react'; import { usePathname } from 'next/navigation'; import { useTranslations } from 'next-intl'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { useRouter } from '@/i18n/navigation'; import { NotificationBell } from '@/components/notifications/notification-bell'; import { useTheme } from '@/components/providers/theme-provider'; import { Button } from '@/components/ui/button'; @@ -44,11 +45,34 @@ interface NavGroup { export default function DashboardLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); - const { user, logout } = useAuthStore(); + const router = useRouter(); + const { user, isAuthenticated, isInitialized, logout } = useAuthStore(); const { theme, toggleTheme } = useTheme(); const t = useTranslations(); const [sidebarOpen, setSidebarOpen] = useState(false); + // Auth guard — redirect unauthenticated users to /login once the auth store + // has finished its cookie→profile probe. Without this, protected queries + // inside the dashboard fire against the API and flood the console with + // 401 ApiErrors before the user even sees the sign-in screen. + useEffect(() => { + if (isInitialized && !isAuthenticated) { + const next = encodeURIComponent(pathname); + router.replace(`/login?next=${next}`); + } + }, [isInitialized, isAuthenticated, pathname, router]); + + // While the auth store initialises, OR right after we've decided to redirect, + // render a lightweight skeleton rather than the full dashboard so no queries + // mount and fire. + if (!isInitialized || !isAuthenticated) { + return ( +
+ {t('common.loading')} +
+ ); + } + const navGroups: NavGroup[] = [ { label: t('dashboard.title'), @@ -251,7 +275,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod {user.fullName} )} - + {user && }