Refactor useAuth hook, settings components, and docs for formatting and readability improvements

- Consolidated multi-line arguments into single lines where appropriate in `useAuth`.
- Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`).
- Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`).
- Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
This commit is contained in:
2025-11-10 11:03:45 +01:00
parent 464a6140c4
commit 96df7edf88
208 changed files with 4056 additions and 4556 deletions

View File

@@ -5,10 +5,6 @@ export const metadata: Metadata = {
title: 'Authentication',
};
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return <AuthLayoutClient>{children}</AuthLayoutClient>;
}

View File

@@ -19,18 +19,13 @@ export default function LoginPage() {
return (
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight">
Sign in to your account
</h2>
<h2 className="text-3xl font-bold tracking-tight">Sign in to your account</h2>
<p className="mt-2 text-sm text-muted-foreground">
Access your dashboard and manage your account
</p>
</div>
<LoginForm
showRegisterLink
showPasswordResetLink
/>
<LoginForm showRegisterLink showPasswordResetLink />
</div>
);
}

View File

@@ -14,7 +14,10 @@ import Link from 'next/link';
// Code-split PasswordResetConfirmForm (319 lines)
const PasswordResetConfirmForm = dynamic(
/* istanbul ignore next - Next.js dynamic import, tested via component */
() => import('@/components/auth/PasswordResetConfirmForm').then((mod) => ({ default: mod.PasswordResetConfirmForm })),
() =>
import('@/components/auth/PasswordResetConfirmForm').then((mod) => ({
default: mod.PasswordResetConfirmForm,
})),
{
loading: () => (
<div className="space-y-4">
@@ -53,15 +56,12 @@ export default function PasswordResetConfirmContent() {
return (
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight">
Invalid Reset Link
</h2>
<h2 className="text-3xl font-bold tracking-tight">Invalid Reset Link</h2>
</div>
<Alert variant="destructive">
<p className="text-sm">
This password reset link is invalid or has expired. Please request a new
password reset.
This password reset link is invalid or has expired. Please request a new password reset.
</p>
</Alert>

View File

@@ -8,16 +8,16 @@ import PasswordResetConfirmContent from './PasswordResetConfirmContent';
export default function PasswordResetConfirmPage() {
return (
<Suspense fallback={
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight">Set new password</h2>
<p className="mt-2 text-sm text-muted-foreground">
Loading...
</p>
<Suspense
fallback={
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight">Set new password</h2>
<p className="mt-2 text-sm text-muted-foreground">Loading...</p>
</div>
</div>
</div>
}>
}
>
<PasswordResetConfirmContent />
</Suspense>
);

View File

@@ -8,9 +8,10 @@ import dynamic from 'next/dynamic';
// Code-split PasswordResetRequestForm
const PasswordResetRequestForm = dynamic(
/* istanbul ignore next - Next.js dynamic import, tested via component */
() => import('@/components/auth/PasswordResetRequestForm').then((mod) => ({
default: mod.PasswordResetRequestForm
})),
() =>
import('@/components/auth/PasswordResetRequestForm').then((mod) => ({
default: mod.PasswordResetRequestForm,
})),
{
loading: () => (
<div className="space-y-4">
@@ -25,9 +26,7 @@ export default function PasswordResetPage() {
return (
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight">
Reset your password
</h2>
<h2 className="text-3xl font-bold tracking-tight">Reset your password</h2>
<p className="mt-2 text-muted-foreground">
We&apos;ll send you an email with instructions to reset your password
</p>

View File

@@ -19,9 +19,7 @@ export default function RegisterPage() {
return (
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold tracking-tight">
Create your account
</h2>
<h2 className="text-3xl font-bold tracking-tight">Create your account</h2>
<p className="mt-2 text-sm text-muted-foreground">
Get started with your free account today
</p>

View File

@@ -15,18 +15,12 @@ export const metadata: Metadata = {
},
};
export default function AuthenticatedLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function AuthenticatedLayout({ children }: { children: React.ReactNode }) {
return (
<AuthGuard>
<div className="flex min-h-screen flex-col">
<Header />
<main className="flex-1">
{children}
</main>
<main className="flex-1">{children}</main>
<Footer />
</div>
</AuthGuard>

View File

@@ -40,11 +40,7 @@ const settingsTabs = [
},
];
export default function SettingsLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Determine active tab based on pathname
@@ -54,12 +50,8 @@ export default function SettingsLayout({
<div className="container mx-auto px-4 py-8">
{/* Page Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground">
Settings
</h1>
<p className="mt-2 text-muted-foreground">
Manage your account settings and preferences
</p>
<h1 className="text-3xl font-bold text-foreground">Settings</h1>
<p className="mt-2 text-muted-foreground">Manage your account settings and preferences</p>
</div>
{/* Tabs Navigation */}
@@ -79,9 +71,7 @@ export default function SettingsLayout({
</TabsList>
{/* Tab Content */}
<div className="rounded-lg border bg-card text-card-foreground p-6">
{children}
</div>
<div className="rounded-lg border bg-card text-card-foreground p-6">{children}</div>
</Tabs>
</div>
);

View File

@@ -11,9 +11,7 @@ export default function PasswordSettingsPage() {
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold text-foreground">
Password Settings
</h2>
<h2 className="text-2xl font-semibold text-foreground">Password Settings</h2>
<p className="text-muted-foreground mt-1">
Change your password to keep your account secure
</p>

View File

@@ -4,7 +4,7 @@
*/
/* istanbul ignore next - Next.js type import for metadata */
import type { Metadata} from 'next';
import type { Metadata } from 'next';
/* istanbul ignore next - Next.js metadata, not executable code */
export const metadata: Metadata = {
@@ -14,12 +14,8 @@ export const metadata: Metadata = {
export default function PreferencesPage() {
return (
<div>
<h2 className="text-2xl font-semibold text-foreground mb-4">
Preferences
</h2>
<p className="text-muted-foreground">
Configure your preferences (Coming in Task 3.5)
</p>
<h2 className="text-2xl font-semibold text-foreground mb-4">Preferences</h2>
<p className="text-muted-foreground">Configure your preferences (Coming in Task 3.5)</p>
</div>
);
}

View File

@@ -11,12 +11,8 @@ export default function ProfileSettingsPage() {
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold text-foreground">
Profile Settings
</h2>
<p className="text-muted-foreground mt-1">
Manage your profile information
</p>
<h2 className="text-2xl font-semibold text-foreground">Profile Settings</h2>
<p className="text-muted-foreground mt-1">Manage your profile information</p>
</div>
<ProfileSettingsForm />

View File

@@ -11,9 +11,7 @@ export default function SessionsPage() {
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold text-foreground">
Active Sessions
</h2>
<h2 className="text-2xl font-semibold text-foreground">Active Sessions</h2>
<p className="text-muted-foreground mt-1">
View and manage devices signed in to your account
</p>

View File

@@ -17,11 +17,7 @@ export const metadata: Metadata = {
},
};
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return (
<AuthGuard requireAdmin>
<a

View File

@@ -27,9 +27,7 @@ export default function AdminPage() {
<div className="space-y-8">
{/* Page Header */}
<div>
<h1 className="text-3xl font-bold tracking-tight">
Admin Dashboard
</h1>
<h1 className="text-3xl font-bold tracking-tight">Admin Dashboard</h1>
<p className="mt-2 text-muted-foreground">
Manage users, organizations, and system settings
</p>
@@ -72,9 +70,7 @@ export default function AdminPage() {
<Settings className="h-5 w-5 text-primary" aria-hidden="true" />
<h3 className="font-semibold">System Settings</h3>
</div>
<p className="text-sm text-muted-foreground">
Configure system-wide settings
</p>
<p className="text-sm text-muted-foreground">Configure system-wide settings</p>
</div>
</Link>
</div>

View File

@@ -27,9 +27,7 @@ export default function AdminSettingsPage() {
</Button>
</Link>
<div>
<h1 className="text-3xl font-bold tracking-tight">
System Settings
</h1>
<h1 className="text-3xl font-bold tracking-tight">System Settings</h1>
<p className="mt-2 text-muted-foreground">
Configure system-wide settings and preferences
</p>
@@ -38,16 +36,12 @@ export default function AdminSettingsPage() {
{/* Placeholder Content */}
<div className="rounded-lg border bg-card p-12 text-center">
<h3 className="text-xl font-semibold mb-2">
System Settings Coming Soon
</h3>
<h3 className="text-xl font-semibold mb-2">System Settings Coming Soon</h3>
<p className="text-muted-foreground max-w-md mx-auto">
This page will allow you to configure system-wide settings,
preferences, and advanced options.
</p>
<p className="text-sm text-muted-foreground mt-4">
Features will include:
This page will allow you to configure system-wide settings, preferences, and advanced
options.
</p>
<p className="text-sm text-muted-foreground mt-4">Features will include:</p>
<ul className="text-sm text-muted-foreground mt-2 max-w-sm mx-auto text-left">
<li> General system configuration</li>
<li> Email and notification settings</li>

View File

@@ -28,12 +28,8 @@ export default function AdminUsersPage() {
</Button>
</Link>
<div>
<h1 className="text-3xl font-bold tracking-tight">
User Management
</h1>
<p className="mt-2 text-muted-foreground">
View, create, and manage user accounts
</p>
<h1 className="text-3xl font-bold tracking-tight">User Management</h1>
<p className="mt-2 text-muted-foreground">View, create, and manage user accounts</p>
</div>
</div>

View File

@@ -11,7 +11,9 @@ import dynamic from 'next/dynamic';
const ComponentShowcase = dynamic(
() => import('@/components/dev/ComponentShowcase').then((mod) => mod.ComponentShowcase),
{
loading: () => <div className="p-8 text-center text-muted-foreground">Loading components...</div>,
loading: () => (
<div className="p-8 text-center text-muted-foreground">Loading components...</div>
),
}
);

View File

@@ -21,9 +21,9 @@ export async function generateStaticParams() {
try {
const files = await fs.readdir(docsDir);
const mdFiles = files.filter(file => file.endsWith('.md'));
const mdFiles = files.filter((file) => file.endsWith('.md'));
return mdFiles.map(file => ({
return mdFiles.map((file) => ({
slug: [file.replace(/\.md$/, '')],
}));
} catch {
@@ -63,12 +63,7 @@ export default async function DocPage({ params }: DocPageProps) {
return (
<div className="bg-background">
{/* Breadcrumbs */}
<DevBreadcrumbs
items={[
{ label: 'Documentation', href: '/dev/docs' },
{ label: title }
]}
/>
<DevBreadcrumbs items={[{ label: 'Documentation', href: '/dev/docs' }, { label: title }]} />
<div className="container mx-auto px-4 py-12">
<div className="mx-auto max-w-4xl">

View File

@@ -5,7 +5,17 @@
*/
import Link from 'next/link';
import { BookOpen, Sparkles, Layout, Palette, Code2, FileCode, Accessibility, Lightbulb, Search } from 'lucide-react';
import {
BookOpen,
Sparkles,
Layout,
Palette,
Code2,
FileCode,
Accessibility,
Lightbulb,
Search,
} from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@@ -106,9 +116,7 @@ export default function DocsHub() {
<section className="border-b bg-gradient-to-b from-background to-muted/20 py-12">
<div className="container mx-auto px-4">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-4xl font-bold tracking-tight mb-4">
Design System Documentation
</h2>
<h2 className="text-4xl font-bold tracking-tight mb-4">Design System Documentation</h2>
<p className="text-lg text-muted-foreground mb-8">
Comprehensive guides, best practices, and references for building consistent,
accessible, and maintainable user interfaces with the FastNext design system.
@@ -170,9 +178,7 @@ export default function DocsHub() {
</div>
</CardHeader>
<CardContent>
<CardDescription className="text-base">
{doc.description}
</CardDescription>
<CardDescription className="text-base">{doc.description}</CardDescription>
</CardContent>
</Card>
</Link>
@@ -203,9 +209,7 @@ export default function DocsHub() {
</div>
</CardHeader>
<CardContent>
<CardDescription>
{doc.description}
</CardDescription>
<CardDescription>{doc.description}</CardDescription>
</CardContent>
</Card>
</Link>
@@ -243,9 +247,7 @@ export default function DocsHub() {
</div>
</CardHeader>
<CardContent>
<CardDescription className="text-base">
{doc.description}
</CardDescription>
<CardDescription className="text-base">{doc.description}</CardDescription>
</CardContent>
</Card>
</Link>
@@ -254,7 +256,6 @@ export default function DocsHub() {
</section>
</div>
</main>
</div>
);
}

View File

@@ -14,12 +14,7 @@ import { z } from 'zod';
import { CheckCircle2, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { DevBreadcrumbs } from '@/components/dev/DevBreadcrumbs';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
@@ -109,9 +104,8 @@ export default function FormsPage() {
{/* Introduction */}
<div className="max-w-3xl space-y-4">
<p className="text-muted-foreground">
Complete form implementations using react-hook-form for state management
and Zod for validation. Includes error handling, loading states, and
accessibility features.
Complete form implementations using react-hook-form for state management and Zod for
validation. Includes error handling, loading states, and accessibility features.
</p>
<div className="flex flex-wrap gap-2">
<Badge variant="outline">react-hook-form</Badge>
@@ -170,16 +164,10 @@ const { register, handleSubmit, formState: { errors } } = useForm({
placeholder="you@example.com"
{...registerLogin('email')}
aria-invalid={!!errorsLogin.email}
aria-describedby={
errorsLogin.email ? 'login-email-error' : undefined
}
aria-describedby={errorsLogin.email ? 'login-email-error' : undefined}
/>
{errorsLogin.email && (
<p
id="login-email-error"
className="text-sm text-destructive"
role="alert"
>
<p id="login-email-error" className="text-sm text-destructive" role="alert">
{errorsLogin.email.message}
</p>
)}
@@ -194,9 +182,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
placeholder="••••••••"
{...registerLogin('password')}
aria-invalid={!!errorsLogin.password}
aria-describedby={
errorsLogin.password ? 'login-password-error' : undefined
}
aria-describedby={errorsLogin.password ? 'login-password-error' : undefined}
/>
{errorsLogin.password && (
<p
@@ -277,10 +263,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
</form>`}
>
<div className="max-w-md mx-auto">
<form
onSubmit={handleSubmitContact(onSubmitContact)}
className="space-y-4"
>
<form onSubmit={handleSubmitContact(onSubmitContact)} className="space-y-4">
{/* Name */}
<div className="space-y-2">
<Label htmlFor="contact-name">Name</Label>
@@ -317,9 +300,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
{/* Category */}
<div className="space-y-2">
<Label htmlFor="contact-category">Category</Label>
<Select
onValueChange={(value) => setValueContact('category', value)}
>
<Select onValueChange={(value) => setValueContact('category', value)}>
<SelectTrigger id="contact-category">
<SelectValue placeholder="Select a category" />
</SelectTrigger>
@@ -364,9 +345,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
<Alert className="border-green-500 text-green-600 dark:border-green-400 dark:text-green-400">
<CheckCircle2 className="h-4 w-4" />
<AlertTitle>Success!</AlertTitle>
<AlertDescription>
Your message has been sent successfully.
</AlertDescription>
<AlertDescription>Your message has been sent successfully.</AlertDescription>
</Alert>
)}
</form>
@@ -384,7 +363,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
title="Error State Best Practices"
description="Use aria-invalid and aria-describedby for accessibility"
before={{
caption: "No ARIA attributes, poor accessibility",
caption: 'No ARIA attributes, poor accessibility',
content: (
<div className="space-y-2 rounded-lg border p-4">
<div className="text-sm font-medium">Email</div>
@@ -394,7 +373,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
),
}}
after={{
caption: "With ARIA, screen reader accessible",
caption: 'With ARIA, screen reader accessible',
content: (
<div className="space-y-2 rounded-lg border p-4">
<div className="text-sm font-medium">Email</div>
@@ -422,15 +401,21 @@ const { register, handleSubmit, formState: { errors } } = useForm({
<ul className="space-y-2 text-sm">
<li className="flex items-start gap-2">
<CheckCircle2 className="h-4 w-4 text-green-600 mt-0.5" />
<span>Add <code className="text-xs">aria-invalid=true</code> to invalid inputs</span>
<span>
Add <code className="text-xs">aria-invalid=true</code> to invalid inputs
</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="h-4 w-4 text-green-600 mt-0.5" />
<span>Link errors with <code className="text-xs">aria-describedby</code></span>
<span>
Link errors with <code className="text-xs">aria-describedby</code>
</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="h-4 w-4 text-green-600 mt-0.5" />
<span>Add <code className="text-xs">role=&quot;alert&quot;</code> to error messages</span>
<span>
Add <code className="text-xs">role=&quot;alert&quot;</code> to error messages
</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="h-4 w-4 text-green-600 mt-0.5" />
@@ -536,9 +521,7 @@ const { register, handleSubmit, formState: { errors } } = useForm({
<div className="space-y-2">
<div className="font-medium text-sm">Optional Field</div>
<code className="block rounded bg-muted p-2 text-xs">
z.string().optional()
</code>
<code className="block rounded bg-muted p-2 text-xs">z.string().optional()</code>
</div>
<div className="space-y-2">

View File

@@ -9,13 +9,7 @@ import Link from 'next/link';
import { Grid3x3 } from 'lucide-react';
import { DevBreadcrumbs } from '@/components/dev/DevBreadcrumbs';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Example, ExampleSection } from '@/components/dev/Example';
import { BeforeAfter } from '@/components/dev/BeforeAfter';
@@ -37,9 +31,8 @@ export default function LayoutsPage() {
{/* Introduction */}
<div className="max-w-3xl space-y-4">
<p className="text-muted-foreground">
These 5 essential layout patterns cover 80% of interface needs. Each
pattern includes live examples, before/after comparisons, and copy-paste
code.
These 5 essential layout patterns cover 80% of interface needs. Each pattern includes
live examples, before/after comparisons, and copy-paste code.
</p>
<div className="flex flex-wrap gap-2">
<Badge variant="outline">Grid vs Flex</Badge>
@@ -79,14 +72,12 @@ export default function LayoutsPage() {
<Card>
<CardHeader>
<CardTitle>Content Card</CardTitle>
<CardDescription>
Constrained to max-w-4xl for readability
</CardDescription>
<CardDescription>Constrained to max-w-4xl for readability</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Your main content goes here. The max-w-4xl constraint
ensures comfortable reading width.
Your main content goes here. The max-w-4xl constraint ensures comfortable
reading width.
</p>
</CardContent>
</Card>
@@ -99,25 +90,24 @@ export default function LayoutsPage() {
title="Common Mistake: No Width Constraint"
description="Content should not span full viewport width"
before={{
caption: "No max-width, hard to read on wide screens",
caption: 'No max-width, hard to read on wide screens',
content: (
<div className="w-full space-y-4 bg-background p-4 rounded">
<h3 className="font-semibold">Full Width Content</h3>
<p className="text-sm text-muted-foreground">
This text spans the entire width, making it hard to read on
large screens. Lines become too long.
This text spans the entire width, making it hard to read on large screens.
Lines become too long.
</p>
</div>
),
}}
after={{
caption: "Constrained with max-w for better readability",
caption: 'Constrained with max-w for better readability',
content: (
<div className="max-w-2xl mx-auto space-y-4 bg-background p-4 rounded">
<h3 className="font-semibold">Constrained Content</h3>
<p className="text-sm text-muted-foreground">
This text has a max-width, creating comfortable line lengths
for reading.
This text has a max-width, creating comfortable line lengths for reading.
</p>
</div>
),
@@ -149,14 +139,10 @@ export default function LayoutsPage() {
{[1, 2, 3, 4, 5, 6].map((i) => (
<Card key={i}>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">
Metric {i}
</CardTitle>
<CardTitle className="text-sm font-medium">Metric {i}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{(Math.random() * 1000).toFixed(0)}
</div>
<div className="text-2xl font-bold">{(Math.random() * 1000).toFixed(0)}</div>
<p className="text-xs text-muted-foreground mt-1">
+{(Math.random() * 20).toFixed(1)}% from last month
</p>
@@ -170,7 +156,7 @@ export default function LayoutsPage() {
title="Grid vs Flex for Equal Columns"
description="Use Grid for equal-width columns, not Flex"
before={{
caption: "flex with flex-1 - uneven wrapping",
caption: 'flex with flex-1 - uneven wrapping',
content: (
<div className="flex flex-wrap gap-4">
<div className="flex-1 min-w-[200px] rounded border bg-background p-4">
@@ -186,7 +172,7 @@ export default function LayoutsPage() {
),
}}
after={{
caption: "grid with grid-cols - consistent sizing",
caption: 'grid with grid-cols - consistent sizing',
content: (
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="rounded border bg-background p-4">
@@ -231,9 +217,7 @@ export default function LayoutsPage() {
<Card className="max-w-md mx-auto">
<CardHeader>
<CardTitle>Login</CardTitle>
<CardDescription>
Enter your credentials to continue
</CardDescription>
<CardDescription>Enter your credentials to continue</CardDescription>
</CardHeader>
<CardContent>
<form className="space-y-4">
@@ -288,10 +272,7 @@ export default function LayoutsPage() {
</CardHeader>
<CardContent className="space-y-2">
{['Dashboard', 'Settings', 'Profile'].map((item) => (
<div
key={item}
className="rounded-md bg-muted px-3 py-2 text-sm"
>
<div key={item} className="rounded-md bg-muted px-3 py-2 text-sm">
{item}
</div>
))}
@@ -304,14 +285,12 @@ export default function LayoutsPage() {
<Card>
<CardHeader>
<CardTitle>Main Content</CardTitle>
<CardDescription>
Fixed 240px sidebar, fluid main area
</CardDescription>
<CardDescription>Fixed 240px sidebar, fluid main area</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
The sidebar remains 240px wide while the main content area
flexes to fill remaining space.
The sidebar remains 240px wide while the main content area flexes to fill
remaining space.
</p>
</CardContent>
</Card>
@@ -344,9 +323,7 @@ export default function LayoutsPage() {
<Card className="max-w-sm w-full">
<CardHeader>
<CardTitle>Centered Card</CardTitle>
<CardDescription>
Centered vertically and horizontally
</CardDescription>
<CardDescription>Centered vertically and horizontally</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
@@ -422,9 +399,7 @@ export default function LayoutsPage() {
<CardDescription>Most common pattern</CardDescription>
</CardHeader>
<CardContent>
<code className="text-xs">
grid-cols-1 md:grid-cols-2 lg:grid-cols-3
</code>
<code className="text-xs">grid-cols-1 md:grid-cols-2 lg:grid-cols-3</code>
<p className="mt-2 text-sm text-muted-foreground">
Mobile: 1 column
<br />
@@ -441,9 +416,7 @@ export default function LayoutsPage() {
<CardDescription>For smaller cards</CardDescription>
</CardHeader>
<CardContent>
<code className="text-xs">
grid-cols-1 md:grid-cols-2 lg:grid-cols-4
</code>
<code className="text-xs">grid-cols-1 md:grid-cols-2 lg:grid-cols-4</code>
<p className="mt-2 text-sm text-muted-foreground">
Mobile: 1 column
<br />
@@ -475,9 +448,7 @@ export default function LayoutsPage() {
<CardDescription>Mobile navigation</CardDescription>
</CardHeader>
<CardContent>
<code className="text-xs">
hidden lg:block
</code>
<code className="text-xs">hidden lg:block</code>
<p className="mt-2 text-sm text-muted-foreground">
Mobile: Hidden (use menu)
<br />

View File

@@ -6,23 +6,9 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import {
Palette,
Layout,
Ruler,
FileText,
BookOpen,
ArrowRight,
Sparkles,
} from 'lucide-react';
import { Palette, Layout, Ruler, FileText, BookOpen, ArrowRight, Sparkles } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
@@ -98,13 +84,11 @@ export default function DesignSystemHub() {
<div className="space-y-4 max-w-3xl">
<div className="flex items-center gap-2">
<Sparkles className="h-8 w-8 text-primary" />
<h1 className="text-4xl font-bold tracking-tight">
Design System Hub
</h1>
<h1 className="text-4xl font-bold tracking-tight">Design System Hub</h1>
</div>
<p className="text-lg text-muted-foreground">
Interactive demonstrations, live examples, and comprehensive documentation for
the FastNext design system. Built with shadcn/ui + Tailwind CSS 4.
Interactive demonstrations, live examples, and comprehensive documentation for the
FastNext design system. Built with shadcn/ui + Tailwind CSS 4.
</p>
</div>
</div>
@@ -116,9 +100,7 @@ export default function DesignSystemHub() {
{/* Demo Pages Grid */}
<section className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Interactive Demonstrations
</h2>
<h2 className="text-2xl font-semibold tracking-tight">Interactive Demonstrations</h2>
<p className="text-sm text-muted-foreground mt-2">
Explore live examples with copy-paste code snippets
</p>
@@ -143,9 +125,7 @@ export default function DesignSystemHub() {
New
</Badge>
)}
{page.status === 'enhanced' && (
<Badge variant="secondary">Enhanced</Badge>
)}
{page.status === 'enhanced' && <Badge variant="secondary">Enhanced</Badge>}
</div>
<CardTitle className="mt-4">{page.title}</CardTitle>
<CardDescription>{page.description}</CardDescription>
@@ -190,19 +170,13 @@ export default function DesignSystemHub() {
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{documentationLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="group"
>
<Link key={link.href} href={link.href} className="group">
<Card className="h-full transition-all hover:border-primary/50 hover:bg-accent/50">
<CardHeader className="space-y-1">
<CardTitle className="text-base group-hover:text-primary transition-colors">
{link.title}
</CardTitle>
<CardDescription className="text-xs">
{link.description}
</CardDescription>
<CardDescription className="text-xs">{link.description}</CardDescription>
</CardHeader>
</Card>
</Link>
@@ -214,9 +188,7 @@ export default function DesignSystemHub() {
{/* Key Features */}
<section className="space-y-6">
<h2 className="text-2xl font-semibold tracking-tight">
Key Features
</h2>
<h2 className="text-2xl font-semibold tracking-tight">Key Features</h2>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<Card>

View File

@@ -10,13 +10,7 @@ import Link from 'next/link';
import { Ruler } from 'lucide-react';
import { DevBreadcrumbs } from '@/components/dev/DevBreadcrumbs';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
// Code-split heavy dev components
@@ -52,9 +46,9 @@ export default function SpacingPage() {
{/* Introduction */}
<div className="max-w-3xl space-y-4">
<p className="text-muted-foreground">
The Golden Rule: <strong>Parents control spacing, not children.</strong>{' '}
Use gap, space-y, and space-x utilities on the parent container. Avoid
margins on children except for exceptions.
The Golden Rule: <strong>Parents control spacing, not children.</strong> Use gap,
space-y, and space-x utilities on the parent container. Avoid margins on children
except for exceptions.
</p>
<div className="flex flex-wrap gap-2">
<Badge variant="outline">gap</Badge>
@@ -73,9 +67,7 @@ export default function SpacingPage() {
<Card>
<CardHeader>
<CardTitle>Common Spacing Values</CardTitle>
<CardDescription>
Use consistent spacing values from the scale
</CardDescription>
<CardDescription>Use consistent spacing values from the scale</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
@@ -95,10 +87,7 @@ export default function SpacingPage() {
<span className="text-sm text-muted-foreground">{item.rem}</span>
<span className="text-sm">{item.use}</span>
<div className="col-span-4">
<div
className="h-2 rounded bg-primary"
style={{ width: item.px }}
></div>
<div className="h-2 rounded bg-primary" style={{ width: item.px }}></div>
</div>
</div>
))}
@@ -158,10 +147,7 @@ export default function SpacingPage() {
<p className="text-sm font-medium mb-2">Grid (gap-6)</p>
<div className="grid grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<div
key={i}
className="rounded-lg border bg-muted p-3 text-center text-sm"
>
<div key={i} className="rounded-lg border bg-muted p-3 text-center text-sm">
Card {i}
</div>
))}
@@ -207,12 +193,8 @@ export default function SpacingPage() {
<div className="rounded-lg border bg-muted p-3 text-sm">
First item (no margin)
</div>
<div className="rounded-lg border bg-muted p-3 text-sm">
Second item (mt-4)
</div>
<div className="rounded-lg border bg-muted p-3 text-sm">
Third item (mt-4)
</div>
<div className="rounded-lg border bg-muted p-3 text-sm">Second item (mt-4)</div>
<div className="rounded-lg border bg-muted p-3 text-sm">Third item (mt-4)</div>
</div>
</div>
@@ -245,7 +227,7 @@ export default function SpacingPage() {
title="Don't Let Children Control Spacing"
description="Parent should control spacing, not children"
before={{
caption: "Children control their own spacing with mt-4",
caption: 'Children control their own spacing with mt-4',
content: (
<div className="space-y-2 rounded-lg border p-4">
<div className="rounded bg-muted p-2 text-xs">
@@ -264,14 +246,12 @@ export default function SpacingPage() {
),
}}
after={{
caption: "Parent controls spacing with space-y-4",
caption: 'Parent controls spacing with space-y-4',
content: (
<div className="space-y-4 rounded-lg border p-4">
<div className="rounded bg-muted p-2 text-xs">
<div>Child 1</div>
<code className="text-[10px] text-green-600">
parent uses space-y-4
</code>
<code className="text-[10px] text-green-600">parent uses space-y-4</code>
</div>
<div className="rounded bg-muted p-2 text-xs">
<div>Child 2</div>
@@ -290,7 +270,7 @@ export default function SpacingPage() {
title="Use Gap, Not Margin for Buttons"
description="Button groups should use gap, not margins"
before={{
caption: "Margin on children - harder to maintain",
caption: 'Margin on children - harder to maintain',
content: (
<div className="flex rounded-lg border p-4">
<Button variant="outline" size="sm">
@@ -303,7 +283,7 @@ export default function SpacingPage() {
),
}}
after={{
caption: "Gap on parent - clean and flexible",
caption: 'Gap on parent - clean and flexible',
content: (
<div className="flex gap-4 rounded-lg border p-4">
<Button variant="outline" size="sm">

View File

@@ -20,23 +20,18 @@ export default function ForbiddenPage() {
<div className="container mx-auto px-6 py-16">
<div className="flex min-h-[60vh] flex-col items-center justify-center text-center">
<div className="mb-8 rounded-full bg-destructive/10 p-6">
<ShieldAlert
className="h-16 w-16 text-destructive"
aria-hidden="true"
/>
<ShieldAlert className="h-16 w-16 text-destructive" aria-hidden="true" />
</div>
<h1 className="mb-4 text-4xl font-bold tracking-tight">
403 - Access Forbidden
</h1>
<h1 className="mb-4 text-4xl font-bold tracking-tight">403 - Access Forbidden</h1>
<p className="mb-2 text-lg text-muted-foreground max-w-md">
You don&apos;t have permission to access this resource.
</p>
<p className="mb-8 text-sm text-muted-foreground max-w-md">
This page requires administrator privileges. If you believe you should
have access, please contact your system administrator.
This page requires administrator privileges. If you believe you should have access, please
contact your system administrator.
</p>
<div className="flex gap-4">

View File

@@ -1,4 +1,4 @@
@import "tailwindcss";
@import 'tailwindcss';
/**
* FastNext Template Design System
@@ -11,38 +11,38 @@
:root {
/* Colors */
--background: oklch(1.0000 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.3211 0 0);
--card: oklch(1.0000 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.3211 0 0);
--popover: oklch(1.0000 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.3211 0 0);
--primary: oklch(0.6231 0.1880 259.8145);
--primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.9670 0.0029 264.5419);
--primary: oklch(0.6231 0.188 259.8145);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.967 0.0029 264.5419);
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
--muted: oklch(0.9846 0.0017 247.8389);
--muted-foreground: oklch(0.5510 0.0234 264.3637);
--accent: oklch(0.9514 0.0250 236.8242);
--muted-foreground: oklch(0.551 0.0234 264.3637);
--accent: oklch(0.9514 0.025 236.8242);
--accent-foreground: oklch(0.3791 0.1378 265.5222);
--destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1.0000 0 0);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.9276 0.0058 264.5313);
--input: oklch(0.9276 0.0058 264.5313);
--ring: oklch(0.6231 0.1880 259.8145);
--chart-1: oklch(0.6231 0.1880 259.8145);
--ring: oklch(0.6231 0.188 259.8145);
--chart-1: oklch(0.6231 0.188 259.8145);
--chart-2: oklch(0.5461 0.2152 262.8809);
--chart-3: oklch(0.4882 0.2172 264.3763);
--chart-4: oklch(0.4244 0.1809 265.6377);
--chart-5: oklch(0.3791 0.1378 265.5222);
--sidebar: oklch(0.9846 0.0017 247.8389);
--sidebar-foreground: oklch(0.3211 0 0);
--sidebar-primary: oklch(0.6231 0.1880 259.8145);
--sidebar-primary-foreground: oklch(1.0000 0 0);
--sidebar-accent: oklch(0.9514 0.0250 236.8242);
--sidebar-primary: oklch(0.6231 0.188 259.8145);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.9514 0.025 236.8242);
--sidebar-accent-foreground: oklch(0.3791 0.1378 265.5222);
--sidebar-border: oklch(0.9276 0.0058 264.5313);
--sidebar-ring: oklch(0.6231 0.1880 259.8145);
--sidebar-ring: oklch(0.6231 0.188 259.8145);
/* Typography - Use Geist fonts from Next.js */
--font-sans: var(--font-geist-sans), system-ui, -apple-system, sans-serif;
@@ -61,11 +61,11 @@
--shadow-color: oklch(0 0 0);
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
/* Spacing */
@@ -81,8 +81,8 @@
--card-foreground: oklch(0.9219 0 0);
--popover: oklch(0.2686 0 0);
--popover-foreground: oklch(0.9219 0 0);
--primary: oklch(0.6231 0.1880 259.8145);
--primary-foreground: oklch(1.0000 0 0);
--primary: oklch(0.6231 0.188 259.8145);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.2686 0 0);
--secondary-foreground: oklch(0.9219 0 0);
--muted: oklch(0.2393 0 0);
@@ -90,23 +90,23 @@
--accent: oklch(0.3791 0.1378 265.5222);
--accent-foreground: oklch(0.8823 0.0571 254.1284);
--destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1.0000 0 0);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.3715 0 0);
--input: oklch(0.3715 0 0);
--ring: oklch(0.6231 0.1880 259.8145);
--chart-1: oklch(0.7137 0.1434 254.6240);
--chart-2: oklch(0.6231 0.1880 259.8145);
--ring: oklch(0.6231 0.188 259.8145);
--chart-1: oklch(0.7137 0.1434 254.624);
--chart-2: oklch(0.6231 0.188 259.8145);
--chart-3: oklch(0.5461 0.2152 262.8809);
--chart-4: oklch(0.4882 0.2172 264.3763);
--chart-5: oklch(0.4244 0.1809 265.6377);
--sidebar: oklch(0.2046 0 0);
--sidebar-foreground: oklch(0.9219 0 0);
--sidebar-primary: oklch(0.6231 0.1880 259.8145);
--sidebar-primary-foreground: oklch(1.0000 0 0);
--sidebar-primary: oklch(0.6231 0.188 259.8145);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.3791 0.1378 265.5222);
--sidebar-accent-foreground: oklch(0.8823 0.0571 254.1284);
--sidebar-border: oklch(0.3715 0 0);
--sidebar-ring: oklch(0.6231 0.1880 259.8145);
--sidebar-ring: oklch(0.6231 0.188 259.8145);
}
/* Make CSS variables available to Tailwind utilities */
@@ -186,24 +186,24 @@ html.dark {
/* Cursor pointer for all clickable elements */
button,
[role="button"],
[type="button"],
[type="submit"],
[type="reset"],
[role='button'],
[type='button'],
[type='submit'],
[type='reset'],
a,
label[for],
select,
[tabindex]:not([tabindex="-1"]) {
[tabindex]:not([tabindex='-1']) {
cursor: pointer;
}
/* Exception: disabled elements should not have pointer cursor */
button:disabled,
[role="button"][aria-disabled="true"],
[type="button"]:disabled,
[type="submit"]:disabled,
[type="reset"]:disabled,
a[aria-disabled="true"],
[role='button'][aria-disabled='true'],
[type='button']:disabled,
[type='submit']:disabled,
[type='reset']:disabled,
a[aria-disabled='true'],
select:disabled {
cursor: not-allowed;
}

View File

@@ -1,27 +1,27 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Providers } from "./providers";
import { AuthProvider } from "@/lib/auth/AuthContext";
import { AuthInitializer } from "@/components/auth";
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import { Providers } from './providers';
import { AuthProvider } from '@/lib/auth/AuthContext';
import { AuthInitializer } from '@/components/auth';
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
display: "swap", // Prevent font from blocking render
variable: '--font-geist-sans',
subsets: ['latin'],
display: 'swap', // Prevent font from blocking render
preload: true,
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
display: "swap", // Prevent font from blocking render
variable: '--font-geist-mono',
subsets: ['latin'],
display: 'swap', // Prevent font from blocking render
preload: false, // Only preload primary font
});
export const metadata: Metadata = {
title: "FastNext Template",
description: "FastAPI + Next.js Template",
title: 'FastNext Template',
description: 'FastAPI + Next.js Template',
};
export default function RootLayout({
@@ -57,9 +57,7 @@ export default function RootLayout({
}}
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<AuthProvider>
<AuthInitializer />
<Providers>{children}</Providers>

View File

@@ -99,10 +99,7 @@ export default function Home() {
</footer>
{/* Shared Demo Credentials Modal */}
<DemoCredentialsModal
open={demoModalOpen}
onClose={() => setDemoModalOpen(false)}
/>
<DemoCredentialsModal open={demoModalOpen} onClose={() => setDemoModalOpen(false)} />
</div>
);
}

View File

@@ -8,8 +8,7 @@ import { ThemeProvider } from '@/components/theme';
// Set NEXT_PUBLIC_ENABLE_DEVTOOLS=true in .env.local to enable
/* istanbul ignore next - Dev-only devtools, not tested in production */
const ReactQueryDevtools =
process.env.NODE_ENV === 'development' &&
process.env.NEXT_PUBLIC_ENABLE_DEVTOOLS === 'true'
process.env.NODE_ENV === 'development' && process.env.NEXT_PUBLIC_ENABLE_DEVTOOLS === 'true'
? lazy(() =>
import('@tanstack/react-query-devtools').then((mod) => ({
default: mod.ReactQueryDevtools,