fix: add les 8

This commit is contained in:
2026-04-07 16:58:45 +02:00
parent f65c24ffcd
commit d5599a601c
12 changed files with 815 additions and 2272 deletions

View File

@@ -0,0 +1,324 @@
# Les 8 — Docenttekst
## Van In-Memory naar Supabase
---
## Lesoverzicht
| Gegeven | Details |
|---------|---------|
| **Les** | 8 van 18 |
| **Onderwerp** | Supabase koppelen aan Next.js |
| **Duur** | 3 uur (09:00 12:00) |
| **Voorbereiding** | Werkend QuickPoll project, Supabase project met polls/options tabellen |
| **Benodigdheden** | Laptop, Cursor/VS Code, browser, Supabase account |
| **Aanpak** | Deel 1-3 klassikaal (docent loopt PDF door met studenten). Deel 4 zelfstandig. |
## Leerdoelen
Na deze les kunnen studenten:
1. De Supabase JavaScript client installeren en configureren
2. Environment variables gebruiken voor API keys
3. Data ophalen via Supabase queries (select met relaties, eq, single)
4. Het verschil uitleggen tussen Server en Client Components
5. Een formulier bouwen dat data INSERT in Supabase
---
## Lesplanning
### 09:0009:15 | Welkom & Intro (15 min)
📌 Slide 1, 2, 3
**Doel:** Studenten welkom heten, plan uitleggen, en PDF uitdelen.
**Wat te zeggen:**
- "Vorige week hebben we een werkend polling app gebouwd met in-memory data."
- "Vandaag koppelen we Supabase: onze database-as-a-service."
- "Jullie krijgen een PDF met de volledige lesopdracht. We lopen Deel 1 t/m 3 samen door. Deel 4 doen jullie zelfstandig."
- "In de PDF staan grijze blokken die je kunt copy-pasten, en TODO-blokken die je zelf moet invullen."
**Check vooraf:**
- Iedereen heeft Supabase account met polls en options tabellen
- Iedereen heeft QuickPoll project lokaal runnen op localhost:3000
- Deel de Lesopdracht PDF uit (digitaal)
---
### 09:1509:45 | KLASSIKAAL: PDF Deel 1 — Setup (30 min)
📌 Slide 4 + PDF Deel 1
**Doel:** Samen de setup doorlopen. Iedereen heeft aan het einde: supabase client geïnstalleerd, .env.local, supabase.ts, en types.
**Toon Slide 4** — Van Array naar Database. Leg uit:
- "Tot nu toe sloegen we data op in een array. Dat verdwijnt bij restart."
- "Supabase geeft ons een echte PostgreSQL database in de cloud."
- Toon links de oude array, rechts de database structuur.
**Open de PDF bij Deel 1 en loop stap voor stap door:**
#### Stap 1.1 — npm install
- "Open je terminal, voer uit: `npm install @supabase/supabase-js`"
- Wacht tot iedereen klaar is
- "Herstart je dev server!"
#### Stap 1.2 — .env.local
- "Open Supabase dashboard → Settings → API"
- "Kopieer je Project URL en anon key"
- "Maak `.env.local` aan in de root van je project"
- Laat ze het invullen, loop rond en check
- **Let op:** herstart dev server na aanmaken .env.local!
#### Stap 1.3 — lib/supabase.ts
- "Dit is onze client — zo praat je app met Supabase"
- Laat ze de code copy-pasten uit de PDF
- Leg uit: `createClient` maakt de verbinding, `process.env.NEXT_PUBLIC_...` leest de env vars
#### Stap 1.4 — types/index.ts
- "Dit zijn de TypeScript types die matchen met onze database tabellen"
- Laat ze copy-pasten
- Wijs op: `id: number`, `options: Option[]` (de relatie)
#### Stap 1.5 — vote_option SQL functie aanmaken
- "Voordat we kunnen stemmen, hebben we een PostgreSQL functie nodig in Supabase."
- "Open Supabase dashboard → SQL Editor"
- Laat ze deze SQL uitvoeren (staat in PDF):
```sql
create or replace function public.vote_option(option_id bigint)
returns void
language sql
security definer
as $$
update public.options
set votes = votes + 1
where id = option_id;
$$;
```
- **Leg uit:** "Dit is een database functie. We roepen 'm straks aan met `supabase.rpc('vote_option', { option_id })`. Een RPC = Remote Procedure Call — je voert PostgreSQL code uit vanuit je app."
- **Waarom?** "We hadden ook een gewone UPDATE kunnen doen, maar met een functie hou je de logica in de database. Volgende les met Auth gaan we deze functie uitbreiden."
- **Let op:** "Zonder deze functie krijg je later een PGRST202 error bij het stemmen — 'Could not find the function public.vote_option'."
**Check:** "Heeft iedereen de setup af? Geen errors? Handen omhoog als je klaar bent."
---
### 09:4510:00 | KLASSIKAAL: PDF Deel 2 — Supabase Queries (15 min)
📌 Slide 5 + PDF Deel 2
**Doel:** Studenten begrijpen de queries en vullen de TODO-blokken in lib/data.ts in.
**Toon Slide 5** — Supabase Queries. Leg de vier operaties uit:
1. **SELECT alles:** `.from("polls").select("*, options(*)")` — de `*` haalt alles op, `options(*)` volgt de relatie
2. **SELECT een:** `.eq("id", 5).single()` — filter + verwacht 1 resultaat
3. **INSERT:** `.insert({ question }).select().single()` — maak nieuw record, krijg het terug
4. **RPC:** `.rpc("vote_option", { option_id })` — roep een database function aan
**Tip:** Schrijf deze vier queries op het whiteboard. Studenten kijken hier de rest van de les naar.
**Open de PDF bij Deel 2 — Stap 2.1:**
- "Vervang de inhoud van `lib/data.ts`. De imports staan er al."
- "Nu de TODO-blokken. Laten we de eerste samen doen."
**getPolls() — doe samen voor:**
```typescript
const { data, error } = await supabase
.from("polls")
.select("*, options(*)");
if (error) {
console.error(error);
return [];
}
return data || [];
```
- "Zie je? `.from("polls")` kiest de tabel, `.select("*, options(*)")` haalt alles op inclusief de relatie."
**getPollById() — laat ze zelf proberen (2 min), loop dan door:**
```typescript
const { data, error } = await supabase
.from("polls")
.select("*, options(*)")
.eq("id", id)
.single();
if (error) return null;
return data;
```
- "`.eq("id", id)` filtert op 1 specifieke poll. `.single()` zegt: ik verwacht 1 resultaat."
**votePoll() — laat ze zelf proberen (2 min), loop dan door:**
```typescript
const { error } = await supabase
.rpc("vote_option", { option_id: optionId });
if (error) {
console.error(error);
return false;
}
return true;
```
- "`.rpc()` roept een PostgreSQL function aan die we eerder hebben aangemaakt."
**Check:** "Heeft iedereen alle drie de functies ingevuld?"
---
### 10:0010:15 | KLASSIKAAL: PDF Deel 3 — Componenten (15 min)
📌 Slide 6 + PDF Deel 3
**Doel:** Studenten copy-pasten de vier componenten uit de PDF en testen de app.
**Toon Slide 6** — Server vs Client. Leg kort uit:
- "Server Components: async, kunnen `await getPolls()` doen. Draaien op de server."
- "Client Components: `'use client'` bovenaan, kunnen `useState`/`onClick` gebruiken. Draaien in de browser."
- "Vuistregel: Server haalt data, Client maakt het interactief."
**Open de PDF bij Deel 3 en loop door:**
#### Stap 3.1 — app/page.tsx
- "Dit is de homepage. Let op: `async function` en `await getPolls()` — dit is een Server Component."
- "Copy-paste uit de PDF."
- "De `<Link href="/create">` link werkt straks na Deel 4."
#### Stap 3.2 — components/PollItem.tsx
- "Dit is een Client Component — zie `'use client'` bovenaan."
- "Het berekent percentages en toont bars. Copy-paste."
#### Stap 3.3 — components/VoteForm.tsx
- "Nog een Client Component. Hier wordt `votePoll()` aangeroepen — de functie die jullie net geschreven hebben!"
- "Copy-paste."
#### Stap 3.4 — app/poll/[id]/page.tsx
- "De detailpagina. Weer een Server Component met `await getPollById()`."
- "Copy-paste."
**Test samen:**
- "Open http://localhost:3000 — zien jullie polls?"
- "Klik op een poll — kun je stemmen?"
- "Check in Supabase dashboard: stijgt het aantal votes?"
**Troubleshooting als het niet werkt:**
- Lege pagina → RLS SELECT policy mist
- `Cannot find module` → check import paths
- Stemmen werkt niet → vote_option RPC functie mist
---
### 10:1510:30 | Pauze (15 min)
📌 Slide 7
**Zeg voor de pauze:** "Na de pauze gaan jullie zelfstandig Deel 4 doen: de /create pagina. De volledige UI staat in de PDF — jullie schrijven alleen de INSERT query."
---
### 10:3010:45 | Uitleg INSERT + start Deel 4 (15 min)
📌 Slide 8 + PDF Deel 4 intro
**Doel:** INSERT concept uitleggen voordat ze zelfstandig aan de slag gaan.
**Wat te zeggen:**
- "Het formulier staat compleet in de PDF (Stap 4.3). Jullie hoeven alleen de handleSubmit functie in te vullen."
- "Maar eerst: Stap 4.1 — de RLS policy. Zonder INSERT policy blokkeert Supabase je."
**Loop door Stap 4.1 (RLS policy):**
- "Open Supabase dashboard → SQL Editor"
- "Voer de twee CREATE POLICY statements uit de PDF uit"
- "Dit is tijdelijk — volgende les beperken we dit met Auth"
**Leg Stap 4.2 (INSERT theorie) uit:**
- "Er zijn twee INSERT stappen:"
1. Insert de poll: `.from("polls").insert({ question }).select().single()` → je krijgt de poll met id terug
2. Insert de options: `.from("options").insert(options.map(...))` → gebruik het `poll.id` van stap 1
- Toon op whiteboard:
```
1. INSERT poll → { id: 5, question: "..." }
2. INSERT options → [{ poll_id: 5, text: "A" }, { poll_id: 5, text: "B" }]
3. router.push("/") → terug naar homepage
```
**Zeg:** "Nu zijn jullie aan de beurt. Stap 4.3 in de PDF: copy-paste het hele bestand, en vul het TODO-blok in. Ik loop rond."
---
### 10:4511:30 | ZELFSTANDIG: PDF Deel 4 — /create pagina (45 min)
📌 Studenten werken met PDF Stap 4.3
**Wat ze doen:**
1. `app/create/page.tsx` aanmaken met de code uit de PDF (Stap 4.3)
2. handleSubmit TODO-blok invullen (de INSERT logica)
3. Testen: poll aanmaken → verschijnt op homepage
**Jouw rol — loop rond en help:**
Typische problemen:
- RLS INSERT policy vergeten → "new row violates row-level security" → Stap 4.1 niet gedaan
- `options` niet gekoppeld aan poll → `poll_id` vergeten in de insert
- `router.push` werkt niet → check `import { useRouter } from "next/navigation"` (niet `"next/router"`)
- Form submit herlaadt pagina → `e.preventDefault()` check
- `poll` is undefined na insert → `.select().single()` vergeten
**Check-in na 15 min:**
- "Wie heeft de RLS policy al toegevoegd?"
- "Wie kan al een poll aanmaken?"
**Check-in na 30 min:**
- "Wie heeft een werkende /create pagina?"
- Help studenten die vastlopen
**Snelle studenten:**
- Validatie toevoegen (vraag niet leeg, min 2 opties)
- Styling verbeteren met Tailwind
- Delete functionaliteit bouwen
---
### 11:3012:00 | Vragen + Huiswerk (30 min)
📌 Slide 9, 10
**Vragen beantwoorden:**
- Open ronde: waar liepen jullie tegenaan?
- Concepten herhalen die onduidelijk waren
- Eventueel: laat een werkende /create pagina zien van een student
**Huiswerk bespreken (Slide 9):**
Verplicht:
- /create pagina afmaken (als niet klaar)
- Validatie toevoegen (vraag niet leeg, min 2 opties)
Extra:
- Delete functionaliteit
- SQL queries direct in Supabase testen
- Realtime subscriptions uittesten
- Styling verbeteren
**Vooruitblik (Slide 10):**
- "Volgende week: Supabase Auth"
- "Inloggen, registreren, en bepalen wie wat mag doen"
---
## Troubleshooting Overzicht
| Probleem | Oorzaak | Oplossing |
|----------|---------|-----------|
| `NEXT_PUBLIC_SUPABASE_URL is undefined` | .env.local niet geladen | Dev server herstarten |
| Lege pagina, geen polls | RLS policy mist | SELECT policy toevoegen in Supabase |
| "new row violates row-level security" | INSERT policy mist | INSERT policy toevoegen |
| `Cannot find module '@/types'` | Import path fout | Check tsconfig.json paths |
| `PGRST202 Could not find the function public.vote_option` | vote_option SQL functie mist | Stap 1.5 uitvoeren in SQL Editor |
| Options verschijnen niet bij poll | Foreign key mismatch | Check poll_id in options tabel |
| Stemmen werkt niet | RPC functie mist | vote_option function aanmaken in SQL editor |
| Form submit herlaadt pagina | preventDefault mist | `e.preventDefault()` in handleSubmit |
| poll is undefined na insert | .select().single() mist | Toevoegen aan insert query |
---
## Voorbereiding Checklist
- [ ] Eigen QuickPoll project werkt lokaal
- [ ] Supabase project met polls + options tabellen
- [ ] vote_option RPC functie aangemaakt
- [ ] SELECT RLS policies staan aan
- [ ] Lesopdracht PDF gedeeld met studenten (digitaal)
- [ ] Whiteboard/marker beschikbaar voor queries

View File

@@ -0,0 +1,346 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 6 0 R /F5 9 0 R /F6 19 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
6 0 obj
<<
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
10 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 33 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 34 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 35 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/Contents 36 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
19 0 obj
<<
/BaseFont /ZapfDingbats /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
20 0 obj
<<
/Contents 37 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
21 0 obj
<<
/Contents 38 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 24 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
22 0 obj
<<
/PageMode /UseNone /Pages 24 0 R /Type /Catalog
>>
endobj
23 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260407143958+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260407143958+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
24 0 obj
<<
/Count 14 /Kids [ 4 0 R 7 0 R 8 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R
17 0 R 18 0 R 20 0 R 21 0 R ] /Type /Pages
>>
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 952
>>
stream
Gatm:9iKe#&A@sBCi?#<#YVI>WA?]aThlS0Rc;a'(LrB]`3C?'T``'D/(lXP#/c#QbAqd6]C5.9-59^4;Yq=b!5=2,GX?s:#k*:i`.RO\^4jk\Trk6=Lp*2_L%5KaJ\Z]Zj@'<q3esPaTpf%?FYF3j&s"d!U<2]RXN]b$gaC6E$6?td7R&Q0ces.)eZ8JZ-o/;@/:aKO\Bqh0dTM#!i=*Sa0](ZAPtqUA':_TcLPt>;A&m;kcN6lBiR6hF!E%W7?@_#n=;Z:K6"[AU7YG`WCp@7/(!$R[/rMKaUA2Ij&8Z^.Jq-bYo0<dP&5mW\Lk3-:q%S"U],3R3XG7qf80GSU_0)![=[)hY(Ie!&Gna7t[4\6/BVdd-gKTD@#U'I[q(N5W3eug?Xc/`Y.1M)@Z>DUeVI1o%D?0<N#G]&FRR<gA1i-i\<?7@o:_Zp98oR3M#`HP6kAuut^lPcp_k48T>U8%u7lBK;U(#J_C$7+=!D9^kB6Q3[l=bb1>$%\0OcCO%a*dD859?t\P27fU$<a(H#OXb0hbpIg.r)XWA=0dFV[q8lL/=Yl9qCM=\oK@NK/"f5O%UY.ck7Br4B2%7^T1s<D;=1E]%VAQD.jD@Z/8Qb#].kO,$s`/j>M0/\p7/=3n$*:K#>+H;6?ONVJ.+=%1TcoE-.,G:C:Cr)c.K&pCt#*liE7)N5Ecu9=W6(q-<)A#CAh8D!#M=l#]qkI,E#6n7Ea+4FtU8NT\'6/Ak5*@*>R;l+#'=3W8.9SiK1g!t'4T^J>joi1H3C2W_gX\FPIjY%R/X\?^j>oaQ/T0mL!B^q5SWH$oeO)8fuuA2,P&2cpLg1LN?/*-Oe4[h>Qe1W24()Q)9uX!pp`IC;7$ZYq62SYA_qfXL-r045$/P%pBCkud++ISi:hbG]2rZOIF>Dj+1g*UcR'_ZFsukPUOhm[f^=U":[0$TS.eIO$@u~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1229
>>
stream
Gau0BD0+\p&H9tYf\@ip;iKfsCbk63\`VU>BjM:tQRNqE(aTH(aX7IZ]:8`m8hpm0K>Y54?n;gpXe+?sSm<SFeJFWhS-#5ST_TW0EhIUaaKn"mFl9?&qcH2nbJ:B7aGCJ8r0E&2+B&*dA'51KQKNn^L:/$]?q%6tJ@MX/T4m>R?E0$)^bJ@CCBs,h'9W?16^Pks#5rk.;o0Y'&k4u19_Ju-Tf7PZ>E.`JI5g,)4`dOkCbuElV1A],CkWqHqRR0h.063Zj<QR4]+Sb,/-LCABu)gQ,u,(>"E\8o*o`^V`Z#'.GJN_U33#Bf_!ql(b=naJ69l0G;MGjX%)scDf/"=@YBZ3q-MW84&sV4B/"ACFgD)F=p+"'9d`$q5otrF?bD5t`'a8b2oO*)I0ed9oqWERH'rl,DZ6fjI^,>(S%Gi[gG9JfJ,83/^;AH>`!;r'7dbcTo)O%g#Qqjk(VHZl@I=7,F8Rt*r&!1Yt'O:VNa(#F=92gCUK!><E>OX-T$D^L_<rXkTb;CJbaGH8tENgCX*sVV>Y>`gBN6S?[9M&!r*".XZ$)s"D5<p`L^1M)qbp?H#6\bI+(f.sfBs8NQ\jb9,8pH;Y`KS_K*AD)oj!+VWF,eo#ImNW4`:dL/9#Pnj'k0C#$7`?5MQA@"r2lc"SGgtW>htoncJK)B2chB)-f),Fc%jU7=NW152k(IE]5K6P[S"PD0fSXg"Di7$EcH@Zq!IG:A$[75c5KXPWA18<<61Tcg0F;K&;:OYZIgcWW;f0:8/G#)V)sl-TrVAj:o0D(4QKV'b[cg"h<",NS6VUhE.L=("7GF71VG$7XjgD:lQrS%K%@Y9jVFkBMBYfo;H<rLT(BO>9'GcD03U-g,cV4"EJ*bGU_>fDUuR4`PR^DDY,Lgn@R74Dmn=&G&T.bJiQ]@sn`K%,2F]ag_75<3ROq@i_=9=BrVBE,7CpZ2WA5fGDHb[N*?Y4Kh7H;d+"TSI&!&1ScO'>?GH[Zdmo*7uBXkL_2j3'/,?9T(BJ)0*VWo_n@2$gKF1'38jqLHOWqC/)*l_<N&m(.pnm6*XTU*9>Hiu):_1KLfWRo!k*"t#EJmYsKNiG"\B'GD5UMV>"B-`h.X)=D"U"]$;_a5WD!L>fAH)<aeD7DD]s89K<q7':bbCGI%s!sm=j^_3(:6LCgZ-9a(q+b#K9[7,OqUoQ>,5)BVC]&gZ+OIgi_!R*"OqKnq;EQ7A%3*gr"!`SM-i~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 746
>>
stream
Gasal;,<]I&:VB$n^ZCsg31X@a$G]%@^J>K`9QmBR$\%Hh",F"?-orQs*GQB'0X$2Q7uinB(h^4k2UFJinZOt!f%(GIH06,P&'Q;"\;C.S;cl*]_;gt(<0\)0b%U3j"Ro]p=KT?a':_rZ36f,_-K+YVISDhrqqK#"e;nFC;Oh_'IfE<Q:l"3-r$gh+HSHF8lHOpB'6)%DH'Y0JgthE5Ut!H8u0<8_c]EC(WdZ=T"WkDYBJJ,aG%/r9;-ES6(@.!QZ]:>L]OIWHf'6C)NCg_YL?VmcL#q3MU6F::r/kcEr?iEp>TW$-?pB43-;&qJ5*)&Oi#/;I=l4M+r^aS:SA)MHelZe8`kKB11.<Q,C5:/9Tu\3QeJ=<(*]NFaAS^HdY5otI9G$!=g_J#:02XD\:a3nD-#h$1.Nf%WTq=gEd.<1<&[tu9;B^dh1\Zo,79%5>7A,uZ:<%6(NR*QMY(j3%*%=E5aj0@lfk[X@$tlWL2+*W)'&;B4npquLE!P#gBnJ\=RH+G4<Et+4VX).G[2Cl>Wh4RkH.o(J@f+Ur9ei=gYNsuDgIq#>uJ'CL8hSj2%[XMWtad\f^BJ7AuuGHAXC]fPP4El^n7;Yr9[]IJ8s5Q7-A3^EiXol.>1nWk6:"]1f#`9M<B1-XmL5,Ec/9hYBA(gK<+L0JD;RFVA$2\O$+P9H#YpaX["dXCfq`W^>dW;^(<V@B`[:\+?P>.=.BQWV1kf#UEcK:]QY=!0_u%8a\rN~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1400
>>
stream
Gb!SlD,91]&H88.1$+Tba2[FhVc?_og>968*X>QW1tB]f-HUjLL\7*O"T8#>Y&0=u65"UH6j\a7h-&(Jo?9<<i*b#lS+h"/E70O0NuYY3r@r8PH-h$O5P.%h*Ssm&%LZ_njq.A4j)GXZ3SJ(?i.R[4b`-EtG(@Q7rW,%Dcoq2^S,S@O38bViqY`QZVhc3M4ZH/8!a1[[@,O!f>ZC<*R$7m.'hX&=+nRF]J#!2C8?\qCipKE8CS#gQ?#s@71c]Rs!95ujo=nSm"KYr\Qm?X-@/T;_lSDQgL><Gqb#I&1[C<JMMR#pR;I&'dcJTD1W(%%OZmlV630]]<;BYPj)9rT"J4.Oj;)7oL>XEjXXP,e=`rN@2#R#-T5%t.k2Xm%;`7r<0f?nYChKWF#,;Qo;W+6AJj#(,W'Q\gR@3cNqYuF:Yg*'dhYCs7Ee(^4nhb`/Tq#gtBO\Z:.TW`^I2a[a;4sIF76K'/b`<Zp4r=>eJ@$h[1=.$n,+M19U9]S-4Q=9dC%Z0b4fo@"f,fYn2#"2-!lV<&PfCPUqb_OLb('f""H1:T$`8tqXl!H+7=[^<GNBe\X*#ZNW9i5EECQ)PT[QuHVo0Oq:KsE;THPjq8"p_6a/ff=VcnH44@"23ffn8[9?+$&0fbcm\Rnr.WK$',8aredVXd.+plNEI_UE!Bg_n'C9-:nCJ0aMj6!QBGALtqK,ie[`j^luAX:&(W;4Xh1m$KEU3PpgZTc+aKr7"m&:,]%m-?hF'\J(1+*L"l!&Gi!2_;@oND7"G`Q8<3kU@Gle?D8a.%HZV-k#Ns5,@s:lN6+dPThZ+5Z0^qDTk'4U7a-l=K[N?Fg\m<di4h.FJ'p.-m9hlM#;n1-OPc@#$CR=H4nhdC<\]6D+EjF%GQ8#NgAr.-[3+T)PCId9>>a<"1V7&[WmXg"4@/M(m9hNV`<O?f=VKFYuNWt9G[s!B;OrT^lO?F5R]IYgKjss8K&lg6!B\6NP;-%CgAZrMgg';hGXl]0H3rXV+lasU5>ISb(!-L;(pY4/9A+rWSaN<Gtk`LmE;b/uaL`:ZD>=7]J`Ma#e2&i9;:/Kl/N3[71<H6UJq1kueD/U(5Oo80\Ba?BTR#8/a.r;a$RN(l!18XNWN#uN9QU\Vi)]2nFOVf+O+dZ/p%0=Mi0G^(NZ&E-*U\,k.a<qjo11$EuXSOqKVi&od=B6;66>D\k23RMqmhXU0JU*mf*sBXCnJ;k/RmNfdUSZ[L.%G!4oeT=/"RZh_D8/ub=f,P-s1>D%1G%b+R,iVX%<n<qV`0aE#nT=IWSF![37k2$0Ys9OIG[;C7WE+A<X9ud`f0obI*kb7k:$^nLAOo/1b$O`,<;C'd^T"hrVg<K36<))Srj:)WuXemY$9(pjUr.pb]!M]nZ`1e^B!-67K*~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 285
>>
stream
GapWf9hWDY&A@g>]]89#G1&9+-UC]C]ne]:-+:/6V+n74K<W,Il/FHP%.=ja:s.-rG8'K0E;)[A%<hg&WePl@ZR\cSr@<k.`bJ.[RmrMt<d[67o%rT=JMNM@A>$:]KuNsq8ESGR\Q$C>;Bco_9(Z/hHQ0Yj1%C-mEQUBS]KG+0i/)_IKq*J(Ekj>HJhBeI"qDSmNl'_eiN`qRFK9Y)i/VccfQeS$a@V&3X]m'cT\ig.M/coC)(CU+Jip.0bfPA1Kb;d'/DUi_PI`*_(HuA+quMG%;mQ~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1320
>>
stream
Gau0CD0+]+%0"PUd.$.e]@a=lN8^4#kH6JiBdQOnVI)q[/@GOcCp.cj%V.?mpM(MgMZ\t-84/.)b=[(+Xn2l'D>GsX*@(V,7aV""!HV[+"kIh-0Y3"27ZB&1[b9oUN]1n^%/RZO_ZntJjQO#3H>^LP"/k1g3s-:h5tCs+S2EhbgG:WXaFjr(0ql@c_>4`uL<^>oMBiiaV@$6MZB3O]!MdspHE0@06(_XP=D&]]CXW`Fl<4A.#&>LF/2PTF%$I(]4sQ/#%Rm2OY/pBkk6Uf+;4!@MeBgO8?k4^LkmTA>.p*&p@d:aI,)["DM;8eYLb`&=d>3P@]^1qCnB@/@lhH4kDF8ieh(f#T1N**0m5r9CTJ478Fo/t\XHi;n3\"LRfXaHGJ9%!HnO3oZ*OZ4@!'s[2=GM5>rmUB$FAO0U*7=nMU2=]c#E5=po>`]?4=mmC:rNiOZq7=>k+'I!&ld2"/U4(k\qVkbg[Y$OFo6eC!iW8OrmGb3Y\5uuYMJrb:gGMM.G2B6n+o2f-S]Re5"L>;DiN_n9Qo!LAEud0^jdep&HtNOG_-g5>7nn9XC*MH!mMfjEAVCopb^kIGKmDKm(\S/qouhhQ\r5KpIDZ+NP?r:[OSaDP./SXHX]GYQUWgM.5*(s-C6D<dQg5FKhio/><7OLCO?.Y4+lk3PtN5t._X($Es3nE>6hQH`T3_s6rh<m%f%R@LWao--&`lfLj>TLn-#q`$[Z'q0dM-\05Pi(=%f#!8jI!8c*bLap%0?62J;p,'W&V5OHHM5Ps#_!#g&lOa8/b'@5L?8PPd[KCX13qA5:'Jiq08($7&NADLRMP8ffKH:>u=lYMFrDTMcgH\qe_]6fTdm@M.Cf'AULe*Sa%b/CkCp,;LhkRe$Vd3FI-P"100;mcun2A.$=JGXaQ"ko6Y-X,Zu0p$gLlD4AI,5$f*8:)'j)or`47d!@Pk`u!coeN(aI1jg%*qJuIMa]d5&QnE`&Z?Oo2J##_QhQHs:IEVQ94Z[b7N$D'F>:bN"1R%oG'*5n%bIe#ip\i@Ulgo)I43>CC9Bn>I[nA(9lNdI7`GZ*P.Ke`)RmVf(3nA_2h_RWc4"=<m)<OqrNG):V;edO]T"^I6b>6Te0#ll7f8!!5%V\+"-7Id''DPI5Y$hha=#*B\28t7=2'6_+rqi=OEiWjGF_^-mNZ2P;`n$>f8?a!R#4(A"6j^im^P"p:MO<h?60q,NMZE):eZkAUaX,33C;2:J1O,f>3Y0r_-+*!FQiCjdRHP_S&u<mu*7Cp0(-@efNr*YD.7]pKPGfIF?CM4kniWL4(NXdBeZMh:j'V9>gdM~>endstream
endobj
31 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 729
>>
stream
Gb!#[?&tF>'Rf.GgmBusP5tkn"C&=V-JcCPRVY@fjF\ul8f&=*%(_2_hg=mg&XtWF/q*r`;/t`gq_?,A"W]8-IeFG*3u>8N$If&7"?KM;i'+qX=8AuFm5Ia,2/U7.0HAaq[7qUliGI,N/U_$E+o_86W&Lq[<85nmedr<gb`ift<Bl5hNG@E-(f1kKK=ekgLc_@WqH'M%)n;H.!7bjrgAM"@o`lY=<(Ah<*u$L2[gud.Jc"a]pq2/#>(@]AH2cVmR+'/5cY$cWN6^SZm2B<AFVDoVa0EcC)=]Ob'/,hs-f*&%8Iq=C&!ZNqC4_Lg&i*gG"-Q;1rlHJ0i>2'oR@<UKG2]EY25@TMNtM,kW]H))s4"Ie_.4]eUd&^HgWk)#dV!f:/#V/l)[4c+M4618/9K%6<k"Fh@FJd9@9U(i\aM[T@E`%#Ot=_3cVhm'3@-Okk%2I2^CWCA,id9`e]di@M(qN(M<'OrH@'8QIK9b/V\tm/%o#ncZ$!_=W:M2B=9*F?7B)Ig`dCA?;%Tu(o#X!@[@#F@\7!7fSt$ko-jbek"t@2AO(s.h?6"0l;en!e0ltJMQ;JdfA!iu2qQfrWL$Gb*r'/O\hNlnce?BUi715,[l2IZDlp[("\X&\<4I%1#5u],67/OSSCY;E'FkS=8Hcl;tX.&Z5?=0fen-+tumT/68eMH0@#Ud;$k%3n92]:UZo6L8F7X,l3p^7%b52lag+-(qO1&~>endstream
endobj
32 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1335
>>
stream
Gau0CgN):C&:N^l3dt5:#suMa*E">ij4%+Hm5eB"0:![aWPN)?OYS_u9i:h8H?A+Hgq4s;`<auY5,Y<jkMUqZ0/re#,Co)'e8JW3)_`7agU*G(quV?V/,rZ*_Qr"%gV$)&KhgC1h-R]`cd?UaFm+_kgT-iDaJZWY6]NAud/^YHL$4s*ep#@O\?mp*CG\*1a&*HW'O'&eM)F/01:"<6"jd#ZEr$Y;Pm1R&$H"5.IP_oAU=V#fNHLa1@,.>>j0fANN4*LUmAC$:%G*+T&2L+kJg6]S.7sb9@hbW@i>SWlBaTnU63]+gS2n'MM:#TKL7VCq?Nmmr-'j)d`l&!kN!YA(+TY(MVo%%*9>8t%No5aY!>f^-(fFfjW;K/BcR=6'Ss;do=l6o1YNoHe@!hkLc\gP1Q\sU74.U7&[TD0g!O](U2nLZR997Ih%j]$ba"6^L^74LjQ>ecYpa($CjnhEF&H$@r5@gPuX,K9@9op:U5Sd<_)IWYV)+SWhOnnk5C2(C;/T\Z`rOj9C;*A<UmBlnZBHE'hCHY<eXdS>66DGMV+/,de3uKCQjsuL<'pM@]\Q/UFs8Bi=m)>i?I*J;e3+cm'M]s97AL,Tj6fALjrCm8uF[$#$*mejJ`[p%Wnd?kq?h3Lf(bAVN=^r3soVtt_r0YD,R2:Ju39"GOT1:5[&<.'<^sl'QeFP]6cTrX@)N@Q+X[2s5Du9?I/h;+:V.Ft/cn8EMomKf^@!Z6<!*1[V@QW6d*ttEdhkC!/_C!Is)n@NVSuZA5^Gra$):trF:1l%RVc(=6q6u#\T.0Gu76%g(po8*CM(!+Ua13Tji("E)_o*7\OS1DpAi4'[+.6.A\=Te6\+V3-&H@??B:Xd^#T`EtFk/%9_,?cQ)Xeh301LU8Z%UDnU28F#ku<`l&/_6IdtF1LCf&'XH#R6_S\E$o["(@]bfr/Zij"pp,teU5<R2^Mi/3L\$jQ<4KUt?D=q/-o`[Mp3UIf;:VJ)tOZ=\?,j;Q>bGB6>j%?PQZ!XqWE4JV80-p!C[s+W.)glJE<\=V5SmqV\4qSP3o4i9aT6R;,RG@:-E-^S]gPK(Y*J/e)BpO63n3mP9YIP[=u/4o+5SYPk)*^%pc2F/SZO!A[,XWKgXbRp+L4'2C3GEc(WX+(hOett5-:eWT30_doR1#;YN.BiTg0m7jI@^gK/II0)?IJUP@]MVugQ0nd_;)Hn2cbX>T\4jliG/p5`SXNa#qUQ(>AJd^-<u:4k:RGk5D0<OgJ9+`#eA2@]*>Qm!o4ngBP:Xl;EI$@F'$(Yd\C!BZi$:(CQ8Hlq;4]1OrHDLC)](oD'T.1CX0$,fb($1\poO~>endstream
endobj
33 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 630
>>
stream
GatU0d;GF-'Rf-p:A[F/@5%fkh6KY@QDmb>XXi*=02?IQ<j4$?,up4[T:N>dP"&ZR#fO9>n%Io:k*X$7%paZ4'ei>?N[UieF:-_LIAF@IP$3n^IE@Fq&g`"$""tcRM%X*lVb*Ds?BF(VIseXpLkS9\aLdE.35fE@#ro28qf,/9>m?bmBK1__et)61-./he)T>5t:hk*_UB%P28uSYQco9V$JFin$H:WO&@3S.@:s6/_!,TL6$8\j!lgaK/-I;%I2(cQT.^S;`f#r@]en(`+O_4261Af9PbBeEiAfbLMK90gn4/&WsRi`oXrl3Z2eMTZL^Q7mE#4hS.9M3mEhcRi&k6kqVkrkutdTJP0Nb[g_:.`r1FM94!oY1#haU5W&V>*qKrj'IA$K[4XWThF#na+0"3<Jd(nQ*o2=IgSi/a2H"s!\[)aejZkNLk<tdJ#D"_>Q$E=(V4.s&$9"6Z%!48(tb&CG3@UE>WU<A)cMTTsmbC0)+AH^C3E&+Rs.Ki[;`[`l1"te[GbmN(r,j^UEELIA,H5:A+-^@E'E,CJZEZ?b(&'-`ekpSd@O*,;;l/;6l^MCYUFH<ErqJRg4(qcNTq'i4c8FR'lj7I<Noi@OtZGT,fI#efVZo/sQ~>endstream
endobj
34 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1425
>>
stream
Gau0CD,91]&H88.0pb\p"A@<h*%+`:!QPq$*%BgGC7D6W,_NF[A,Fhm\-hc`46sAgn<S\W6#dUbmQCBj4HCj0g@XD#qfi-\kemBi%bq-c)S![)nB^%FDgZ9g1=ERtgTu2G\t(J64h!rIkVfbPX6XkXfj<G$=ho?lq#_?gBMZ@*`a;OL$`lIEGs?r4#K.1D54"=F$B:.'5XkfEEH[8f%eqjLh]!CKoQgoQ[o]6(jl8LfOEXBer++TYA\:b-r4uA:coTK\MAjo26kMm%KF*4q"(G6G\eRO`&7L<T@l).Sp2*52#r3cG(oB"5+q6k,$Sa;1YscrJn,_L&ic$(DK\oP*A%#8eKSHHo'iTFti"nD+F?EQDG"qk]$A(USY-LHS7#Gr0IW^&&6Pp&K/D/-f\NMnOAE\>%TX,'"dQU7R3n782>41P/j5m,NdNS^IE>Vk[(G=%:$orA+0Ba6k@LT]EYKL#;]Wo2#k,cEfC)DZpI#b/tP06us-\F]O4Du"F%\/>s3f8W[^e\ol5pE!3A$oHNTIoAlW\/09FoUP*4'm-=!++!-$9#)S5;u[LDB#\?[_&+5Ro_(9"FHa9GJ"FWBGkd$$OYk^I@b\S8oEfsnq'LR]U,SPgT,Y(XQ`G!X.oF]1Zg&K\i`1GO5\^,g$-m+co_TOp_K$/6jl7LWF8#OMFGeOWkis0U5srM1<K*a56);7dIN4Q:]1qd3]TD#"N-O+)%Lk>W:/j\O$em?/`Z&a_3W9qiKdib&b,C/iS(>L@2B]kH!T#J=a;Gi#C&<HB'HiO!1AG)ZO)A,2D52*NYLQW(Ld^r9Mgjq!Ekl=bCY-3+Oedk4N=Ymmqj4KJZp]cQOK-)N'uP;A,81?Go;n'o3R!j.8X$=0$K&tKP45(>lg*cm35a_gp5c99B"_#P6uNhDcHN25&^_(7HYUU'&RbV&-X1(TM]8d8UK=EA*SOQGN=`eo^hQX5c%"bp[BD`><tE:Q5hJX'cpRbfX6SQ_cF)tK[nm;iX.[U>BcqZ;PUoh1dB/$)6OSrBYs^(Mc1%(9T,MNO\l[a$/)Nqil$@W_uT$Lb"*lOFI)M1N<SblKN^6]1bpJ*nmLi!R\"stGtSG/'NJ+3Fu.pQ&,k*YYjZUKs2[dH!kCsL^1uY>5hdD$1j;OP)Ugl-`;T,)pGmV9T>.k@1995[NKnMH;(ntq)=C=rQ%Gel9&fstc![C).s7]8!:`)lpPKE!6IHalWpFS'(HT?ob2h?:#Nij"5L(X!27N'Cm^3r,SfYMR;r@UMQ>%/$.?^KsXS6F^7AeP;>G,i:TR#SAoi[+HK2Ln_<3,$m:!X53`JGl-jE923A4hP5!n8E_4BT9P6_D`ZlW>aEOM3N^*QO(k->?bP11t>kU\7Cteml-[)42,-H2YR7_1R,ISto,-pG0:Nb<"0,QAYr(rWA1fG7X~>endstream
endobj
35 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1337
>>
stream
Gau0C=``:f&:WeDFS([Q'F9@H"qn@fPLuj6AaS.>AmD;9[:$YY1,Au0'`WSc#=<qAJRbi#8^l;C-O*Q7D+)-+#N/Mk.b=kA2<\1NeFblp^;EsSn>fqB-Q=qVdAjS_\8:D!X3nR&3dRkd>KuGY$T?W6Y6Fk+V?OZ`W*VCJYdoWg_1cncr7%2PHBZG.dV+.rq"2:>8"<-s,LqrF'>'h<7Q&\sEd\Fai=-0:UIseg>H8T,nQd7LL,MP']fUl''0u#7oIY+(pOn,Yd#"o7$=Nt$n/TdXZD#]1UrV)IhKsf?Oe-]8VCF3fn.AUSF.]:lJ^4NoXp"Z.Z2(&u]UbaQk\q<bR:rk@<aZ+!0OtWb8M>c^4PTkO7bBBfcX=JM#17V)$p@ZAGk`#H$u&?W;;EN7O3gI8mM@a_J+hh$)p]&ZAAmRfhQJ_\d'OF!g>cUB[`;)0NZqN]j8D"hMqJ^Ro0FN^&TP>gG=UC5@Q/I2/e%j;nAKC$bVWf8Lc4fi-[tpD/pZd+$:.!QfC]j3%J4hd%t'RWbc$V!/pC$[[D<<B!>98`Ad?rmW<>4h(Pdrh]-H]'*F50^7ZrKD.j&srAUR_/5=BgQhMjA;8Y603`G87C/91_Dd.&Gci7B)7!@r%"P6;cZ7X(ZJn1fCAJJp6WVukqL?unMhojt+Y`U,W&MEabLZY"3%e*0,.FoO"bL_iBAB!,iX2]+MZ&`cXbG)*UQT#1@g/EHZ8-_.4D21Rn.kG\7Z6_7ZE.c&uplB\3._UgFj&aZQ@>D,kUgT5;u#qHbSUbCrfI_!^.kEAUk]#1f2:DSmLeP-^N]Bj:iDRrpD4g;U3+0mhNq;msDQk)Y*^:=lU/c*us-jW*H2c9as#&#))1qa9:Pg+b/.VhPE9+WtHp``%oRW`S-p15,G.AJ:t2LR`Y"NZe3'm]Cb^PE>M3faj<([&#m"(:X[OsJa@N06Gm'\G;bOUoKe6EL]ha#Nu-4=fG<QQu0o+H-bT@Pn!mbGFr7WKp0'*L&=L]3YNRQS$mBP"\Z%DUL[S`s:dA3[S\G*/mfpkN7Wj?Up(\C`Hk=QUhDW8cfZXrB?A<^SJ3ZQiM'o7"HiBPk:ZX5Ijnu73aoh7%Q`V?\QIU17lh\7!m)G1`^9O3\lM3qk`.E#$]mojI!keN[QKIVQf_cGoYAk%aFSAB7MslF8,QBF>EZFrpMQjDjZ.tD-FBbU>W7YH!5AbE+WYs-kH5r6]27k%dqXM)]Mp_@3ChN1E.mk<TSdEPISHPjK(!n*.o\,p_WE'8ls$PVFF'2RR^p%hSrW8F1WiUErHiI!mPb\=q.1,Y<eSTYuis1cQ46`Y'Pd`H:-Y@$q=4`~>endstream
endobj
36 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1211
>>
stream
Gb"/(;01/N&:WeDXGl192'GO?Kdo,^C\>LUWu8q_*k[R\>e/Xdab@/Yrc^"%["Kp;@h*_p(qq8^h!OOsI_3.s_,=W%ruZsqa"B2ED6P%"7prgM(:YIEX]rE9g%.;[NnNh]?$/rDCYITf=$<IMbtb`Xm<;tR5JO7e#B3g%]5?l<dt1SYK,OuiPPn[U"1+aDK6F:)0uEJ0g6q5/M.GL7fle@cSK!/tAWeoHpYdg<0ET_3PVi(SMus(S.5YECh",m/@..`WO?N<Q7pd#c&oe2/In@>8b2-r.\BhB=]C#Fqh3MKj8<S?G+nk1m7##A[)m+G6o]k=\T<$o!Bk:TLFoXuoA?_kfa2Dou'!J*nKN/OdL6:EB#goPa#bbn`-0mG&`e4I@=dut+HL4"DPY.%hOYT@;KXtJ'9F4@s2nMU8O]:fWgi=D@r^I&WTEZAmE3KR19t":3$-KTb[Rpg(OuP^@k&sjeGV*DY\bW'V8f<9&E53-lO"nQsjZ`O0-Tkjq.`R(>&UX1(;T7KQ_DH=VdkiqC*TKR\A+ZHtft8s^o/Af)TDnfk/+i"hl)u1#MFlLD3r*\@U+@W)1F1A@0k_9!c[i*h\6@l!(c:lRj`]otK%I\=/q@PCp`iLl(g]Kn17G^/72qD%4,IYlb`c)d+WHu?1@.=g>6em''8c[daE>$85Mm4/b-9her.*]]lmtZqMUaUg.]e9a11Kr?cP>rZTZUp@cgP).57L'$rS])[1$!m*_^f]a8^[/uHfA36Et\4F.0Z?MG]C#X-RsaX;auP+N(eGW937Z8'[nhDDO%TOYXDoF+fV3P33]Ji&[)UQA]XZ17a=BEK'6r2Q-MGc[e89i(@es>]4cEU2/dK"WLuP6Xc_lX<>+3XDk[SFAJ@p?Bh!HUPYr&]<qsUlEa67%Dh][35uHZ5JQ%+#E_X8IFc^BF+"=aLP-8\'meT+ALr[m2c'TV;j[&"BIWnr?$T@)*\J&>1WPj05E*h2_*O?$IKo1Ys<F!c*Obh)?b%k"7k_e&ILVR](+6*m#YA(ice3=6sWJ'c&9T[jVE1k@jFjX'p=7i/'m[l9?HS99+$Jj7[>ISdADlhdNd;oe`GP(V#&$04[/8rY`NdB/o6YN("niaN\^3B>Q)XgNiJ.09]TCfs0@\V_3@`=ukJP`a3S#Y,=$!\)LkO]`\Te95,<DeS-KG0"U':IZ2'OeokjeEP"T_8bJc]hr~>endstream
endobj
37 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1621
>>
stream
GauHKgMYb8&:O:S9`3tI0[9L6"e%?0S>9/+YuIA]*!@d3/5[V%oPuD$q>/Ah.>`(6;b[:^Ybh$Zbac"\fqAQr4dQ(/blD(0iU"tobD,Nr=K^W"GR/rldI!(Hm.>/4O6jr>^^CQ$nfuR-Z!mNQi-JcjU$4s'(m1SB5g580VU]]CcH(\K[W\AD%cQJ+@LCsPN3SV75DSb1`\3S6:\=#FmC<"^o"B7"+)NqJ36NSC(-MMB#gQb/Z[C"^eDMtA3*iCQ.Jn,\Ie3_[h:aG$];=>0g?k!n!b2(f0NA*F?oJ;fQDRd$*K$\@$*RJA'?jm9YY5oJcOKs=q1`TFIeU[hen2he*gegp/_Fs$[)q3:<!W,(7,]`DY[B6i+q[HrGaapp,n`[I_'Z6O\3A"JP`R$q8Fk4<1G8+!T-(l#$D3j6+C1]g!L!%Z!\\(N6-G'LJRXMI<*UeEPl:BjaEqt*J#(2L5h?]FJ"[jH/4kC:PiPNeTH_XH3A67WC=N^Y-`8XBJ/M@A/el8jQOp"=2Pjk`c6o-=R(c-oECh<'A[7[D;!+>g4ZT@FWB_eM*JQV_DRdcf1ngpG@9EoVB(/`_E!Oaor?nN5;'creH,c-a+,\8L76-nd$XG/7Dd'sXi(`CV_-_:sd5b$YWpZ=mY@G@hPu:!c:a?9FMAr!Rka?=$F*?C,LAuV$N<QL4FIC?5QUsC,6l/:HD;"3)_RAq$lTQ[RLB\#3nnWqXgBE.Io7CeYVqGR8@;?AWZJK(ZJPGe@%mSZLT"Rp8f.ms:n!:*&m'UZ?mZ1I.pG1R0*:0NPA(]JXF(88^F#HEIl?4u*[8J4P3snr-S9u$@EWNLI(:<d<!F;Ec(i6X1-rOB;]34`M2QBr,$qK5h_oEQEcT1[+,`a,/EL#O48>JS\Q--:A;i>;S0;ja!\;s2@0/#(1B<"h/ftE,$]fYmY%.Y.7kU)'7O-*7Kgul4FHp@CQr:T)WYD2Ku^-Wu6pDXQ`\:P=s[DY6Z[\p7OWHh)_,g_NY+CVfR(>fe:+D*8gOdl$S*FB7GW`^XsHWVRVAZB*[S6JP'&S\@=Z@I/f7t[j^+2Z%B8?r;%_h&cGe-TL!kgL)dRc-Ht[CbfP:#k.?<uaZaais>A6dZo6km[qJGYJr-PlrLG+0*>3;.>D]H$s`3ge_"4UA@J`12;WO^)U8P<At$)m^6uCX\desPI'Qg\YL/db%d*eGYD6bW.B0.-69/4[<Ohlo\jP4TKrmpU,hm(hQSK[6k>)Vj)**435q#7fL[Hj:Z)T?7*ndhK:3_LHb;1m<JaeiOu2s8kR`err$Ka1[Rt.mV=DPr=Qa;f<`*W?D=72Pc<J6tAoHN'oJk0M@.u?b9MJqq+7)Jk%8K;[e+I@k0U`=,$^>beX=n5A3qZODdi2,L<K^4sL8pp*'t_(Wc#\V;NErB@UHhrg)GE>El)W`G\Xn()"-4qh6(CIucMJecfDf5<\/<*k\@h\-BDlT>F%]BT)8FYhilDBM2+k[%c`8upL'MplYp.q/H"rba8;k^#^teapg"P*4dU>\+,qY6_DFE?AHaVXbB]NibBXnnX\Y:BJbDh=tM7/5+)j.Z56S6oa?jB-)"l\fUE7PT%^oScTE,6;*cCa_28,iSS4]Z\~>endstream
endobj
38 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1634
>>
stream
Gatm;gN)%,&:O:SD#G""j3RpR&Xg,jini*Y'5O*Sc#d>*Q6Ck\9gVmE:B/!8?:7\W9M)3D&d;e>loE!:YU&/d7EY(%$Li3?QWF:qKuk>Hhsk.G&O&l`*`#=D),_i@BhVnSN#`Od1&Bk96UNp.")tooojS8'm+FVEk9l'W3aj?aL7gfT&GGuGN.,)j@`@qo1RK_$Ec*+N_J<@TcS'e$W7qbP]J`#Y`'o[h2Xd-Br.Yt1V=L:TFi3fEMV5[g1JB@l_G8l-GZ^\AR,udKA*g&s,N:s+Z"5c'SE67Y%N1`D`IQd@m")4kWkfYUP6EKs.)??T8B,[PZ(eV2oLJO6n-tR*@7G2iRQkLMkN^:rD!>+`DSNl>S"-`DV"cB+9?u_i9e96_qIT]aKb>hE&nhU@=s=L():C#.KpZPUi(bq"b%2dH*,`^s[$o_]JdI\oboZ1A?&(h[o.+p8B)[/+lbhU,B.t>hBq7Vb\<Is?ZV7ka>YWQ<8@fq:/X]p5n,LJ>pKV`4\\"],)5.clMW)E7bIK'u@n8*(#Gnct_.Qro$[C\,8h:UH_bq>]?=rPi(&Z$u]Ja+CY28k#Qm*%X$<WT[WjP+M'[pp9KNnhK[(tN6i(kHREQRf!V4N5W?5Q4$c=[?fA^)!lj+g#>SaWg-T'Nm3)"5\@J-9Y?a":HB//d8Ra_nK(C;n4:H*LMYUR(q@964Gh'M@XN\m.MGkFsXjcg?65\E+TlG[KDnY[YUQndiWQrGgnO@I3A\l[,bMroHJujV!m@&s_SL:9,8Y$tr>7Dg#4=aY/Yp8na$@@5n&N,>-X^4p<SLSLAH*G`;r,NgYBWR.b92W4U<oM8qIBA(\HG?6kO8Oi]RF#!)1.OhIm.L60MXV*3mQX/l?[E3%8*Y.mud!%r\Foub:/6<rXdXh1#sdY.r<fsGPCXHQm01P35gR9GUC(T`mKgjp*_GIUI6FCH>K2D-quh;<68PreUl=,*jBQ^GBn$!bs`n(d"cSW9R[6Q0^SCYZL6L>uU_E<e2aQHR#*jd!;:I]KZ,X2CR`';.>@A20a27",;;Pbe"Ld<PSDq`6iQWRI*4bFD56DB99%nr1KNq*itpBN7_rkL6bi7>8)MjH'0DWi`IX1q4lWT!#;Y9DD@^\rfcWEd@c&U#^X2IN!G\9>6I;g&p*m'uPIR<Zj&]dP,9n'%,Xj5&&<eT9r`!^]'B9-A,:Jk6P)WI':I49tb!hRu<!&%=R"]CrZIp2BmiL./<PPjbUh!qUg:+C>[@m/9:t0)4j*r=3%8>dJdOXo9g)%):s"sB<sbRf@HQOcf"cV\gta#OJuaBpL9aYBuBtN9)R&q%]TkG`E+q/A1K!]NfMiP93E08h7]ud5S["4)]%$%^Z-<0^oPWDeIm5JR2#GmnS1<UmI@>9L3G]'[)-1j.&`iBFIj6aJ/#-q^?-KT5AD!BrN7mu+K>sd*.,frj'NtCB845C2m`DDeYRl&4mB0F=e%HKCth_;]RTqF`!KhZDF)a[NNkhWfB&7OST&rUFutF!P8uosL$_X_(<lKOSLHX*:LSV(S\j?3$@C.f'Fr_n?;HV&Y,=gOL)i0N;3+/qOpHCS_Nof2A)R5)Z$ibI7.Yk8.5\foB1.0W>$=#$*]3J\apUuD"4e'?F8~>endstream
endobj
xref
0 39
0000000000 65535 f
0000000061 00000 n
0000000143 00000 n
0000000250 00000 n
0000000362 00000 n
0000000567 00000 n
0000000672 00000 n
0000000787 00000 n
0000000992 00000 n
0000001197 00000 n
0000001307 00000 n
0000001513 00000 n
0000001719 00000 n
0000001925 00000 n
0000002131 00000 n
0000002337 00000 n
0000002543 00000 n
0000002749 00000 n
0000002955 00000 n
0000003161 00000 n
0000003245 00000 n
0000003451 00000 n
0000003657 00000 n
0000003727 00000 n
0000004008 00000 n
0000004161 00000 n
0000005204 00000 n
0000006525 00000 n
0000007362 00000 n
0000008854 00000 n
0000009230 00000 n
0000010642 00000 n
0000011462 00000 n
0000012889 00000 n
0000013610 00000 n
0000015127 00000 n
0000016556 00000 n
0000017859 00000 n
0000019572 00000 n
trailer
<<
/ID
[<ab4fa5324f02268490d94a51d4d35c59><ab4fa5324f02268490d94a51d4d35c59>]
% ReportLab generated PDF document -- digest (opensource)
/Info 23 0 R
/Root 22 0 R
/Size 39
>>
startxref
21298
%%EOF

View File

@@ -0,0 +1,145 @@
# Les 8 — Slide-overzicht
## Van In-Memory naar Supabase (10 slides)
---
## Slide-indeling
### Slide 1: Titelslide
**Titel:** Les 8 — Van In-Memory naar Supabase
**Ondertitel:** Koppelen van Supabase aan Next.js
---
### Slide 2: Terugblik vorige les
**Titel:** Terugblik — Waar waren we?
**Bullets:**
- Stemmen werkt lokaal (in-memory data)
- QuickPoll app: / en /poll/[id] pagina's
- VoteForm component → stemmen onmiddellijk
- Nu: alles naar een echte database!
---
### Slide 3: Planning vandaag
**Titel:** Planning — Les 8 (3 uur)
**Timeline:**
- 09:00-09:15 | Welkom & Intro (15 min)
- 09:15-09:45 | **KLASSIKAAL: Setup** — PDF Deel 1 (30 min)
- 09:45-10:00 | **KLASSIKAAL: Queries** — PDF Deel 2 (15 min)
- 10:00-10:15 | **KLASSIKAAL: Componenten** — PDF Deel 3 (15 min)
- 10:15-10:30 | Pauze (15 min)
- 10:30-10:45 | Uitleg INSERT + start Deel 4 (15 min)
- 10:45-11:30 | **ZELFSTANDIG: /create pagina** — PDF Deel 4 (45 min)
- 11:30-12:00 | Vragen + Huiswerk (30 min)
**Aanpak:** Deel 1-3 klassikaal met de PDF. Deel 4 zelfstandig.
---
### Slide 4: Van Array naar Database
**Titel:** Van In-Memory Array naar Supabase
**Links:** In-memory (OUD)
```javascript
const polls = [
{ question: "Favoriete taal?",
options: ["JS", "Python"],
votes: [10, 5]
}
];
```
**Rechts:** Supabase Database (NIEUW)
```
polls tabel
├─ id (1)
├─ question ("Favoriete taal?")
└─ options[] (relatie)
options tabel
├─ poll_id (1)
├─ text ("JS")
└─ votes (10)
```
---
### Slide 5: Supabase Queries
**Titel:** Supabase Queries
**Ondertitel:** Vier operaties die je nodig hebt
**Queries:**
- SELECT alles: `.from("polls").select("*, options(*)")`
- SELECT een: `.eq("id", 5).single()`
- INSERT: `.insert({ question }).select().single()`
- RPC: `.rpc("vote_option", { option_id })`
**Tekst:** Dit zijn de TODO blokken in de PDF!
---
### Slide 6: Server vs Client: Wie doet wat?
**Titel:** Server vs Client
**Ondertitel:** Wie doet wat?
**Twee kolommen:**
**SERVER Component:**
- async function
- await getPolls()
- Data fetching
- Direct naar DB
**CLIENT Component:**
- 'use client'
- useState, onClick
- Interactief: klik, type
- useEffect
**Zeg:** "Server haalt data, Client maakt het interactief."
---
### Slide 7: Pauze
**Titel:** Pauze
**Tekst:** Deel 1-3 klaar! Na de pauze: zelfstandig /create pagina bouwen.
---
### Slide 8: Zelf Doen — /create pagina
**Titel:** Zelf Doen — PDF Deel 4
**Ondertitel:** Het formulier staat in de PDF — jij schrijft de INSERT!
**Stappen:**
1. RLS INSERT policy toevoegen (Stap 4.1)
2. Copy-paste app/create/page.tsx (Stap 4.3)
3. handleSubmit TODO invullen (de INSERT logica)
4. Testen: poll aanmaken → verschijnt op homepage
---
### Slide 9: Huiswerk
**Titel:** Huiswerk
**Verplicht:**
- /create pagina afmaken
- Validatie toevoegen (vraag niet leeg, min 2 opties)
**Extra:**
- Delete functionaliteit
- SQL queries in Supabase testen
- Styling verbeteren
---
### Slide 10: Afsluiting
**Titel:** Tot volgende week!
**Tekst:**
- Volgende les: Supabase Auth
- Inloggen, registreren & bepalen wie wat mag

Binary file not shown.