Add shadcn UI foundation and update documentation

This commit is contained in:
Janpeter Visser 2026-04-18 14:39:22 +02:00
parent 7d443a004a
commit e7e151b439
28 changed files with 1333 additions and 589 deletions

View file

@ -1,9 +1,19 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { signOutAction } from "@/app/auth-actions";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { sanitizeNextPath } from "@/lib/auth/navigation";
import { getAuthState } from "@/lib/auth/session";
import { getProfileBundleForCurrentUser } from "@/lib/profile/service";
import Link from "next/link";
import { cn } from "@/lib/utils";
export const dynamic = "force-dynamic";
@ -74,9 +84,11 @@ export default async function DashboardPage({ searchParams }: DashboardPageProps
<main className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(167,201,87,0.22),_transparent_32%),linear-gradient(180deg,_#f5f4ee_0%,_#eef2e6_100%)] px-6 py-10 text-slate-900 sm:px-8">
<div className="mx-auto flex max-w-6xl flex-col gap-8">
{notice ? (
<div className="rounded-[1.5rem] border border-emerald-200 bg-emerald-50 px-5 py-4 text-sm leading-7 text-emerald-900">
{notice}
</div>
<Alert className="rounded-[1.5rem] border-emerald-200 bg-emerald-50 text-emerald-950 [&_svg]:text-emerald-700">
<AlertDescription className="leading-7 text-current">
{notice}
</AlertDescription>
</Alert>
) : null}
<header className="flex flex-col gap-5 rounded-[2rem] border border-black/10 bg-white/75 p-6 shadow-[0_18px_60px_rgba(71,85,105,0.12)] backdrop-blur sm:flex-row sm:items-start sm:justify-between sm:p-8">
@ -98,105 +110,125 @@ export default async function DashboardPage({ searchParams }: DashboardPageProps
<div className="flex flex-wrap items-center gap-3">
<Link
href="/settings"
className="inline-flex rounded-full border border-black/10 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:-translate-y-0.5 hover:text-slate-950"
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"h-11 rounded-full px-5",
)}
>
Instellingen
</Link>
<button
type="submit"
className="inline-flex rounded-full border border-emerald-900/15 bg-emerald-950 px-5 py-3 text-sm font-semibold text-emerald-50 transition hover:-translate-y-0.5 hover:bg-emerald-900"
>
<Button type="submit" size="lg" className="h-11 rounded-full px-5">
Uitloggen
</button>
</Button>
</div>
</form>
</header>
<section className="grid gap-5 md:grid-cols-3">
<article className="rounded-[1.75rem] border border-black/10 bg-white/75 p-6 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Auth
</p>
<p className="mt-3 text-lg font-semibold text-slate-900">
Cookie-based sessie actief
</p>
<p className="mt-3 text-sm leading-7 text-slate-700">
Gebruiker-ID `{authState.userId}` is server-side gevalideerd via Supabase SSR-auth.
</p>
</article>
<Card className="rounded-[1.75rem] border border-border/60 bg-card/90 py-0 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Auth
</p>
<CardTitle className="text-lg text-slate-900">Cookie-based sessie actief</CardTitle>
</CardHeader>
<CardContent className="pb-6">
<CardDescription className="text-sm leading-7 text-muted-foreground">
Gebruiker-ID `{authState.userId}` is server-side gevalideerd via Supabase SSR-auth.
</CardDescription>
</CardContent>
</Card>
<article className="rounded-[1.75rem] border border-black/10 bg-white/75 p-6 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Profiel
</p>
<p className="mt-3 text-lg font-semibold text-slate-900">
{profileTitle}
</p>
<p className="mt-3 text-sm leading-7 text-slate-700">
Taal `{profile.locale}` en timezone `{profile.timezone}` staan nu per
gebruiker opgeslagen.
</p>
</article>
<Card className="rounded-[1.75rem] border border-border/60 bg-card/90 py-0 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Profiel
</p>
<CardTitle className="text-lg text-slate-900">{profileTitle}</CardTitle>
</CardHeader>
<CardContent className="pb-6">
<CardDescription className="text-sm leading-7 text-muted-foreground">
Taal `{profile.locale}` en timezone `{profile.timezone}` staan nu per
gebruiker opgeslagen.
</CardDescription>
</CardContent>
</Card>
<article className="rounded-[1.75rem] border border-black/10 bg-white/75 p-6 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Onboarding
</p>
<p className="mt-3 text-lg font-semibold text-slate-900">
{onboardingState}
</p>
<p className="mt-3 text-sm leading-7 text-slate-700">
Nieuwe accounts starten bewust zonder afgeronde onboarding, zodat
`ST-103` straks een duidelijke eerste flow kan aansturen.
</p>
</article>
<Card className="rounded-[1.75rem] border border-border/60 bg-card/90 py-0 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Onboarding
</p>
<CardTitle className="text-lg text-slate-900">{onboardingState}</CardTitle>
</CardHeader>
<CardContent className="pb-6">
<CardDescription className="text-sm leading-7 text-muted-foreground">
Nieuwe accounts starten bewust zonder afgeronde onboarding, zodat
`ST-103` straks een duidelijke eerste flow kan aansturen.
</CardDescription>
</CardContent>
</Card>
<article className="rounded-[1.75rem] border border-black/10 bg-white/75 p-6 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Instellingen
</p>
<p className="mt-3 text-lg font-semibold text-slate-900">
Punten {formatToggleState(settings.showEnergyPoints, "zichtbaar", "verborgen")}
</p>
<p className="mt-3 text-sm leading-7 text-slate-700">
Ochtendreminder: {morningReminderState}. Reflectieprompts:{" "}
{formatToggleState(settings.reflectionReminderEnabled)}.
</p>
</article>
<Card className="rounded-[1.75rem] border border-border/60 bg-card/90 py-0 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Instellingen
</p>
<CardTitle className="text-lg text-slate-900">
Punten {formatToggleState(settings.showEnergyPoints, "zichtbaar", "verborgen")}
</CardTitle>
</CardHeader>
<CardContent className="pb-6">
<CardDescription className="text-sm leading-7 text-muted-foreground">
Ochtendreminder: {morningReminderState}. Reflectieprompts:{" "}
{formatToggleState(settings.reflectionReminderEnabled)}.
</CardDescription>
</CardContent>
</Card>
</section>
{!profile.onboardingCompleted ? (
<section className="flex flex-col gap-4 rounded-[1.75rem] border border-amber-900/15 bg-amber-50 px-6 py-5 text-sm leading-7 text-amber-950 shadow-[0_12px_40px_rgba(146,64,14,0.08)] sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="font-semibold">Je onboarding is nog niet afgerond.</p>
<p className="mt-1 max-w-2xl text-amber-900">
Je kunt de korte flow later alsnog afronden om je basisinstellingen
en eerste voorkeuren vast te leggen.
</p>
</div>
<Link
href="/onboarding"
className="inline-flex shrink-0 rounded-full bg-amber-950 px-5 py-3 text-sm font-semibold text-amber-50 transition hover:-translate-y-0.5 hover:bg-amber-900"
>
Rond onboarding af
</Link>
</section>
<Card className="rounded-[1.75rem] border border-amber-900/15 bg-amber-50 py-0 text-amber-950 shadow-[0_12px_40px_rgba(146,64,14,0.08)]">
<CardContent className="flex flex-col gap-4 px-6 py-5 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="font-semibold">Je onboarding is nog niet afgerond.</p>
<p className="mt-1 max-w-2xl text-sm leading-7 text-amber-900">
Je kunt de korte flow later alsnog afronden om je basisinstellingen
en eerste voorkeuren vast te leggen.
</p>
</div>
<Link
href="/onboarding"
className={cn(
buttonVariants({ size: "lg" }),
"h-11 shrink-0 rounded-full bg-amber-950 px-5 text-amber-50 hover:bg-amber-900",
)}
>
Rond onboarding af
</Link>
</CardContent>
</Card>
) : (
<section className="flex flex-col gap-4 rounded-[1.75rem] border border-emerald-950/10 bg-emerald-950 px-6 py-5 text-sm leading-7 text-emerald-50 shadow-[0_12px_40px_rgba(6,78,59,0.18)] sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="font-semibold">Je instellingen kun je nu ook los beheren.</p>
<p className="mt-1 max-w-2xl text-emerald-100/85">
`ST-104` staat nu klaar als aparte route, zodat je reminders,
timezone en zichtbaarheid van punten later zelfstandig kunt aanpassen.
</p>
</div>
<Link
href="/settings"
className="inline-flex shrink-0 rounded-full bg-white px-5 py-3 text-sm font-semibold text-emerald-950 transition hover:-translate-y-0.5 hover:bg-emerald-50"
>
Open instellingen
</Link>
</section>
<Card className="rounded-[1.75rem] border border-primary/10 bg-primary py-0 text-primary-foreground shadow-[0_12px_40px_rgba(22,58,43,0.18)]">
<CardContent className="flex flex-col gap-4 px-6 py-5 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="font-semibold">Je instellingen kun je nu ook los beheren.</p>
<p className="mt-1 max-w-2xl text-sm leading-7 text-primary-foreground/85">
`ST-104` staat nu klaar als aparte route, zodat je reminders,
timezone en zichtbaarheid van punten later zelfstandig kunt aanpassen.
</p>
</div>
<Link
href="/settings"
className={cn(
buttonVariants({ variant: "secondary", size: "lg" }),
"h-11 shrink-0 rounded-full px-5",
)}
>
Open instellingen
</Link>
</CardContent>
</Card>
)}
</div>
</main>

View file

@ -9,38 +9,38 @@
Palatino, Georgia, serif;
--font-body: "Inter", "Aptos", "Segoe UI", "Helvetica Neue", Arial,
sans-serif;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--background: #f5f4ee;
--foreground: #0f172a;
--card: rgb(255 255 255 / 0.84);
--card-foreground: #0f172a;
--popover: #ffffff;
--popover-foreground: #0f172a;
--primary: #163a2b;
--primary-foreground: #effaf3;
--secondary: #e5ecde;
--secondary-foreground: #163a2b;
--muted: #eef2e6;
--muted-foreground: #51606f;
--accent: #dbe7d1;
--accent-foreground: #163a2b;
--destructive: #b91c1c;
--border: rgb(15 23 42 / 0.1);
--input: rgb(15 23 42 / 0.12);
--ring: #5d8a67;
--chart-1: #163a2b;
--chart-2: #5d8a67;
--chart-3: #90a955;
--chart-4: #b7c5a4;
--chart-5: #d7dfce;
--radius: 1rem;
--sidebar: #fbfaf5;
--sidebar-foreground: #0f172a;
--sidebar-primary: #163a2b;
--sidebar-primary-foreground: #effaf3;
--sidebar-accent: #dbe7d1;
--sidebar-accent-foreground: #163a2b;
--sidebar-border: rgb(15 23 42 / 0.08);
--sidebar-ring: #5d8a67;
}
* {
@ -64,8 +64,8 @@ a {
}
@theme inline {
--font-heading: var(--font-sans);
--font-sans: var(--font-sans);
--font-heading: var(--font-display);
--font-sans: var(--font-body);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@ -107,47 +107,57 @@ a {
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--background: #111927;
--foreground: #eef6f0;
--card: rgb(17 25 39 / 0.84);
--card-foreground: #eef6f0;
--popover: #152131;
--popover-foreground: #eef6f0;
--primary: #d9f2de;
--primary-foreground: #133225;
--secondary: #243244;
--secondary-foreground: #eef6f0;
--muted: #243244;
--muted-foreground: #b1bec8;
--accent: #31485b;
--accent-foreground: #eef6f0;
--destructive: #ef4444;
--border: rgb(255 255 255 / 0.12);
--input: rgb(255 255 255 / 0.14);
--ring: #88b593;
--chart-1: #d9f2de;
--chart-2: #88b593;
--chart-3: #90a955;
--chart-4: #51606f;
--chart-5: #243244;
--sidebar: #152131;
--sidebar-foreground: #eef6f0;
--sidebar-primary: #d9f2de;
--sidebar-primary-foreground: #133225;
--sidebar-accent: #243244;
--sidebar-accent-foreground: #eef6f0;
--sidebar-border: rgb(255 255 255 / 0.12);
--sidebar-ring: #88b593;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
html {
@apply font-sans;
background: var(--background);
color: var(--foreground);
}
}
body {
@apply bg-background text-foreground;
margin: 0;
min-height: 100vh;
font-family: var(--font-body), sans-serif;
-webkit-font-smoothing: antialiased;
}
}

View file

@ -1,9 +1,5 @@
import type { Metadata } from "next";
import "./globals.css";
import { Geist } from "next/font/google";
import { cn } from "@/lib/utils";
const geist = Geist({subsets:['latin'],variable:'--font-sans'});
export const metadata: Metadata = {
title: "Inspannings Monitor",
@ -17,8 +13,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="nl" className={cn("font-sans", geist.variable)}>
<body>{children}</body>
<html lang="nl">
<body className="min-h-screen">{children}</body>
</html>
);
}

View file

@ -3,6 +3,10 @@ import { redirect } from "next/navigation";
import { AuthNotice } from "@/components/auth/auth-notice";
import { AuthPanel } from "@/components/auth/auth-panel";
import { signInAction } from "@/app/auth-actions";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { getAuthNotice } from "@/lib/auth/messages";
import { buildPathWithQuery, sanitizeNextPath } from "@/lib/auth/navigation";
import { getAuthState } from "@/lib/auth/session";
@ -53,42 +57,51 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
<AuthNotice notice={notice} />
{!authState.isConfigured ? (
<div className="rounded-2xl border border-sky-200 bg-sky-50 px-4 py-4 text-sm leading-7 text-sky-900">
Voeg eerst je Supabase-gegevens toe in `.env.local` op basis van `.env.example`.
</div>
<Alert className="rounded-[1.5rem] border-sky-200 bg-sky-50 text-sky-950 [&_svg]:text-sky-700">
<AlertDescription className="leading-7 text-current">
Voeg eerst je Supabase-gegevens toe in `.env.local` op basis van `.env.example`.
</AlertDescription>
</Alert>
) : (
<form action={signInAction} className="space-y-4">
<form action={signInAction} className="space-y-5">
<input type="hidden" name="next" value={next} />
<label className="block text-sm font-medium text-slate-800">
E-mailadres
<input
className="mt-2 w-full rounded-2xl border border-black/10 bg-stone-50 px-4 py-3 text-base outline-none transition focus:border-emerald-600 focus:bg-white"
<div className="space-y-2">
<Label htmlFor="email" className="text-slate-800">
E-mailadres
</Label>
<Input
id="email"
className="h-12 rounded-[1.25rem] bg-background/80 px-4 text-base md:text-base"
type="email"
name="email"
autoComplete="email"
required
/>
</label>
</div>
<label className="block text-sm font-medium text-slate-800">
Wachtwoord
<input
className="mt-2 w-full rounded-2xl border border-black/10 bg-stone-50 px-4 py-3 text-base outline-none transition focus:border-emerald-600 focus:bg-white"
<div className="space-y-2">
<Label htmlFor="password" className="text-slate-800">
Wachtwoord
</Label>
<Input
id="password"
className="h-12 rounded-[1.25rem] bg-background/80 px-4 text-base md:text-base"
type="password"
name="password"
autoComplete="current-password"
minLength={8}
required
/>
</label>
</div>
<button
<Button
type="submit"
className="inline-flex w-full items-center justify-center rounded-2xl bg-emerald-950 px-5 py-3 text-sm font-semibold text-emerald-50 transition hover:-translate-y-0.5 hover:bg-emerald-900"
size="lg"
className="w-full rounded-[1.25rem]"
>
Inloggen
</button>
</Button>
</form>
)}
</AuthPanel>

View file

@ -1,13 +1,20 @@
import Link from "next/link";
import { signOutAction } from "@/app/auth-actions";
import { AuthNotice } from "@/components/auth/auth-notice";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { getAuthNotice } from "@/lib/auth/messages";
import { getAuthState } from "@/lib/auth/session";
import { cn } from "@/lib/utils";
export const dynamic = "force-dynamic";
const headerActionClassName =
"shrink-0 whitespace-nowrap rounded-full border border-black/10 bg-white/70 px-4 py-2 text-sm font-medium text-slate-900 shadow-sm transition hover:-translate-y-0.5";
const loopSteps = [
{
title: "Check-in",
@ -68,30 +75,36 @@ export default async function Home({ searchParams }: HomePageProps) {
<>
<Link
href="/dashboard"
className={headerActionClassName}
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"h-11 shrink-0 whitespace-nowrap rounded-full px-5",
)}
>
Naar dashboard
</Link>
<form action={signOutAction}>
<button
type="submit"
className={headerActionClassName}
>
<Button type="submit" variant="outline" size="lg" className="h-11 shrink-0 whitespace-nowrap rounded-full px-5">
Uitloggen
</button>
</Button>
</form>
</>
) : (
<>
<Link
href="/login"
className={headerActionClassName}
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"h-11 shrink-0 whitespace-nowrap rounded-full px-5",
)}
>
Inloggen
</Link>
<Link
href="/sign-up"
className={headerActionClassName}
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"h-11 shrink-0 whitespace-nowrap rounded-full px-5",
)}
>
Account aanmaken
</Link>
@ -105,91 +118,97 @@ export default async function Home({ searchParams }: HomePageProps) {
</div>
</header>
{notice ? (
<div className="mb-6 rounded-[1.5rem] border border-emerald-200 bg-emerald-50 px-5 py-4 text-sm leading-7 text-emerald-900">
{notice.text}
</div>
) : null}
<AuthNotice notice={notice} />
<section className="grid gap-6 lg:grid-cols-[1.35fr_0.95fr]">
<article className="rounded-[2rem] border border-black/10 bg-white/70 p-6 shadow-[0_18px_60px_rgba(71,85,105,0.12)] backdrop-blur sm:p-8">
<p className="mb-4 max-w-2xl text-lg leading-8 text-slate-700">
<Card className="rounded-[2rem] border border-border/60 bg-card/90 py-0 shadow-[0_18px_60px_rgba(71,85,105,0.12)] backdrop-blur">
<CardContent className="p-6 sm:p-8">
<p className="mb-4 max-w-2xl text-lg leading-8 text-slate-700">
De projectbasis staat nu, inclusief de eerste auth-laag via Supabase.
Release 1 blijft bewust smal: publieke landing, aparte login/signup
routes en een eerste protected dashboard als basis voor de volgende stories.
</p>
<div className="grid gap-4 md:grid-cols-3">
{loopSteps.map((step, index) => (
<section
key={step.title}
className="rounded-[1.5rem] border border-black/8 bg-stone-50 p-5"
>
<p className="mb-3 text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">
Stap {index + 1}
</p>
<h2 className="mb-2 font-[family-name:var(--font-display)] text-2xl">
{step.title}
</h2>
<p className="text-sm leading-7 text-slate-700">{step.copy}</p>
</section>
))}
</div>
</article>
</p>
<div className="grid gap-4 md:grid-cols-3">
{loopSteps.map((step, index) => (
<Card
key={step.title}
className="rounded-[1.5rem] border border-border/50 bg-background/80 py-0 shadow-none"
>
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-muted-foreground">
Stap {index + 1}
</p>
<CardTitle className="font-[family-name:var(--font-display)] text-2xl">
{step.title}
</CardTitle>
</CardHeader>
<CardContent className="pb-5">
<CardDescription className="text-sm leading-7 text-muted-foreground">
{step.copy}
</CardDescription>
</CardContent>
</Card>
))}
</div>
</CardContent>
</Card>
<aside className="rounded-[2rem] border border-emerald-950/10 bg-emerald-950 px-6 py-7 text-emerald-50 shadow-[0_18px_60px_rgba(6,78,59,0.18)] sm:px-8">
<p className="mb-4 text-xs font-semibold uppercase tracking-[0.24em] text-emerald-200/80">
Release 1 focus
</p>
<ul className="space-y-3">
<Card className="rounded-[2rem] border border-primary/10 bg-primary py-0 text-primary-foreground shadow-[0_18px_60px_rgba(22,58,43,0.18)]">
<CardHeader className="px-6 pt-7 sm:px-8">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-primary-foreground/75">
Release 1 focus
</p>
</CardHeader>
<CardContent className="space-y-3 px-6 pb-7 sm:px-8">
{releaseFocus.map((item) => (
<li
<Card
key={item}
className="rounded-2xl border border-white/10 bg-white/8 px-4 py-3 text-sm leading-7"
className="rounded-[1.5rem] border border-white/10 bg-white/8 py-0 text-primary-foreground shadow-none"
>
{item}
</li>
<CardContent className="px-4 py-3 text-sm leading-7">{item}</CardContent>
</Card>
))}
</ul>
{authState.isConfigured ? (
<p className="mt-5 text-sm leading-7 text-emerald-100/80">
Auth is ingericht met e-mail, wachtwoord en verplichte e-mailverificatie.
</p>
) : (
<p className="mt-5 text-sm leading-7 text-emerald-100/80">
Voeg `.env.local` toe om login, signup en protected routes lokaal te activeren.
</p>
)}
</aside>
{authState.isConfigured ? (
<p className="pt-2 text-sm leading-7 text-primary-foreground/80">
Auth is ingericht met e-mail, wachtwoord en verplichte e-mailverificatie.
</p>
) : (
<p className="pt-2 text-sm leading-7 text-primary-foreground/80">
Voeg `.env.local` toe om login, signup en protected routes lokaal te activeren.
</p>
)}
</CardContent>
</Card>
</section>
<section className="mt-8 grid gap-5 rounded-[2rem] border border-black/10 bg-white/60 p-6 shadow-[0_10px_45px_rgba(71,85,105,0.08)] backdrop-blur sm:grid-cols-2 lg:grid-cols-4">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Volgende story
</p>
<p className="mt-2 font-semibold text-slate-900">
ST-201 Ochtendcheck-in
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Doelgroep
</p>
<p className="mt-2 font-semibold text-slate-900">Volwassen individuele gebruikers</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Positionering
</p>
<p className="mt-2 font-semibold text-slate-900">Wellness / self-management</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Status
</p>
<p className="mt-2 font-semibold text-slate-900">Auth, onboarding en settings actief</p>
</div>
</section>
<Card className="mt-8 rounded-[2rem] border border-border/60 bg-card/80 py-0 shadow-[0_10px_45px_rgba(71,85,105,0.08)] backdrop-blur">
<CardContent className="grid gap-5 p-6 sm:grid-cols-2 lg:grid-cols-4">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Volgende story
</p>
<p className="mt-2 font-semibold text-slate-900">ST-201 Ochtendcheck-in</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Doelgroep
</p>
<p className="mt-2 font-semibold text-slate-900">Volwassen individuele gebruikers</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Positionering
</p>
<p className="mt-2 font-semibold text-slate-900">Wellness / self-management</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Status
</p>
<p className="mt-2 font-semibold text-slate-900">Auth, onboarding en settings actief</p>
</div>
</CardContent>
</Card>
</div>
</main>
);

