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
29
app/(app)/dashboard/page.tsx
Normal file
29
app/(app)/dashboard/page.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { cookies } from 'next/headers'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { SessionData, sessionOptions } from '@/lib/session'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ProductList } from '@/components/dashboard/product-list'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await getIronSession<SessionData>(await cookies(), sessionOptions)
|
||||
|
||||
const products = await prisma.product.findMany({
|
||||
where: { user_id: session.userId, archived: false },
|
||||
orderBy: { created_at: 'desc' },
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto w-full">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-xl font-medium text-foreground">Mijn Producten</h1>
|
||||
{!session.isDemo && (
|
||||
<Button render={<Link href="/products/new" />}>+ Nieuw product</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ProductList products={products} isDemo={session.isDemo ?? false} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
app/(app)/layout.tsx
Normal file
22
app/(app)/layout.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { redirect } from 'next/navigation'
|
||||
import { cookies } from 'next/headers'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { SessionData, sessionOptions } from '@/lib/session'
|
||||
import { NavBar } from '@/components/shared/nav-bar'
|
||||
|
||||
export default async function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
const session = await getIronSession<SessionData>(await cookies(), sessionOptions)
|
||||
|
||||
if (!session.userId) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex flex-col">
|
||||
<NavBar isDemo={session.isDemo} />
|
||||
<main className="flex-1 flex flex-col">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
40
app/(auth)/login/page.tsx
Normal file
40
app/(auth)/login/page.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import Link from 'next/link'
|
||||
import { loginAction } from '@/actions/auth'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { AuthForm } from '@/components/auth/auth-form'
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-sm space-y-6">
|
||||
|
||||
{/* Logo / titel */}
|
||||
<div className="text-center space-y-1">
|
||||
<h1 className="text-2xl font-medium text-foreground">Scrum4Me</h1>
|
||||
<p className="text-sm text-muted-foreground">Inloggen bij je account</p>
|
||||
</div>
|
||||
|
||||
{/* Formulier */}
|
||||
<div className="bg-surface-container-low rounded-xl p-6 space-y-4 border border-border">
|
||||
<AuthForm action={loginAction} submitLabel="Inloggen" />
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
Nog geen account?{' '}
|
||||
<Link href="/register" className="text-primary hover:underline font-medium">
|
||||
Registreer hier
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Demo credentials */}
|
||||
<div className="bg-info-container text-info-container-foreground rounded-xl p-4 text-sm space-y-1 border border-border">
|
||||
<p className="font-medium">Demo-account (alleen lezen)</p>
|
||||
<p>Gebruikersnaam: <span className="font-mono font-medium">demo</span></p>
|
||||
<p>Wachtwoord: <span className="font-mono font-medium">demo1234</span></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
31
app/(auth)/register/page.tsx
Normal file
31
app/(auth)/register/page.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import Link from 'next/link'
|
||||
import { registerAction } from '@/actions/auth'
|
||||
import { AuthForm } from '@/components/auth/auth-form'
|
||||
|
||||
export default function RegisterPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-sm space-y-6">
|
||||
|
||||
{/* Logo / titel */}
|
||||
<div className="text-center space-y-1">
|
||||
<h1 className="text-2xl font-medium text-foreground">Scrum4Me</h1>
|
||||
<p className="text-sm text-muted-foreground">Nieuw account aanmaken</p>
|
||||
</div>
|
||||
|
||||
{/* Formulier */}
|
||||
<div className="bg-surface-container-low rounded-xl p-6 space-y-4 border border-border">
|
||||
<AuthForm action={registerAction} submitLabel="Account aanmaken" />
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
Al een account?{' '}
|
||||
<Link href="/login" className="text-primary hover:underline font-medium">
|
||||
Inloggen
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue