feat: ST-006-ST-008 auth pages, middleware, nav shell en dashboard
- Login/register pages met AuthForm (useActionState + useFormStatus) - Server Actions voor login, register, logout met Zod validatie - Middleware checkt session cookie zonder iron-session op Edge runtime - AppLayout met auth-check en NavBar met demo badge en actieve links - Dashboard toont productenlijst via ProductList Client Component - Fix: a-in-a hydration error opgelost door div plus useRouter te gebruiken Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
24924c9b79
commit
8017968e60
9 changed files with 375 additions and 2 deletions
79
components/auth/auth-form.tsx
Normal file
79
components/auth/auth-form.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
'use client'
|
||||
|
||||
import { useActionState } from 'react'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
type ActionResult = { error: string | Record<string, string[]> } | undefined
|
||||
|
||||
function SubmitButton({ label }: { label: string }) {
|
||||
const { pending } = useFormStatus()
|
||||
return (
|
||||
<Button type="submit" className="w-full" disabled={pending}>
|
||||
{pending ? 'Even wachten…' : label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function getErrorMessage(error: ActionResult): string | null {
|
||||
if (!error) return null
|
||||
if (typeof error.error === 'string') return error.error
|
||||
// Field errors object — flatten to first message
|
||||
const first = Object.values(error.error).flat()[0]
|
||||
return first ?? null
|
||||
}
|
||||
|
||||
interface AuthFormProps {
|
||||
action: (_prevState: unknown, formData: FormData) => Promise<ActionResult>
|
||||
submitLabel: string
|
||||
}
|
||||
|
||||
export function AuthForm({ action, submitLabel }: AuthFormProps) {
|
||||
const [state, formAction] = useActionState(action, undefined)
|
||||
const errorMessage = getErrorMessage(state)
|
||||
|
||||
return (
|
||||
<form action={formAction} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="username" className="text-sm font-medium text-foreground">
|
||||
Gebruikersnaam
|
||||
</label>
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
required
|
||||
minLength={3}
|
||||
placeholder="jouw-naam"
|
||||
className="bg-input-background border-border focus-visible:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="password" className="text-sm font-medium text-foreground">
|
||||
Wachtwoord
|
||||
</label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
minLength={8}
|
||||
placeholder="••••••••"
|
||||
className="bg-input-background border-border focus-visible:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="bg-error-container text-error-container-foreground rounded-lg px-3 py-2 text-sm border-l-4 border-error">
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SubmitButton label={submitLabel} />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue