# Les 6: QuickPoll Live Coding Guide

> Dit is je stap-voor-stap spiekbriefje. Lees dit op je eigen scherm terwijl je op de beamer codeert.
> Alles staat in de volgorde waarin je het typt. Niets wordt overgeslagen.

---

## STAP 0 — Project aanmaken

**Terminal:**
```bash
npx create-next-app@latest quickpoll
```

**Opties selecteren:**
- TypeScript → Yes
- ESLint → Yes
- Tailwind CSS → Yes
- `src/` directory → Yes
- App Router → Yes
- Import alias → @/*

```bash
cd quickpoll
npm run dev
```

**Check:** localhost:3000 → Next.js standaardpagina

**Vertel:** "Dit is create-next-app. Geeft ons een compleet project met TypeScript, Tailwind en de App Router. Alles wat we nodig hebben."

---

## STAP 1 — Types

### 1a. Maak `src/types/index.ts`

**Vertel:** "Eerst: wat IS een poll? We definiëren dat met een TypeScript interface."

Typ:
```tsx
export interface Poll {
```

**Vertel:** "Een poll heeft een id…"

```tsx
  id: string;
```

"…een vraag…"

```tsx
  question: string;
```

"…een lijst opties…"

```tsx
  options: string[];
```

"…en stemmen per optie. De index matcht: votes[0] hoort bij options[0]."

```tsx
  votes: number[];
}
```

---

## STAP 1 vervolg — Data

### 1b. Maak `src/lib/data.ts`

**Vertel:** "Nu onze 'database'. Gewoon een array in het geheugen. Later vervangen we dit door Supabase."

Typ de import:
```tsx
import type { Poll } from "@/types";
```

**Vertel:** "We maken drie polls aan als testdata."

```tsx
let polls: Poll[] = [
  {
    id: "1",
    question: "Wat is je favoriete programmeer taal?",
    options: ["JavaScript", "Python", "TypeScript", "Rust"],
    votes: [45, 32, 28, 15],
  },
  {
    id: "2",
    question: "Hoe veel uur slaap krijg je per nacht?",
    options: ["< 6 uur", "6-8 uur", "8+ uur"],
    votes: [12, 68, 35],
  },
  {
    id: "3",
    question: "Welke framework gebruik je het meest?",
    options: ["React", "Vue", "Svelte", "Angular"],
    votes: [89, 34, 12, 8],
  },
];
```

**Vertel:** "Nu helper functies. Eerst: alle polls ophalen."

```tsx
export function getPolls(): Poll[] {
  return polls;
}
```

"Eén poll ophalen op id:"

```tsx
export function getPollById(id: string): Poll | undefined {
  return polls.find((poll) => poll.id === id);
}
```

**Vertel:** "Let op het return type: `Poll | undefined`. Misschien bestaat de poll niet — dat moeten we afvangen."

"En stemmen:"

```tsx
export function votePoll(pollId: string, optionIndex: number): Poll | undefined {
  const poll = polls.find((p) => p.id === pollId);
```

"Check of de poll bestaat en de index klopt:"

```tsx
  if (!poll || optionIndex < 0 || optionIndex >= poll.options.length) {
    return undefined;
  }
```

"Stem ophogen en poll teruggeven:"

```tsx
  poll.votes[optionIndex]++;
  return poll;
}
```

**Checkpoint:** "Heeft iedereen types/index.ts en lib/data.ts?"

---

## STAP 2 — Layout

### 2a. Vervang `src/app/layout.tsx`

**Vertel:** "De layout wrapt elke pagina. Navbar en footer staan hier — die veranderen nooit."

Wis de inhoud. Typ de imports:

```tsx
import type { Metadata } from "next";
import Link from "next/link";
import "./globals.css";
```

**Vertel:** "Link is de Next.js versie van een `<a>` tag. Client-side navigatie, geen page reload."

SEO metadata:

```tsx
export const metadata: Metadata = {
  title: "QuickPoll",
  description: "Een snelle polling app met Next.js",
};
```

**Vertel:** "metadata is voor SEO. Kijk straks in je browser tab."

De functie:

```tsx
export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="nl">
      <body className="bg-gray-50 min-h-screen flex flex-col">
```

**Vertel:** "Nu de navbar. Simpel: logo en een home link."

```tsx
        <nav className="bg-white shadow-sm border-b border-gray-200">
          <div className="container mx-auto px-4 py-4 flex items-center gap-8">
            <Link
              href="/"
              className="text-2xl font-bold text-purple-600 hover:text-purple-700 transition-colors"
            >
              QuickPoll
            </Link>
            <Link
              href="/"
              className="text-gray-700 hover:text-purple-600 transition-colors font-medium"
            >
              Home
            </Link>
          </div>
        </nav>
```

Main en footer:

```tsx
        <main className="flex-1">{children}</main>

        <footer className="bg-white border-t border-gray-200 mt-12">
          <div className="container mx-auto px-4 py-6 text-center text-gray-600 text-sm">
            © 2026 QuickPoll — Built with Next.js 15
          </div>
        </footer>
      </body>
    </html>
  );
}
```

**Vertel:** "`{children}` — hier komt de pagina-inhoud. Elke pagina in de app wordt hier ingeladen."

**Check:** localhost:3000 → navbar met "QuickPoll" zichtbaar

---

### 2b. Vervang `src/app/page.tsx`

**Vertel:** "Nu de homepage. Dit is een Server Component — geen 'use client', data direct ophalen."

Wis de inhoud. Typ:

```tsx
import Link from "next/link";
import { getPolls } from "@/lib/data";
```

**Vertel:** "We importeren getPolls uit onze data module. Dat `@/` is een shortcut voor de src/ folder."

```tsx
export default function Home() {
  const polls = getPolls();
```

**Vertel:** "Kijk: we roepen getPolls() gewoon aan. Geen useEffect, geen loading state. Dit is een Server Component — data ophalen is direct."

De JSX:

```tsx
  return (
    <div className="container mx-auto py-12 px-4">
      <div className="mb-12">
        <h1 className="text-4xl font-bold text-gray-900">QuickPoll</h1>
        <p className="text-gray-600 mt-2">
          Kies een poll en stem af in een oogwenk
        </p>
      </div>
```

**Vertel:** "Nu de poll cards. We mappen over de polls array."

```tsx
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {polls.map((poll) => {
          const totalVotes = poll.votes.reduce((sum, v) => sum + v, 0);

          return (
            <Link key={poll.id} href={`/poll/${poll.id}`}>
              <div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-lg transition-shadow cursor-pointer h-full">
                <h2 className="text-lg font-semibold text-gray-900 mb-4">
                  {poll.question}
                </h2>
                <div className="flex items-center justify-between text-sm text-gray-600">
                  <span>{poll.options.length} opties</span>
                  <span>{totalVotes} stemmen</span>
                </div>
                <div className="mt-4 text-purple-600 font-semibold text-sm">
                  Stemmen →
                </div>
              </div>
            </Link>
          );
        })}
      </div>
    </div>
  );
}
```

**Vertel:** "`.reduce()` telt alle stemmen op. En elke card linkt naar `/poll/1`, `/poll/2` etc. Die pagina maken we straks."

**Check:** localhost:3000 → 3 poll cards zichtbaar

**Checkpoint:** "Ziet iedereen 3 cards? Mooi."

---

## STAP 3 — GET API Route

### 3a. Maak `src/app/api/polls/[id]/route.ts`

**Vertel:** "Nu onze eerste API route. In Next.js is de folder-structuur je URL. Deze folder wordt `/api/polls/1`."

"Die `[id]` met vierkante haakjes — dat is een dynamic route. Het getal in de URL wordt de id parameter."

Typ:

```tsx
import { NextResponse } from "next/server";
import { getPollById } from "@/lib/data";
```

**Vertel:** "Een API route exporteert functies met HTTP method namen. GET voor data ophalen."

```tsx
export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
```

**Vertel:** "Let op: `Promise<{ id: string }>`. In Next.js 15 zijn params een Promise. Vergeet de await niet!"

```tsx
  const { id } = await params;
  const poll = getPollById(id);
```

"Als de poll niet bestaat: 404."

```tsx
  if (!poll) {
    return NextResponse.json({ error: "Poll not found" }, { status: 404 });
  }
```

"Anders: stuur de poll als JSON."

```tsx
  return NextResponse.json(poll);
}
```

**Test:** Open browser → `localhost:3000/api/polls/1` → JSON!

**Vertel:** "Probeer ook `/api/polls/999` — daar krijg je de 404."

**Checkpoint:** "Werkt de API? Recap klaar. Nu het nieuwe werk."

---

## STAP 4 — POST Vote Route

### 4a. Maak `src/app/api/polls/[id]/vote/route.ts`

**Vertel:** "Tot nu toe hadden we GET — data ophalen. Nu POST — data wijzigen. In dit geval: stemmen."

Typ de imports:

```tsx
import { NextResponse } from "next/server";
import { votePoll } from "@/lib/data";
```

**Vertel:** "Eerst definiëren we types voor de parameters en de request body."

```tsx
interface RouteParams {
  params: Promise<{ id: string }>;
}

interface VoteBody {
  optionIndex: number;
}
```

**Vertel:** "Een POST route volgt altijd vijf stappen: params, body, validatie, actie, response."

```tsx
export async function POST(
  request: Request,
  { params }: RouteParams
): Promise<NextResponse> {
```

"Stap 1: params uitlezen — welke poll?"

```tsx
  const { id } = await params;
```

"Stap 2: body uitlezen — welke optie? `request.json()` leest wat de client meestuurt."

```tsx
  const body: VoteBody = await request.json();
```

"Stap 3: validatie — is optionIndex een nummer?"

```tsx
  if (typeof body.optionIndex !== "number") {
    return NextResponse.json(
      { error: "optionIndex is verplicht" },
      { status: 400 }
    );
  }
```

**Vertel:** "400 = bad request. Jij stuurde slechte data."

"Stap 4: actie uitvoeren."

```tsx
  const updatedPoll = votePoll(id, body.optionIndex);

  if (!updatedPoll) {
    return NextResponse.json(
      { error: "Poll niet gevonden of ongeldige optie" },
      { status: 404 }
    );
  }
```

**Vertel:** "404 = niet gevonden. Twee checks: poll bestaat niet, of optie-index ongeldig."

"Stap 5: response sturen."

```tsx
  return NextResponse.json(updatedPoll);
}
```

### 4b. Testen in de browser console

**Vertel:** "We testen! Open DevTools (F12), ga naar Console, en plak dit:"

```javascript
fetch('/api/polls/1/vote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ optionIndex: 0 })
}).then(r => r.json()).then(console.log)
```

**Vertel:** "Zie je? De votes zijn veranderd in de JSON. De API werkt."

**Checkpoint:** "Heeft iedereen een JSON resultaat in de console?"

---

## STAP 5 — Poll Detail Pagina

### 5a. Maak `src/app/poll/[id]/page.tsx`

**Vertel:** "Nu de pagina waar je op een poll stemt. Weer een dynamic route: `/poll/1`, `/poll/2` etc."

Imports:

```tsx
import { notFound } from "next/navigation";
import { getPollById } from "@/lib/data";
import VoteForm from "@/components/VoteForm";
import type { Metadata } from "next";
```

**Vertel:** "We importeren VoteForm — dat component bestaat nog niet, dat bouwen we zo."

Type:

```tsx
interface PageProps {
  params: Promise<{ id: string }>;
}
```

**Vertel:** "Eerste nieuw concept: `generateMetadata`. Hiermee krijgt elke poll z'n eigen titel in de browser tab."

```tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const { id } = await params;
  const poll = getPollById(id);

  if (!poll) {
    return { title: "Poll niet gevonden" };
  }

  return {
    title: `${poll.question} — QuickPoll`,
    description: `Stem op: ${poll.options.join(", ")}`,
  };
}
```

**Vertel:** "Gratis SEO. Google ziet 'Wat is de beste editor?' in plaats van gewoon 'QuickPoll'."

Nu de pagina zelf:

```tsx
export default async function PollPage({ params }: PageProps) {
  const { id } = await params;
  const poll = getPollById(id);
```

**Vertel:** "Tweede concept: `notFound()`. Als de poll niet bestaat, roep je dit aan. Next.js toont dan automatisch een 404 pagina."

```tsx
  if (!poll) {
    notFound();
  }
```

"En we renderen de poll titel en het VoteForm component:"

```tsx
  return (
    <div className="max-w-2xl mx-auto py-12 px-4">
      <h1 className="text-2xl font-bold text-gray-900 mb-6">
        {poll.question}
      </h1>
      <VoteForm poll={poll} />
    </div>
  );
}
```

**Vertel:** "Dit is een Server Component die een Client Component rendert. Server haalt data, client doet interactie. Dat is de kern van Next.js."

**Check:** localhost:3000/poll/1 → error (VoteForm bestaat nog niet, dat klopt!)

---

## ☕ PAUZE — 15 minuten

**Vertel:** "Pauze! Stap 0-5 staan er. Na de pauze bouwen we het interactieve hart: de VoteForm."

---

## STAP 6 — VoteForm Component

### 6a. Maak `src/components/VoteForm.tsx`

**Vertel:** "Dit is het leukste stuk. Een Client Component met interactiviteit."

"Eerste regel: `'use client'`. Daarmee zeg je tegen Next.js: dit draait in de browser. Alles met useState of onClick MOET dit hebben."

```tsx
"use client";
```

Imports:

```tsx
import { useState } from "react";
import type { Poll } from "@/types";
```

Props interface:

```tsx
interface VoteFormProps {
  poll: Poll;
}
```

**Vertel:** "We ontvangen een poll als prop van de Server Component."

Functie openen:

```tsx
export default function VoteForm({ poll }: VoteFormProps) {
```

### 6b. State variabelen

**Vertel:** "We hebben vier stukken state nodig. Ik leg ze één voor één uit."

"Welke optie is geselecteerd? `null` want er kan nog niks geselecteerd zijn:"

```tsx
  const [selectedOption, setSelectedOption] = useState<number | null>(null);
```

"Is er al gestemd?"

```tsx
  const [hasVoted, setHasVoted] = useState<boolean>(false);
```

"Zijn we bezig met het versturen? Om dubbel klikken te voorkomen:"

```tsx
  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
```

"De huidige poll data — die updaten we na het stemmen:"

```tsx
  const [currentPoll, setCurrentPoll] = useState<Poll>(poll);
```

**Checkpoint:** "Heeft iedereen de vier useState regels?"

### 6c. Helper functies

**Vertel:** "Totaal stemmen berekenen:"

```tsx
  const totalVotes: number = currentPoll.votes.reduce(
    (sum, v) => sum + v,
    0
  );
```

"Percentage per optie:"

```tsx
  function getPercentage(votes: number): number {
    if (totalVotes === 0) return 0;
    return Math.round((votes / totalVotes) * 100);
  }
```

### 6d. handleVote functie

**Vertel:** "Nu de stem-functie. Dit is dezelfde fetch als we net in de console testten — maar nu in een component."

```tsx
  async function handleVote(): Promise<void> {
```

"Guard clause: als er niks geselecteerd is of we al bezig zijn, stop."

```tsx
    if (selectedOption === null || isSubmitting) return;
    setIsSubmitting(true);
```

"De fetch naar onze POST route:"

```tsx
    const response = await fetch(`/api/polls/${currentPoll.id}/vote`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ optionIndex: selectedOption }),
    });
```

"Als het lukt: update de poll data en zet hasVoted op true."

```tsx
    if (response.ok) {
      const updatedPoll: Poll = await response.json();
      setCurrentPoll(updatedPoll);
      setHasVoted(true);
    }

    setIsSubmitting(false);
  }
```

**Checkpoint:** "Tot hier mee? Dan gaan we de UI bouwen."

### 6e. UI — De opties buttons

**Vertel:** "De return. We mappen over de opties — elke optie wordt een button."

```tsx
  return (
    <div className="space-y-3">
      {currentPoll.options.map((option, index) => {
        const percentage = getPercentage(currentPoll.votes[index]);
        const isSelected = selectedOption === index;
```

**Vertel:** "Elke button. onClick selecteert de optie, maar alleen als je nog niet gestemd hebt."

```tsx
        return (
          <button
            key={index}
            onClick={() => !hasVoted && setSelectedOption(index)}
            disabled={hasVoted}
```

**Vertel:** "Nu de conditional classes. Dit is hoe je in Tailwind conditioneel stylet — ternary operators."

```tsx
            className={`w-full text-left p-4 rounded-lg border-2 transition-all
              relative overflow-hidden ${
              hasVoted
                ? "border-gray-200 cursor-default"
                : isSelected
                  ? "border-purple-500 bg-purple-50"
                  : "border-gray-200 hover:border-purple-300 cursor-pointer"
            }`}
          >
```

**Vertel:** "De percentage bar. Een div die de achtergrond paars vult. De breedte is het percentage."

```tsx
            {hasVoted && (
              <div
                className="absolute inset-0 bg-purple-100 transition-all duration-500"
                style={{ width: `${percentage}%` }}
              />
            )}
```

**Vertel:** "`transition-all duration-500` zorgt voor de animatie. Pure CSS via Tailwind."

De tekst met percentage:

```tsx
            <div className="relative flex justify-between items-center">
              <span className="font-medium">{option}</span>
              {hasVoted && (
                <span className="text-sm font-semibold text-purple-700">
                  {percentage}%
                </span>
              )}
            </div>
          </button>
        );
      })}
```

### 6f. UI — Stem button en bedankt bericht

**Vertel:** "De stem-knop. Alleen zichtbaar als je nog niet gestemd hebt."

```tsx
      {!hasVoted && (
        <button
          onClick={handleVote}
          disabled={selectedOption === null || isSubmitting}
          className="w-full bg-purple-600 text-white py-3 rounded-lg font-medium
            hover:bg-purple-700 disabled:bg-gray-300 transition-colors mt-4"
        >
          {isSubmitting ? "Bezig met stemmen..." : "Stem!"}
        </button>
      )}
```

"En het bedankt bericht na het stemmen:"

```tsx
      {hasVoted && (
        <p className="text-center text-green-600 font-medium mt-4">
          Bedankt voor je stem! Totaal: {totalVotes} stemmen
        </p>
      )}
    </div>
  );
}
```

### 6g. Testen!

**Test:** localhost:3000/poll/1

**Vertel:** "Klik op een optie — paarse border. Klik Stem. Zie je de animatie? De bars vullen zich."

**Loop rond. Dit is het lastigste stuk. Neem 10 min extra als nodig.**

**Veelvoorkomende fouten:**
- `"use client"` vergeten → crash
- Fetch URL zonder backticks → werkt niet
- Bestand niet opgeslagen → niks veranderd

---

## STAP 7 — Loading, Error & Not-Found

**Vertel:** "In gewoon React bouw je dit zelf. In Next.js: maak een bestand aan en het werkt."

### 7a. Maak `src/app/loading.tsx`

**Vertel:** "Skeleton loading. Die grijze blokken die pulseren — `animate-pulse` is een Tailwind class."

```tsx
export default function Loading() {
  return (
    <div className="container mx-auto py-12 px-4 space-y-4">
      <div className="animate-pulse">
        <div className="h-8 bg-gray-200 rounded w-1/3 mb-2" />
        <div className="h-4 bg-gray-200 rounded w-1/2 mb-8" />
      </div>
      {[1, 2, 3].map((i) => (
        <div
          key={i}
          className="animate-pulse bg-white rounded-xl border border-gray-200 p-6"
        >
          <div className="h-5 bg-gray-200 rounded w-3/4 mb-3" />
          <div className="h-4 bg-gray-200 rounded w-1/4" />
        </div>
      ))}
    </div>
  );
}
```

### 7b. Maak `src/app/error.tsx`

**Vertel:** "Error boundary. LET OP: `'use client'` is hier VERPLICHT. Dat is een Next.js vereiste."

```tsx
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="text-center py-16">
      <h2 className="text-2xl font-bold text-red-600 mb-4">
        Er ging iets mis!
      </h2>
      <p className="text-gray-600 mb-6">{error.message}</p>
      <button
        onClick={() => reset()}
        className="bg-purple-600 text-white px-6 py-3 rounded-lg hover:bg-purple-700"
      >
        Probeer opnieuw
      </button>
    </div>
  );
}
```

**Vertel:** "`reset` herlaadt het component dat faalde. Zo hoeft de user niet de hele pagina te refreshen."

### 7c. Maak `src/app/not-found.tsx`

```tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <div className="text-center py-16">
      <h2 className="text-4xl font-bold text-gray-900 mb-4">404</h2>
      <p className="text-gray-600 mb-6">Deze pagina bestaat niet.</p>
      <Link
        href="/"
        className="bg-purple-600 text-white px-6 py-3 rounded-lg hover:bg-purple-700 inline-block"
      >
        Terug naar home
      </Link>
    </div>
  );
}
```

**Test:** localhost:3000/poll/999 → 404 pagina!

**Vertel:** "Dat werkt door de `notFound()` aanroep in onze poll pagina."

**Checkpoint:** "Alle drie werkend?"

---

## STAP 8 — Middleware

### 8a. Maak `src/middleware.ts`

⚠️ **Let op locatie: `src/middleware.ts` — NIET in app/, NIET in een subfolder!**

**Vertel:** "Middleware is als een portier. Elke request passeert hier eerst. Nu gebruiken we het voor logging — later bij Supabase voor authenticatie."

```tsx
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest): NextResponse {
  const start = Date.now();

  console.log(`[${request.method}] ${request.nextUrl.pathname}`);

  const response = NextResponse.next();
  response.headers.set("x-request-time", String(Date.now() - start));

  return response;
}
```

**Vertel:** "`NextResponse.next()` betekent: alles oké, ga door naar de route."

"De matcher bepaalt welke routes de middleware raakt:"

```tsx
export const config = {
  matcher: ["/api/:path*", "/poll/:path*"],
};
```

**Vertel:** "Alleen API routes en poll pagina's. De homepage skipt de middleware."

### 8b. Testen

**Vertel:** "Open je terminal waar `npm run dev` draait. Klik op een poll in de browser."

**In de terminal:** `[GET] /poll/1` verschijnt.

**Vertel:** "En in DevTools: Network tab → klik op een request → Headers → `x-request-time`. Dat is de verwerkingstijd die onze middleware toevoegt."

---

## ✅ APP COMPLEET!

**Vertel:** "De hele app werkt. Van nul naar werkend in één les."

---

## BONUS — Nieuwe Poll Aanmaken (als je tijd overhebt)

> ~20 minuten. Doe dit klassikaal als je voor 2:45 klaar bent.

**Vertel:** "We gaan een pagina maken waar je zelf een nieuwe poll kunt aanmaken. Twee dingen nodig: een API route die polls aanmaakt, en een pagina met een formulier."

### Bonus A. Voeg `createPoll` toe aan `src/lib/data.ts`

**Vertel:** "Eerst een helper functie in onze data module. Open `data.ts` en voeg onderaan toe:"

```tsx
export function createPoll(question: string, options: string[]): Poll {
  const newPoll: Poll = {
    id: String(nextId++),
    question,
    options,
    votes: new Array(options.length).fill(0),
  };
  polls.push(newPoll);
  return newPoll;
}
```

**Vertel:** "`new Array(options.length).fill(0)` — maakt een array met nullen, even lang als het aantal opties. Elke optie begint met 0 stemmen."

### Bonus B. Maak `src/app/api/polls/route.ts`

**Vertel:** "Nu de API route. Let op: dit is `api/polls/route.ts`, niet in de `[id]` folder. Dit wordt `POST /api/polls`."

```tsx
import { NextResponse } from "next/server";
import { createPoll } from "@/lib/data";
```

**Vertel:** "We definiëren een type voor de request body."

```tsx
interface CreatePollBody {
  question: string;
  options: string[];
}
```

"De POST handler:"

```tsx
export async function POST(request: Request): Promise<NextResponse> {
```

"Body uitlezen:"

```tsx
  const body: CreatePollBody = await request.json();
```

"Validatie — vraag moet er zijn, en minimaal 2 opties:"

```tsx
  if (!body.question || !body.options || body.options.length < 2) {
    return NextResponse.json(
      { error: "Vraag en minimaal 2 opties zijn verplicht" },
      { status: 400 }
    );
  }
```

"Poll aanmaken en teruggeven:"

```tsx
  const newPoll = createPoll(body.question, body.options);
  return NextResponse.json(newPoll, { status: 201 });
}
```

**Vertel:** "201 = created. Standaard HTTP status als je iets nieuws aanmaakt."

### Bonus C. Maak `src/app/create/page.tsx`

**Vertel:** "Nu het formulier. Dit is een Client Component — we hebben state nodig voor de inputs."

```tsx
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
```

**Vertel:** "`useRouter` is voor navigatie vanuit een Client Component. Straks sturen we de user terug naar home."

```tsx
export default function CreatePollPage() {
  const router = useRouter();
  const [question, setQuestion] = useState("");
  const [options, setOptions] = useState(["", ""]);
  const [isSubmitting, setIsSubmitting] = useState(false);
```

**Vertel:** "Drie stukken state. De vraag, de opties (begint met 2 lege strings), en een submit flag."

"Een functie om een optie toe te voegen:"

```tsx
  function addOption() {
    setOptions([...options, ""]);
  }
```

"En een functie om een optie te updaten op een specifieke index:"

```tsx
  function updateOption(index: number, value: string) {
    const newOptions = [...options];
    newOptions[index] = value;
    setOptions(newOptions);
  }
```

**Vertel:** "Nu de submit functie. Filtert lege opties weg, POST naar de API, redirect naar home."

```tsx
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setIsSubmitting(true);

    const filledOptions = options.filter((opt) => opt.trim() !== "");

    if (!question.trim() || filledOptions.length < 2) {
      alert("Vul een vraag in en minimaal 2 opties.");
      setIsSubmitting(false);
      return;
    }

    const response = await fetch("/api/polls", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ question, options: filledOptions }),
    });

    if (response.ok) {
      router.push("/");
    }

    setIsSubmitting(false);
  }
```

**Vertel:** "`e.preventDefault()` voorkomt dat de pagina herlaadt. `router.push('/')` stuurt je terug naar de homepage."

"Nu de JSX. Een form met inputs:"

```tsx
  return (
    <div className="max-w-2xl mx-auto py-12 px-4">
      <h1 className="text-2xl font-bold text-gray-900 mb-6">
        Nieuwe Poll Aanmaken
      </h1>

      <form onSubmit={handleSubmit} className="space-y-6">
        <div>
          <label className="block text-sm font-medium text-gray-700 mb-2">
            Vraag
          </label>
          <input
            type="text"
            value={question}
            onChange={(e) => setQuestion(e.target.value)}
            placeholder="Stel je vraag..."
            className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
          />
        </div>
```

"De opties — we mappen over de options array:"

```tsx
        <div>
          <label className="block text-sm font-medium text-gray-700 mb-2">
            Opties
          </label>
          <div className="space-y-3">
            {options.map((option, index) => (
              <input
                key={index}
                type="text"
                value={option}
                onChange={(e) => updateOption(index, e.target.value)}
                placeholder={`Optie ${index + 1}`}
                className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
              />
            ))}
          </div>
```

"Knop om meer opties toe te voegen:"

```tsx
          <button
            type="button"
            onClick={addOption}
            className="mt-3 text-purple-600 hover:text-purple-700 font-medium text-sm"
          >
            + Optie toevoegen
          </button>
        </div>
```

"En de submit knop:"

```tsx
        <button
          type="submit"
          disabled={isSubmitting}
          className="w-full bg-purple-600 text-white py-3 rounded-lg font-medium hover:bg-purple-700 disabled:bg-gray-300 transition-colors"
        >
          {isSubmitting ? "Aanmaken..." : "Poll Aanmaken"}
        </button>
      </form>
    </div>
  );
}
```

### Bonus D. Link toevoegen in layout

**Vertel:** "Nog even een link in de navbar. Open `layout.tsx` en voeg toe naast de Home link:"

```tsx
<Link
  href="/create"
  className="text-gray-700 hover:text-purple-600 transition-colors font-medium"
>
  Nieuwe Poll
</Link>
```

### Bonus E. Testen!

**Test:** localhost:3000/create → vul een vraag in → voeg opties toe → klik aanmaken → redirect naar home → nieuwe poll staat er!

**Vertel:** "Klaar! De hele app is compleet. Je kunt polls bekijken, stemmen, en nieuwe polls aanmaken."

---

## ✅ HELEMAAL KLAAR!

**Samenvatting wat we gebouwd hebben:**
1. Types & data (TypeScript interfaces, in-memory database)
2. Layout & homepage (Server Component, Link navigatie)
3. GET API route (folder = URL, dynamic routes)
4. POST API route (stemmen, validatie, status codes)
5. Poll detail pagina (generateMetadata, notFound)
6. VoteForm (Client Component, useState, fetch, animaties)
7. Loading, Error, Not-Found (speciale bestanden)
8. Middleware (request interceptie)
9. Bonus: Create poll (form, POST, redirect)

**De Next.js flow:**
Route (folder) → Page (server) → Client Component (interactie) → API Route (data) → Response

**Volgende les:** Tailwind CSS & shadcn/ui