View file

@ -2,9 +2,19 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { signOutAction } from "@/app/auth-actions";
import { SettingsForm } from "@/components/settings/settings-form";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { sanitizeNextPath } from "@/lib/auth/navigation";
import { getAuthState } from "@/lib/auth/session";
import { getProfileBundleForCurrentUser } from "@/lib/profile/service";
import { cn } from "@/lib/utils";
export const dynamic = "force-dynamic";
@ -81,55 +91,63 @@ export default async function SettingsPage({ searchParams }: SettingsPageProps)
<div className="flex flex-wrap items-center gap-3">
<Link
href="/dashboard"
className="rounded-full border border-black/10 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:-translate-y-0.5 hover:text-slate-950"
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"h-11 rounded-full px-5",
)}
>
Terug naar dashboard
</Link>
<form action={signOutAction}>
<button
type="submit"
className="rounded-full bg-emerald-950 px-5 py-3 text-sm font-semibold text-emerald-50 transition hover:-translate-y-0.5 hover:bg-emerald-900"
>
<Button type="submit" size="lg" className="h-11 rounded-full px-5">
Uitloggen
</button>
</Button>
</form>
</div>
</header>
{notice ? (
<div className="rounded-[1.5rem] border border-emerald-200 bg-emerald-50 px-5 py-4 text-sm leading-7 text-emerald-900">
{notice}
</div>
<Alert className="rounded-[1.5rem] border-emerald-200 bg-emerald-50 text-emerald-950 [&_svg]:text-emerald-700">
<AlertDescription className="leading-7 text-current">
{notice}
</AlertDescription>
</Alert>
) : null}
<section className="grid gap-5 lg:grid-cols-[1.1fr_0.9fr]">
<SettingsForm profileBundle={profileBundle} />
<aside className="space-y-5">
<article className="rounded-[1.75rem] border border-black/10 bg-white/75 p-6 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Account
</p>
<p className="mt-3 text-lg font-semibold text-slate-900">
{profileTitle}
</p>
<p className="mt-3 text-sm leading-7 text-slate-700">
E-mailadres: {profileBundle.profile.email ?? authState.email ?? "Onbekend"}
</p>
</article>
<Card className="rounded-[1.75rem] border border-border/60 bg-card/90 py-0 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Account
</p>
<CardTitle className="text-lg text-slate-900">{profileTitle}</CardTitle>
</CardHeader>
<CardContent className="pb-6">
<CardDescription className="text-sm leading-7 text-muted-foreground">
E-mailadres: {profileBundle.profile.email ?? authState.email ?? "Onbekend"}
</CardDescription>
</CardContent>
</Card>
<article className="rounded-[1.75rem] border border-black/10 bg-white/75 p-6 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-500">
Huidige status
</p>
<p className="mt-3 text-lg font-semibold text-slate-900">
Onboarding {profileBundle.profile.onboardingCompleted ? "afgerond" : "later afronden"}
</p>
<p className="mt-3 text-sm leading-7 text-slate-700">
Je kunt later altijd terug naar onboarding of direct verder bouwen op
deze voorkeuren in de dagflow.
</p>
</article>
<Card className="rounded-[1.75rem] border border-border/60 bg-card/90 py-0 shadow-[0_12px_40px_rgba(71,85,105,0.08)]">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Huidige status
</p>
<CardTitle className="text-lg text-slate-900">
Onboarding {profileBundle.profile.onboardingCompleted ? "afgerond" : "later afronden"}
</CardTitle>
</CardHeader>
<CardContent className="pb-6">
<CardDescription className="text-sm leading-7 text-muted-foreground">
Je kunt later altijd terug naar onboarding of direct verder bouwen op
deze voorkeuren in de dagflow.
</CardDescription>
</CardContent>
</Card>
</aside>
</section>
</div>

