diff --git a/app/(app)/products/[id]/solo/page.tsx b/app/(app)/products/[id]/solo/page.tsx
new file mode 100644
index 0000000..e88773c
--- /dev/null
+++ b/app/(app)/products/[id]/solo/page.tsx
@@ -0,0 +1,81 @@
+import { notFound, redirect } from 'next/navigation'
+import { getSession } from '@/lib/auth'
+import { getAccessibleProduct } from '@/lib/product-access'
+import { setLastProductCookie } from '@/lib/cookies'
+import { prisma } from '@/lib/prisma'
+import { SoloBoard } from '@/components/solo/solo-board'
+import { NoActiveSprint } from '@/components/solo/no-active-sprint'
+import type { SoloTask } from '@/components/solo/solo-board'
+
+interface Props {
+ params: Promise<{ id: string }>
+}
+
+export default async function SoloProductPage({ params }: Props) {
+ const { id } = await params
+ const session = await getSession()
+ if (!session.userId) redirect('/login')
+
+ const product = await getAccessibleProduct(id, session.userId)
+ if (!product) notFound()
+
+ await setLastProductCookie(id)
+
+ const sprint = await prisma.sprint.findFirst({
+ where: { product_id: id, status: 'ACTIVE' },
+ })
+
+ if (!sprint) {
+ return (
+
+
+
+ )
+ }
+
+ const [rawTasks, unassignedCount] = await Promise.all([
+ prisma.task.findMany({
+ where: {
+ story: {
+ sprint_id: sprint.id,
+ assignee_id: session.userId,
+ },
+ },
+ include: {
+ story: { select: { id: true, title: true } },
+ },
+ orderBy: [
+ { story: { sort_order: 'asc' } },
+ { priority: 'asc' },
+ { sort_order: 'asc' },
+ ],
+ }),
+ prisma.story.count({
+ where: { sprint_id: sprint.id, assignee_id: null },
+ }),
+ ])
+
+ const tasks: SoloTask[] = rawTasks.map(t => ({
+ id: t.id,
+ title: t.title,
+ description: t.description,
+ implementation_plan: t.implementation_plan,
+ priority: t.priority,
+ sort_order: t.sort_order,
+ status: t.status as SoloTask['status'],
+ story_id: t.story.id,
+ story_title: t.story.title,
+ }))
+
+ return (
+
+ )
+}
diff --git a/app/(app)/solo/page.tsx b/app/(app)/solo/page.tsx
new file mode 100644
index 0000000..7991495
--- /dev/null
+++ b/app/(app)/solo/page.tsx
@@ -0,0 +1,26 @@
+import { redirect } from 'next/navigation'
+import { getSession } from '@/lib/auth'
+import { getLastProductCookie } from '@/lib/cookies'
+import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access'
+import { prisma } from '@/lib/prisma'
+import { ProductPicker } from '@/components/solo/product-picker'
+
+export default async function SoloPage() {
+ const session = await getSession()
+ if (!session.userId) redirect('/login')
+
+ const lastProductId = await getLastProductCookie()
+
+ if (lastProductId) {
+ const product = await getAccessibleProduct(lastProductId, session.userId)
+ if (product && !product.archived) redirect(`/products/${lastProductId}/solo`)
+ }
+
+ const products = await prisma.product.findMany({
+ where: { archived: false, ...productAccessFilter(session.userId) },
+ orderBy: { created_at: 'desc' },
+ select: { id: true, name: true, description: true },
+ })
+
+ return
+}
diff --git a/components/solo/no-active-sprint.tsx b/components/solo/no-active-sprint.tsx
new file mode 100644
index 0000000..12fab60
--- /dev/null
+++ b/components/solo/no-active-sprint.tsx
@@ -0,0 +1,25 @@
+import Link from 'next/link'
+
+interface NoActiveSprintProps {
+ productId: string
+ productName: string
+}
+
+export function NoActiveSprint({ productId, productName }: NoActiveSprintProps) {
+ return (
+
+
🏃
+
Geen actieve sprint
+
+ Er is nog geen actieve sprint voor {productName}.
+ Start een sprint in het Sprint Board om hier je taken te zien.
+
+
+ Naar Sprint Board →
+
+
+ )
+}
diff --git a/components/solo/product-picker.tsx b/components/solo/product-picker.tsx
new file mode 100644
index 0000000..513675c
--- /dev/null
+++ b/components/solo/product-picker.tsx
@@ -0,0 +1,46 @@
+import Link from 'next/link'
+
+interface Product {
+ id: string
+ name: string
+ description: string | null
+}
+
+interface ProductPickerProps {
+ products: Product[]
+}
+
+export function ProductPicker({ products }: ProductPickerProps) {
+ return (
+
+
Solo bord
+
+ Kies een product om je persoonlijke Kanban-bord te openen.
+
+
+ {products.length === 0 ? (
+
+
Je hebt nog geen producten.
+
+ + Nieuw product aanmaken
+
+
+ ) : (
+
+ {products.map(product => (
+
+ {product.name}
+ {product.description && (
+ {product.description}
+ )}
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/components/solo/solo-board.tsx b/components/solo/solo-board.tsx
new file mode 100644
index 0000000..3ed956a
--- /dev/null
+++ b/components/solo/solo-board.tsx
@@ -0,0 +1,32 @@
+'use client'
+
+export interface SoloTask {
+ id: string
+ title: string
+ description: string | null
+ implementation_plan: string | null
+ priority: number
+ sort_order: number
+ status: 'TO_DO' | 'IN_PROGRESS' | 'REVIEW' | 'DONE'
+ story_id: string
+ story_title: string
+}
+
+export interface SoloBoardProps {
+ productId: string
+ productName: string
+ sprintGoal: string
+ tasks: SoloTask[]
+ unassignedCount: number
+ isDemo: boolean
+ currentUserId: string
+}
+
+// Full implementation in ST-356
+export function SoloBoard(_props: SoloBoardProps) {
+ return (
+
+
Solo bord wordt geladen in ST-356…
+
+ )
+}
diff --git a/lib/cookies.ts b/lib/cookies.ts
new file mode 100644
index 0000000..c63bdc1
--- /dev/null
+++ b/lib/cookies.ts
@@ -0,0 +1,19 @@
+import { cookies } from 'next/headers'
+
+const LAST_PRODUCT_COOKIE = 'lastProductId'
+const THIRTY_DAYS_SECONDS = 60 * 60 * 24 * 30
+
+export async function setLastProductCookie(productId: string) {
+ const store = await cookies()
+ store.set(LAST_PRODUCT_COOKIE, productId, {
+ httpOnly: true,
+ sameSite: 'lax',
+ maxAge: THIRTY_DAYS_SECONDS,
+ path: '/',
+ })
+}
+
+export async function getLastProductCookie(): Promise {
+ const store = await cookies()
+ return store.get(LAST_PRODUCT_COOKIE)?.value ?? null
+}
diff --git a/proxy.ts b/proxy.ts
index d16da0d..0a4e228 100644
--- a/proxy.ts
+++ b/proxy.ts
@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { sessionOptions } from '@/lib/session'
-const protectedRoutes = ['/dashboard', '/products', '/todos', '/settings']
+const protectedRoutes = ['/dashboard', '/products', '/todos', '/settings', '/solo']
const authRoutes = ['/login', '/register']
export function proxy(request: NextRequest) {