forked from cardosofelipe/fast-next-template
Revert Zustand persist middleware approach and restore AuthInitializer
- Remove persist middleware from authStore (causing hooks timing issues) - Restore original AuthInitializer component pattern - Keep good Phase 3 optimizations: - Theme FOUC fix (inline script) - React Query refetchOnWindowFocus disabled - Code splitting for dev/auth components - Shared form components (FormField, useFormError) - Store location in lib/stores
This commit is contained in:
@@ -63,16 +63,11 @@ function LoadingSpinner() {
|
||||
export function AuthGuard({ children, requireAdmin = false, fallback }: AuthGuardProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { isAuthenticated, isLoading: authLoading, user, _hasHydrated } = useAuthStore();
|
||||
const { isAuthenticated, isLoading: authLoading, user } = useAuthStore();
|
||||
|
||||
// Fetch user data if authenticated but user not loaded
|
||||
const { isLoading: userLoading } = useMe();
|
||||
|
||||
// Wait for store to hydrate from localStorage to prevent hook order issues
|
||||
if (!_hasHydrated) {
|
||||
return fallback ? <>{fallback}</> : <LoadingSpinner />;
|
||||
}
|
||||
|
||||
// Determine overall loading state
|
||||
const isLoading = authLoading || (isAuthenticated && !user && userLoading);
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Authentication components
|
||||
|
||||
// Auth initialization
|
||||
export { AuthInitializer } from './AuthInitializer';
|
||||
|
||||
// Route protection
|
||||
export { AuthGuard } from './AuthGuard';
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Moon, Sun, Mail, User,
|
||||
Mail, User,
|
||||
Settings, LogOut, Shield, AlertCircle, Info,
|
||||
Trash2, ArrowLeft
|
||||
Trash2
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -73,41 +72,10 @@ import { Example, ExampleGrid, ExampleSection } from './Example';
|
||||
* Component showcase
|
||||
*/
|
||||
export function ComponentShowcase() {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
const toggleTheme = () => {
|
||||
setIsDark(!isDark);
|
||||
document.documentElement.classList.toggle('dark');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto flex h-16 items-center justify-between px-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/dev">
|
||||
<Button variant="ghost" size="icon" aria-label="Back to hub">
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">Component Showcase</h1>
|
||||
<p className="text-sm text-muted-foreground">All shadcn/ui components with code</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="bg-background">
|
||||
{/* Content */}
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
<div className="space-y-12">
|
||||
|
||||
119
frontend/src/components/dev/DevLayout.tsx
Normal file
119
frontend/src/components/dev/DevLayout.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
/**
|
||||
* DevLayout Component
|
||||
* Shared layout for all /dev routes with navigation and theme toggle
|
||||
* This file is excluded from coverage as it's a development tool
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Code2, Palette, LayoutDashboard, Box, FileText, BookOpen, Home } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ThemeToggle } from '@/components/theme';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface DevLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: 'Hub',
|
||||
href: '/dev',
|
||||
icon: Home,
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
title: 'Components',
|
||||
href: '/dev/components',
|
||||
icon: Box,
|
||||
},
|
||||
{
|
||||
title: 'Forms',
|
||||
href: '/dev/forms',
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: 'Layouts',
|
||||
href: '/dev/layouts',
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: 'Spacing',
|
||||
href: '/dev/spacing',
|
||||
icon: Palette,
|
||||
},
|
||||
{
|
||||
title: 'Docs',
|
||||
href: '/dev/docs',
|
||||
icon: BookOpen,
|
||||
},
|
||||
];
|
||||
|
||||
export function DevLayout({ children }: DevLayoutProps) {
|
||||
const pathname = usePathname();
|
||||
|
||||
const isActive = (href: string, exact?: boolean) => {
|
||||
if (exact) {
|
||||
return pathname === href;
|
||||
}
|
||||
return pathname.startsWith(href);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto px-4">
|
||||
{/* Single Row: Logo + Badge + Navigation + Theme Toggle */}
|
||||
<div className="flex h-14 items-center justify-between gap-6">
|
||||
{/* Left: Logo + Badge */}
|
||||
<div className="flex items-center gap-3 shrink-0">
|
||||
<Code2 className="h-5 w-5 text-primary" />
|
||||
<h1 className="text-base font-semibold">FastNext</h1>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Dev
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Center: Navigation */}
|
||||
<nav className="flex gap-1 overflow-x-auto flex-1">
|
||||
{navItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const active = isActive(item.href, item.exact);
|
||||
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<Button
|
||||
variant={active ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
className={cn(
|
||||
'gap-2 whitespace-nowrap',
|
||||
!active && 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{item.title}
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Right: Theme Toggle */}
|
||||
<div className="shrink-0">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user