@@ -116,15 +216,15 @@ export default function LoginPage() {
{/* Magic Link Login */}
{!showDemoLogin && (
-
+
📧 Login met Magic Link
-
+
)
diff --git a/components/ui/bento-grid.tsx b/components/ui/bento-grid.tsx
new file mode 100644
index 0000000..342a957
--- /dev/null
+++ b/components/ui/bento-grid.tsx
@@ -0,0 +1,79 @@
+import { ReactNode } from "react";
+import { ArrowRightIcon } from "@radix-ui/react-icons";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+
+const BentoGrid = ({
+ children,
+ className,
+}: {
+ children: ReactNode;
+ className?: string;
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const BentoCard = ({
+ name,
+ className,
+ background,
+ Icon,
+ description,
+ href,
+ cta,
+}: {
+ name: string;
+ className: string;
+ background: ReactNode;
+ Icon: any;
+ description: string;
+ href: string;
+ cta: string;
+}) => (
+
+
{background}
+
+
+
+ {name}
+
+
{description}
+
+
+
+
+
+);
+
+export { BentoCard, BentoGrid };
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
new file mode 100644
index 0000000..ac472bb
--- /dev/null
+++ b/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes
,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ },
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/content/nl/timeline.json b/content/nl/timeline.json
new file mode 100644
index 0000000..9b9c578
--- /dev/null
+++ b/content/nl/timeline.json
@@ -0,0 +1,160 @@
+{
+ "heading": "Build in Public: 4 Weken",
+ "description": "Volg de ontwikkeling van een volledig EPD prototype - van idee tot werkende applicatie. Volledige transparantie over features, tijd en kosten.",
+ "weeks": [
+ {
+ "weekNumber": 1,
+ "title": "Week 1 • Nov 11-17",
+ "status": "completed",
+ "description": "Foundation & Marketing - Technische basis opzetten en marketing website bouwen met teal-first design system. Database schema, auth flow en vereenvoudigde user journey.",
+ "features": [
+ {
+ "title": "Project Setup",
+ "description": "Next.js 15 + Supabase + Vercel running met complete development environment",
+ "icon": "Rocket"
+ },
+ {
+ "title": "Database & Auth",
+ "description": "PostgreSQL schema met 5 core tables en RLS policies voor data isolatie",
+ "icon": "Database"
+ },
+ {
+ "title": "Marketing Refactor",
+ "description": "Vereenvoudigde homepage met statement section en timeline component",
+ "icon": "Sparkles"
+ },
+ {
+ "title": "Teal Design System",
+ "description": "Modern, innovatief brand identity met teal primary color en WCAG AA compliance",
+ "icon": "Palette"
+ }
+ ],
+ "metrics": {
+ "hours": "32 uur",
+ "cost": "€0",
+ "linesOfCode": "~2500"
+ },
+ "achievements": [
+ "✅ Development environment compleet",
+ "✅ Database schema en RLS policies live",
+ "✅ Marketing website refactored",
+ "✅ Teal design system geïmplementeerd"
+ ]
+ },
+ {
+ "weekNumber": 2,
+ "title": "Week 2 • Nov 18-24",
+ "status": "planned",
+ "description": "EPD Core - Client management module met CRUD operations, responsive UI en navigatie. De basis van het EPD systeem.",
+ "features": [
+ {
+ "title": "Coming Soon Dashboard",
+ "description": "Placeholder dashboard met roadmap en verwachtingen management",
+ "icon": "Calendar"
+ },
+ {
+ "title": "App Layout & Navigation",
+ "description": "Header, sidebar, breadcrumbs en logout flow voor EPD applicatie",
+ "icon": "Layout"
+ },
+ {
+ "title": "Client CRUD",
+ "description": "Volledige client management: toevoegen, bewerken, verwijderen en zoeken",
+ "icon": "Users"
+ },
+ {
+ "title": "Responsive Design",
+ "description": "Mobile-first UI met adaptive layouts voor alle schermformaten",
+ "icon": "Smartphone"
+ }
+ ],
+ "metrics": {
+ "hours": "~28 uur",
+ "cost": "~€50",
+ "linesOfCode": "~2000"
+ },
+ "achievements": []
+ },
+ {
+ "weekNumber": 3,
+ "title": "Week 3 • Nov 25-Dec 1",
+ "status": "planned",
+ "description": "AI Magic - TipTap editor, Claude API integratie en AI-gestuurde workflows. Het hart van de time-saving features.",
+ "features": [
+ {
+ "title": "TipTap Rich Text Editor",
+ "description": "ProseMirror-based editor voor intake notes met formatting en shortcuts",
+ "icon": "FileText"
+ },
+ {
+ "title": "AI Intake Samenvatting",
+ "description": "Claude 3.5 Sonnet genereert samenvattingen in < 5 seconden",
+ "time": "< 5 sec",
+ "traditional": "15-20 min",
+ "icon": "Brain"
+ },
+ {
+ "title": "DSM-light Classificatie",
+ "description": "Automatische categorisatie van problematiek op basis van intake",
+ "time": "< 3 sec",
+ "traditional": "10-15 min",
+ "icon": "Tags"
+ },
+ {
+ "title": "SMART Behandelplan",
+ "description": "AI-gegenereerde behandelplannen met doelen en interventies",
+ "time": "< 5 sec",
+ "traditional": "20-30 min",
+ "icon": "Target"
+ }
+ ],
+ "metrics": {
+ "hours": "~35 uur",
+ "cost": "~€50",
+ "linesOfCode": "~3000"
+ },
+ "achievements": []
+ },
+ {
+ "weekNumber": 4,
+ "title": "Week 4 • Dec 2-8",
+ "status": "planned",
+ "description": "Polish & Launch - Onboarding, performance optimization en demo preparation. Klaar voor de wereld.",
+ "features": [
+ {
+ "title": "User Onboarding",
+ "description": "Tooltips en walkthrough voor first-time users met feature highlights",
+ "icon": "HelpCircle"
+ },
+ {
+ "title": "Performance Optimization",
+ "description": "Lighthouse score > 90, LCP < 2.5s en code splitting",
+ "icon": "Zap"
+ },
+ {
+ "title": "Accessibility Audit",
+ "description": "WCAG AA compliance, keyboard navigation en focus states",
+ "icon": "Eye"
+ },
+ {
+ "title": "Production Deployment",
+ "description": "Live op Vercel met monitoring en error tracking setup",
+ "icon": "Rocket"
+ }
+ ],
+ "metrics": {
+ "hours": "~25 uur",
+ "cost": "~€50",
+ "linesOfCode": "~1500"
+ },
+ "achievements": []
+ }
+ ],
+ "totals": {
+ "totalHours": "120 uur",
+ "totalCost": "€150",
+ "traditionalCost": "€100.000+",
+ "traditionalTime": "12-24 maanden",
+ "savings": "99.85% goedkoper, 99% sneller"
+ }
+}
diff --git a/lib/types/client.ts b/lib/types/client.ts
new file mode 100644
index 0000000..2d39c5c
--- /dev/null
+++ b/lib/types/client.ts
@@ -0,0 +1,59 @@
+/**
+ * Client Types
+ *
+ * Type definitions for client-related data structures
+ */
+
+import { Database } from '@/lib/database.types';
+
+// Base client type from database
+export type Client = Database['public']['Tables']['clients']['Row'];
+export type ClientInsert = Database['public']['Tables']['clients']['Insert'];
+export type ClientUpdate = Database['public']['Tables']['clients']['Update'];
+
+// Extended client type with computed fields
+export interface ClientWithAge extends Client {
+ age: number;
+ full_name: string;
+}
+
+// Client form data (for create/edit forms)
+export interface ClientFormData {
+ first_name: string;
+ last_name: string;
+ birth_date: string; // ISO date string (YYYY-MM-DD)
+}
+
+// Client list filters
+export interface ClientFilters {
+ search?: string;
+ sortBy?: 'name' | 'age' | 'created_at';
+ sortOrder?: 'asc' | 'desc';
+}
+
+/**
+ * Calculate age from birth date
+ */
+export function calculateAge(birthDate: string | Date): number {
+ const birth = typeof birthDate === 'string' ? new Date(birthDate) : birthDate;
+ const today = new Date();
+ let age = today.getFullYear() - birth.getFullYear();
+ const monthDiff = today.getMonth() - birth.getMonth();
+
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
+ age--;
+ }
+
+ return age;
+}
+
+/**
+ * Transform client to extended client with computed fields
+ */
+export function transformClient(client: Client): ClientWithAge {
+ return {
+ ...client,
+ age: calculateAge(client.birth_date),
+ full_name: `${client.first_name} ${client.last_name}`,
+ };
+}
diff --git a/middleware.ts b/middleware.ts
index 5184825..fbb1466 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -74,9 +74,9 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(redirectUrl)
}
- // Redirect to /clients if authenticated and trying to access login
+ // Redirect to /epd/clients if authenticated and trying to access login
if (user && pathname === '/login') {
- return NextResponse.redirect(new URL('/clients', request.url))
+ return NextResponse.redirect(new URL('/epd/clients', request.url))
}
return supabaseResponse
diff --git a/package.json b/package.json
index e0154b2..4afa27a 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"types:generate": "npx supabase gen types typescript --project-id dqugbrpwtisgyxscpefg > lib/supabase/types.ts"
},
"dependencies": {
+ "@radix-ui/react-icons": "^1.3.2",
+ "@radix-ui/react-slot": "^1.2.4",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.4.0",
"@supabase/ssr": "^0.7.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ae11b6b..4d78229 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,12 @@ importers:
.:
dependencies:
+ '@radix-ui/react-icons':
+ specifier: ^1.3.2
+ version: 1.3.2(react@19.2.0)
+ '@radix-ui/react-slot':
+ specifier: ^1.2.4
+ version: 1.2.4(@types/react@19.2.2)(react@19.2.0)
'@react-three/drei':
specifier: ^10.7.7
version: 10.7.7(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1))(@types/react@19.2.2)(@types/three@0.181.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1)
@@ -638,6 +644,29 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-icons@1.3.2':
+ resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==}
+ peerDependencies:
+ react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc
+
+ '@radix-ui/react-slot@1.2.4':
+ resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@react-three/drei@10.7.7':
resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==}
peerDependencies:
@@ -3201,6 +3230,23 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 19.2.2
+
+ '@radix-ui/react-icons@1.3.2(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+
+ '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 19.2.2
+
'@react-three/drei@10.7.7(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1))(@types/react@19.2.2)(@types/three@0.181.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1)':
dependencies:
'@babel/runtime': 7.28.4