View file

@ -3,6 +3,10 @@ import { redirect } from "next/navigation";
import { AuthNotice } from "@/components/auth/auth-notice";
import { AuthPanel } from "@/components/auth/auth-panel";
import { signUpAction } from "@/app/auth-actions";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { getAuthNotice } from "@/lib/auth/messages";
import { buildPathWithQuery, sanitizeNextPath } from "@/lib/auth/navigation";
import { getAuthState } from "@/lib/auth/session";
@ -53,42 +57,51 @@ export default async function SignUpPage({ searchParams }: SignUpPageProps) {
<AuthNotice notice={notice} />
{!authState.isConfigured ? (
<div className="rounded-2xl border border-sky-200 bg-sky-50 px-4 py-4 text-sm leading-7 text-sky-900">
Voeg eerst je Supabase-gegevens toe in `.env.local` op basis van `.env.example`.
</div>
<Alert className="rounded-[1.5rem] border-sky-200 bg-sky-50 text-sky-950 [&_svg]:text-sky-700">
<AlertDescription className="leading-7 text-current">
Voeg eerst je Supabase-gegevens toe in `.env.local` op basis van `.env.example`.
</AlertDescription>
</Alert>
) : (
<form action={signUpAction} className="space-y-4">
<form action={signUpAction} className="space-y-5">
<input type="hidden" name="next" value={next} />
<label className="block text-sm font-medium text-slate-800">
E-mailadres
<input
className="mt-2 w-full rounded-2xl border border-black/10 bg-stone-50 px-4 py-3 text-base outline-none transition focus:border-emerald-600 focus:bg-white"
<div className="space-y-2">
<Label htmlFor="email" className="text-slate-800">
E-mailadres
</Label>
<Input
id="email"
className="h-12 rounded-[1.25rem] bg-background/80 px-4 text-base md:text-base"
type="email"
name="email"
autoComplete="email"
required
/>
</label>
</div>
<label className="block text-sm font-medium text-slate-800">
Wachtwoord
<input
className="mt-2 w-full rounded-2xl border border-black/10 bg-stone-50 px-4 py-3 text-base outline-none transition focus:border-emerald-600 focus:bg-white"
<div className="space-y-2">
<Label htmlFor="password" className="text-slate-800">
Wachtwoord
</Label>
<Input
id="password"
className="h-12 rounded-[1.25rem] bg-background/80 px-4 text-base md:text-base"
type="password"
name="password"
autoComplete="new-password"
minLength={8}
required
/>
</label>
</div>
<button
<Button
type="submit"
className="inline-flex w-full items-center justify-center rounded-2xl bg-emerald-950 px-5 py-3 text-sm font-semibold text-emerald-50 transition hover:-translate-y-0.5 hover:bg-emerald-900"
size="lg"
className="w-full rounded-[1.25rem]"
>
Account aanmaken
</button>
</Button>
</form>
)}
</AuthPanel>