feat(ui): add layout, club-switcher, and auth guard
Implements Task 18: App Layout + Club-Switcher + Auth Guard New components: - TenantContext: Manages activeClubId state with TanStack Query - QueryProvider: TanStack Query client wrapper (60s stale time) - AuthGuard: Auth + tenant redirect logic (unauthenticated → /login) - ClubSwitcher: shadcn DropdownMenu for switching clubs - SignOutButton: Simple sign out button - Protected layout: Sidebar navigation + top bar with ClubSwitcher Key features: - Fetches clubs from /api/clubs/me - Auto-loads activeClubId from localStorage - Sets X-Tenant-Id cookie on club switch - Invalidates all queries on club switch - Redirect logic: unauthenticated → /login, 0 clubs → message, 1 club → auto-select, >1 clubs + no active → /select-club TDD: - 6 AuthGuard tests (loading, unauthenticated, 0 clubs, 1 club, multiple clubs, authenticated) - 3 ClubSwitcher tests (renders current club, lists all clubs, calls setActiveClub on selection) Dependencies: - Added @tanstack/react-query All tests pass (25/25). Build succeeds.
This commit is contained in:
53
frontend/src/app/(protected)/layout.tsx
Normal file
53
frontend/src/app/(protected)/layout.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { AuthGuard } from '@/components/auth-guard';
|
||||
import { ClubSwitcher } from '@/components/club-switcher';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { LogOut } from 'lucide-react';
|
||||
import { SignOutButton } from '@/components/sign-out-button';
|
||||
|
||||
export default function ProtectedLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<AuthGuard>
|
||||
<div className="flex min-h-screen bg-gray-50">
|
||||
<aside className="hidden md:flex flex-col w-64 bg-white border-r">
|
||||
<div className="p-4 border-b">
|
||||
<h1 className="text-xl font-bold">WorkClub</h1>
|
||||
</div>
|
||||
<nav className="flex-1 p-4 space-y-2">
|
||||
<Link href="/dashboard" className="flex items-center px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100">
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link href="/tasks" className="flex items-center px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100">
|
||||
Tasks
|
||||
</Link>
|
||||
<Link href="/shifts" className="flex items-center px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100">
|
||||
Shifts
|
||||
</Link>
|
||||
<Link href="/members" className="flex items-center px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100">
|
||||
Members
|
||||
</Link>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<div className="flex-1 flex flex-col">
|
||||
<header className="bg-white border-b h-16 flex items-center justify-between px-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<ClubSwitcher />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<SignOutButton />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="flex-1 p-6 overflow-auto">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</AuthGuard>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user