diff --git a/Les08-Docenttekst.md b/Les08-Docenttekst.md new file mode 100644 index 0000000..ead0f6f --- /dev/null +++ b/Les08-Docenttekst.md @@ -0,0 +1,207 @@ +# 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 | +| **Lesmateriaal** | Lesopdracht PDF (studenten werken hier zelfstandig doorheen) | + +## 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 Components en Client Components +5. Een formulier bouwen dat data INSERT in Supabase + +--- + +## Aanpak + +Studenten krijgen een **Lesopdracht PDF** met alle component-code (volledige UI). Ze hoeven alleen de **Supabase queries** zelf te schrijven (gemarkeerd als TODO-blokken). De docent legt concepten uit met slides, doet een korte demo, en loopt daarna rond. + +--- + +## Lesplanning + +### 09:00–09:15 | Welkom & Uitleg aanpak (15 min) +πŸ“Œ Slide 1, 2, 3 + +**Wat te zeggen:** +- "Vorige week: werkende polling app met in-memory data." +- "Vandaag koppelen we Supabase: onze database-as-a-service." +- "Jullie werken vandaag **zelfstandig** met een PDF. Alle UI-code staat erin. Jullie schrijven de Supabase queries." +- "Ik leg eerst de concepten uit, dan gaan jullie aan de slag." + +**Check:** +- Iedereen heeft Supabase account met polls en options tabellen +- Iedereen heeft QuickPoll project lokaal draaiend +- Deel de Lesopdracht PDF uit (digitaal) + +--- + +### 09:15–09:45 | Uitleg concepten (30 min) +πŸ“Œ Slide 4, 5, 6 + +#### 09:15 | Slide 4: Van Array naar Database + +**Zeg:** +"Tot nu toe stond jullie data in een array. Dat werkt, maar is weg zodra je de server herstart. Supabase geeft ons een echte PostgreSQL database." + +Toon het verschil: +``` +// OUD: in-memory +const polls = [{ question: "...", votes: [0, 0] }] + +// NIEUW: Supabase +supabase.from("polls").select("*, options(*)") +``` + +#### 09:25 | Slide 5: Supabase queries + +**Toon de vier belangrijkste operaties:** +1. `.from("polls").select("*, options(*)")` β†’ Haal alles op met relaties +2. `.eq("id", 5).single()` β†’ Filter op 1 record +3. `.insert({ question })` β†’ Nieuw record toevoegen +4. `.rpc("vote_option", { option_id })` β†’ Database functie aanroepen + +**Demo:** Open Supabase dashboard, toon Table Editor met polls en options tabel. Laat de relatie zien (foreign key). + +#### 09:35 | Slide 6: Server vs Client + +**Zeg:** +"Belangrijk patroon: Server Components zijn `async` β€” die halen data op. Client Components hebben `'use client'` β€” die zijn interactief (forms, klikken). In de PDF zien jullie dit terug." + +--- + +### 09:45–10:15 | Deel 1: Setup + Queries (30 min, zelfstandig) +πŸ“Œ Slide 5 (blijft staan als referentie) + +**Zeg:** +"Open de Lesopdracht PDF. Werk Deel 1, 2 en 3 door. Dat is de setup, queries schrijven, en componenten kopiΓ«ren. Na de pauze doen we Deel 4: de /create pagina." + +**Studenten doen nu:** +- Deel 1 (PDF): npm install, .env, supabase client, types +- Deel 2 (PDF): lib/data.ts β€” TODO blokken invullen (getPolls, getPollById, votePoll) +- Deel 3 (PDF): Componenten kopiΓ«ren (page.tsx, PollItem, VoteForm, poll/[id]) + +**Jij loopt rond. Veelvoorkomende issues:** + +| Probleem | Oplossing | +|----------|-----------| +| npm install failed | Check internet, node_modules verwijderen en opnieuw | +| Env vars undefined | NEXT_PUBLIC_ prefix? Dev server herstart? | +| getPolls() returns [] | Query syntax checken. Staat er data in Supabase? | +| TypeScript errors | Import vergeten? Types kloppen met database? | +| "RLS policy violation" | RLS uitschakelen of SELECT policy toevoegen | + +**Check-in (10:00):** +"Wie heeft de homepage al werkend met Supabase data? Steek je hand op." +β†’ Als minder dan de helft: kort voordoen op beamer. +β†’ Als meer dan de helft: doorgaan, help de rest individueel. + +--- + +### 10:15–10:30 | PAUZE (15 min) +πŸ“Œ Slide 7 + +--- + +### 10:30–10:45 | Uitleg INSERT + /create (15 min) +πŸ“Œ Slide 8 + +**Zeg:** +"Nu gaan jullie een /create pagina bouwen. Het formulier staat al in de PDF β€” jullie schrijven alleen de INSERT logica." + +**Toon op beamer:** +```typescript +// 1. Insert poll +const { data: poll } = await supabase + .from("polls") + .insert({ question: "Mijn vraag" }) + .select() + .single(); + +// 2. Insert options met poll.id +await supabase.from("options").insert([ + { poll_id: poll.id, text: "Optie A", votes: 0 }, + { poll_id: poll.id, text: "Optie B", votes: 0 }, +]); +``` + +**Zeg:** +- "Eerst insert je de poll β†’ je krijgt het id terug" +- "Dan insert je de options met dat poll_id" +- "En dan redirect je naar de homepage" + +**RLS policy:** +"Voordat het werkt: voeg INSERT policies toe. Staat in Deel 4, Stap 4.1 van de PDF." + +--- + +### 10:45–11:30 | Deel 2: /create pagina (45 min, zelfstandig) + +**Studenten doen nu:** +- Deel 4 (PDF): RLS policy toevoegen, handleSubmit implementeren +- Testen: poll aanmaken β†’ verschijnt op homepage + +**Jij loopt rond. Veelvoorkomende issues:** + +| Probleem | Oplossing | +|----------|-----------| +| "RLS policy violation" bij INSERT | SQL policy uitgevoerd in dashboard? | +| poll is undefined na insert | .select().single() vergeten? | +| Opties worden niet opgeslagen | poll.id doorgeven aan options insert? | +| Form refresht de pagina | e.preventDefault() in handleSubmit? | +| Redirect werkt niet | useRouter van "next/navigation"? | + +**Check-in (11:15):** +"Wie heeft succesvol een poll aangemaakt? Open Supabase dashboard en toon dat ie erin staat." +β†’ Toon op beamer als demo. + +--- + +### 11:30–11:45 | Vragen & Reflectie (15 min) + +**Mogelijke vragen:** + +**V: Waarom async/await?** +A: Supabase is over het netwerk. We moeten wachten op antwoord. + +**V: Wat is het verschil tussen Server en Client Component?** +A: Server = async, data fetching, geen interactiviteit. Client = 'use client', useState, onClick. + +**V: Kan ik realtime updates zien?** +A: Later! Supabase heeft realtime subscriptions. + +--- + +### 11:45–12:00 | Huiswerk & Afsluiting (15 min) +πŸ“Œ Slide 9, 10 + +**Huiswerk:** +1. /create pagina afmaken (als niet klaar) +2. Validatie: vraag niet leeg, min 2 opties, foutmeldingen +3. Extra: delete functionaliteit, styling + +**Slide 10: Afsluiting** +"Volgende les: Supabase Auth. Inloggen, registreren, en bepalen wie wat mag. Tot dan!" + +--- + +## Tips voor docenten + +1. **Niet te veel voordoen.** De PDF is self-contained. Studenten leren meer door zelf te doen. +2. **Loop ronde, spot problemen vroeg.** De eerste 10 minuten na "ga aan de slag" zijn cruciaal. +3. **Check-ins doen.** Vraag om handopsteken. Als <50% het heeft: kort voordoen. +4. **Toon Supabase dashboard.** "Zie je? De data staat echt in de database!" +5. **Authenticatie is volgende les.** Zeg het af en toe, zodat ze weten dat RLS nog tijdelijk is. diff --git a/Les08-Lesopdracht.pdf b/Les08-Lesopdracht.pdf new file mode 100644 index 0000000..04d3e89 --- /dev/null +++ b/Les08-Lesopdracht.pdf @@ -0,0 +1,346 @@ +%PDF-1.4 +%“Œ‹ž 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 8 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 +<< +/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font +>> +endobj +9 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 +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:20260331170259+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260331170259+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 9 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@';A&m;kcN6lBiR6hF!E%W7?@_#n=;Z:K6"[AU7YG`WCp@7/(!$R[/rMKaUA2Ij&8Z^.Jq-bYo0DUeVI1o%D?0U8%u7lBK;U(#J_C$7+=!D9^kB6Q3[l=bb1>$%\0OcCO%a*dD859?t\P27fU$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 1267 +>> +stream +Gau0CD0+Dj&H9tYf^ojA*6.Og2OW?+d\FKLF%KP+HT/tS,E*7/Cs/(I8__@:^Qj`a&1G7tJf2aAYG>fTT1:CD0_$@`*[Um-4_VPU5V2,)!HAMQHDf-Se\9PSgsWM;0_gbTD9L*fjd36cfDS2Q:ScHe[i*_FuL?!#.k8B"i_E]l=6n+*=8n!\+q_4#HRgU:XO9n*ZT`h`lEPsksA@hn4cX+WT#:d9ghL9iR1L.Gl)1cG/HjA5,oHApCYPsRN)Er,2XP"V@"S![(S;/X2<9HZlW[)P$R<@Jll)<=K8R+SG>dL;_:_H&t]:-+:9:Jl9JAlM0?(paQHF+%s5csrpgr-[GgG0!M[K@1^c;eiDf^2C/sB#Hg-nbc$?;B7/[(!fAY"Qp-"QGB$S(!,dRh@#J#0pftp&es=@'J=X97-#Enk,e/eDfB]AI!Y?qd(R(O3^2hP#+$E-&'%R[H+Vb"nEFe"m^Q-"h?^ZPJI99V+cajncN`i"Ii[hR2!&H3L4*ot,:4m.ceai\;A>scV)5VR$L47XFJP(T:8ns8;5h^)CFQ80*tL@H,@)I'P[?Q-CXG%I!*20re6ZH;GbA&MeL5WIeOSL53=QQSREE(nn]oYWIEEf6V>W3sF_m?aD6$_fhP_,Pkt0_G!(d\l;4h\@SoV9hbT,Z'VW@$'hN$X"C5!b#IX?=-*GsR4fffU>Bf=,Jb%H0P>Bii\p=aL(*_"nG>-'7C_-Mdq$P<_)@K1;jPU_j/SSAJ]mUb_a5cH!L5`@H)='TgMj;;rr-QPnp[>b*ulcAr%FBlN6.`ESRcrVI@5)snsr/H0KAeA.-Rg1d.HiVL*n\dcXA5"_[7(]*cG?+6HNbPlhXq+endstream +endobj +27 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1525 +>> +stream +Gb!;d?$#!`'Re<2\BP]/CkO*adn\XhdrrL'RjLg98nZ2s(JnRW,0SZF8cF(?CdW^^$"UBd1h8:B=45VZ8&n\MYis',pNR*cgkGH?*4UOe!4]odke;gZ]6dV+]SZ7`o0O%0n6M[VcjQfp2)F1thf45#JRsT<=D40g:r>F7P:oVY*Rb](6oB[DIh/Xp`"YTE:*L]E)F[$W$/rdI3JmT0JT+V`P#r\hpB%p]-Dr59^iE@p%E.X1<$,SRUPmBbDX&<8=OP/"ua17X*Uf90&(qc+M:j3oCffE?0:2tUDemt>N1QF,`k=t_6,hThN[>kcYN^A":F)Zd6u(pqTqa/a3a^3Q(XcHTe(L%k^JJ3+fanF.R>hJsTW_S)2b"7%Q4R7]U@]&W4McopmY?u:cqj8V9?^e6;qqO2-F<"n9elN_K8&"hZ\7_J-cY`>!CTTmR[jS^f;kI-sWY\4([L!C./7:\<@/>#SdY2-1Blk$QrUZf+3#ISAT7o9U*Ym;#l!;U/XN9Ja"iCX7=\?__m(&hE'*u36UL-_6H>36\R@aK[)U-f\u/\:UBI5/urs&4FH&T40E`:4WmU*P-<$ToBl-9W-SJV"OJe.B28FQcmk-cCWSE,2R`r>!/[Etgt/H]Cl4+NLm'$t`@/DHNUZDHGT%"6[).!n&,ro%#7X(+tMQ7'b26,,R)*(8iJ3p]*cp7M,jJ!dZGPBr!'K%bP7c;V0MUdm[p6enh60Q)bD*<*&'/ZK`seiY:ZUA^F8`fV9kT(lQZj_AFHOhTd;ekLd1c+o4q-GI&A,G![d;:GBGU+IVT2U+&[+%1,Np0T`92HAus/a`<_7B#><53T#Tj&c4#78",aAYUEF"On:\)C@#M,nIl]12#AKLaV/k7js+."`Q[L+f4_VA^%7b0*#hMp1X=&C2b;""rl=/B%mdl:hE:$erL=pp;7;2QE&W#%mg7Qsin7[K4_HcMN5NjiS$URN)ORaL+"Fl[69tYje)Bq%`JU,&A0qNOO#>YP.Q<@cHo0,5irJMU038?Tg+`2U?"d5U+DJP/8ajMK\DYTmTtgendstream +endobj +28 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1260 +>> +stream +Gatm:D0+E#&H9tYf\oIk@L5d8+d(4;,Yab'4cW`&rDNLNO;:YDl'0Z=i*lEL\N=,o5V3oW_s-9+pN^q#?O1c7Qg"Gl!Ve@#p2:(i%AP?`a)YR0%A-+LD[j#.l.VHhgHL1;YAY$1oqe1+D:u)JGC!Y>]h[1g_=G5'"+8[]3m!MP;'J`e?u$G@ru-i'&S(#7i$1lMQ=;[&oY$DY';`*`(^$4&']+WYhqTXn0T&:]=#OsQQ5:F!D!i%'Dd9VI7Fg_C]tclD#9-/o7EL'h+?&0)ji1nA&EErcZg6VK*,P@Y=MhdJHG)C!L]go*Y/ZtdtQCp"JSP8dcYgMa%:gYN*a\=0k(C#95c6.[;7[d?9*/8Jm?L6foI0K/?\59N/nBA@eO^$GnNcSPbl6eYW.;H;R'_Z/tt%gToM]N;V%G'b7WtP1$:M:2VS7&=f\#Fd8l*6L#XF2cu_>iQnt/UK0n*cH1Xq^Kh9L?qb*G`?J`LmKZ.E\YmW\C4['?5"tL&43dWfpCM:I$a)PnM>$hn9)2;dTW@rI#QAfsG0&r&FDYWq&iLd'sWqY+q6s_X$3%%5"O)^s6ocB%Zcl)C>?HWNQ'?]nb^"@D7(U)_S*Tq5l-N4C!nXDj?Q@Yq3VVIi9f6(0a-.C-0d)s=;L3^2U7WMbq;cuT`IJT89/3i#mendstream +endobj +29 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 791 +>> +stream +Gb!#[gMWKG&:N^l7TM%mQ53Rr*(]BpMU_@=Wg!k`aUcCq(n-.)bMnGAs1O%<Ap9]1+DXZprHJc,8$GB*4;n)5T34u=SY%.0135k8m2>mU=RdTmo9?2cSPZ'.9imbU->j)Y-"5C]a$uWZ1/^B<[!eFKKM<>'o2W-R-p:M/)6!PZ%Rc$Z5*Xp[^'-E(Z_ms5HCSb8$`Z1X2FZ%XA0%["gH.9N$4":lZH*(RCICZl?8m]E&6a(V(VAZ6WK"!K4T#\'S&<+14W+c/2A'#$W.INb@($!9k3YJV!nNTmZi,-s",@Qq]C(_^F)jVpEqQ`WLOG\-peCi:$[m7Pqk`'f*u\g3Q!,SK0-L]<82)Mn3u%.;MZWW#1_A)e'!!FaYK)7?8\%Za$*0/B+%6E9+)B:9JQ@/P4i>Lk6+F`.17:j/s=\`M8bFq.tP)T;+oo9bnZ`1(Q_I\9Z^91=;9"]/fi8_e@0A4(gke#R1U6V&gq,/ZI><='Pl+F#qm1\T9#lT3D-$/F&:<2MPRCn`94dUSluN0jD7Xm0Mb"M?bmT&@A$Zia09m=P5Hse:7dc`[L"b[>dRnNPs&kIMtC9*Cu#>o/542#k#hW.XgIFUO4q#Wp-$sD^2#UkS:#aEH@\k"g0:KONj%jW?\raOBqShQS2fella_I.IGA.R)i])qTDH%,JbBJt6)]/~>endstream +endobj +30 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1301 +>> +stream +Gau0CgN):C&:N^l3dt5:#m,sQ?pcF*Fe!L&-Z2IKC('%s)#1hIAX!N])Cu!Lb+,EM(n[H,0LVMDbOlFd0m=nF;]NW3pHn!J"8_*H#tE3>hu`]bQ[NF'p0qe0X@BN$L[3j4=7eC%.seDJkZTPt>UeeTC6n_)XL<#oCX?sf+:rET-AM8hcu*qsSI1%<#,2Ei\oBnH+6!RB8b1h-6;0+,*99tAKS24E"=9@%fYCSlF1;h'*-q-i.pe%orniV4>5,^-h.;`iQ2`Y)?GLc\jXS2^7l1tD'#g4u#^IIUMBIjc?"cnf+^]ghD:Ho(5<\JfhX'RJf*$th@i"qs9)sR.>I(XoAEmROhZmOj:IB*b"URtR^g)prIY%jeQh:;6%(7dr[5%7t[&]fOkY[@_'.jQBHU5\D4]B@++-d12^nMQfW#MQ)*o&sKGWnG[Zj,bf^RXGq^D-$N9oA]cTjt=$fPrU&'W*1OekhTNNifZTDbQ*FIHTU2JPG.cn)UeMIU?BA\LU^f9"eKD3!HZ$sd/&)4WV?CNi=KB-Bi)+TXS0]&^X&h7KH4_+lkRQ0H$ba-*SK(b_`\D=6UIf=`a/H9cP%%Ng4c.e!]>'"Vg]BI[2ZTupa;03b:s4;A\idO-=i5o!$rN;kJ;'3C?l4jAg2j(H[hLX@_Q;68k-FJmB#5@t>Wr;:kBQUi;4or%=Od:]04f!M5roCW9ooMmE*<8\SC:2oKW0JRL#O_4HTRB6p=^@OTOu]$*(dL8P+$nif)$KTma6e-%^PoF5.>V%%qD3<(F)o2WhCrAVLYW8/2:UEa*o(f-EUJggm#_t;dBT,Fnm;!RXVjlV=6FDY9phru_ZBXB#YkWBm3o&$5>po[>?DIPW;Hm`ERh/~>endstream +endobj +31 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 675 +>> +stream +GatU0?#Q2d'Rf.GSG%e=_I,n_ABQH6>,FpB['"(<]K!_6XFM`Z9<*mL4p+NFUhSF0!cbES?g%:>LVcq"D83Cc(^D`B'\#T+#OR^=JfB(DZj2RSHb)+TIG:Y:3'O%Ml`3!V-D'[/NfQR'j/YWR=D>]tX\5jY6Wl%K*/<(XI;_A5'aB$CU?!.uBX]ItJjd'rD25-56.R6m;-4i`c&Xjj[1jPU6C&!%\:BS\JmVaPKS#I?X&'>3d2OZoL8\XNj9sJ9>Mh>"m,@3tC-U^fc?n`+a+lEA1o$\b;6f=;=_%Ll"(YkM=7f__m1*AJp_K_ei(kUkTe+r>8DQFXlES,C($I]I5*jg*,#hM+7<,"d;oQEmOe`\K_=?Q&I3g;o5TNKoVpaOUQ"8qFiucb"^'mn8/;=A3pS8\ULF]hU9u\MU0-l:mFaT[i+L^04rJ"L!\3gi4FW0Br-VC-c^U5&U#AQ@g//WEI9#OUC6dn`JN2endstream +endobj +32 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1259 +>> +stream +Gau0BD/\E'&H9tYR.:PYabDYWCoUN.Enh[P)LC>AC.hPYG[_GIc)oTkXY*Eq6n!hO7&=?tCY.pC/t9,)0M"iF8rdBS;?NA0bOuVd#+Ki%;cl@pj\[cVm[85*U\Ggbf7\a-=\h`_BUU>85CJ];biL[j`k)6a@;$KTV>R'=^4K7O>.<&8I1198!_CfhS-J&P47hY_g7OJQ3V&&0b;95JbJ>U(^X^5h+&=9l8lk&srla+iV)2(4&ta8F[eLH2`#2!of[\9a>R5,?VA;`_gnQ*]-Ja4tp-cL^XJreeGHa0EbjOR4jYp%+f'=WED^9@cl7Olib3PrkFqVNY!3>t^P?MjWt_JVYH+R(`7%QCNL"SVYJD!t%cJ0^^C]nXUX;.?NHT3Ga4E[q;F:h3g;$p\3"+Ui&]b*^>l#;[jXM&q\PeML?\f$4E.a&G$cpnXB%H%&Q`4;]i%oQR5IV4T7p*.g;i40[7\XgP1&[V#:I)l\YX)efoP-58PU38AfMGY^HU.sO@q?8,t(Rb`toL=(uh&$^D%le+@Hl`A1,Rdq6#Phk#K!OCA#RqY_&f):eO]'/q..K5asX,GAmHqD8nIBendstream +endobj +33 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1341 +>> +stream +Gau0BD0+Gi%0#[%Ja,n!?:L?g,YRR_&kQ7-OISX+kW\,VuspT:m\[Gr!4[ff!cE#hXhiJLJCC6k>O]75=@MZ9kXbR-u?(rhEjoI>hsEBAgT0@lN03.E7W-@]%)WO\,+WC\G\cm9Eu)fg0uZWLd1P/P[$>p@=sdB%'jQ2<\C@h)+M%?SI(4dEg?2>fZ:7?fT7G%(/LM"M=h1_g>[#]5=Ou$O:iXLP;*$X4+3T'QC;s`)T>ohVAAY"0khWRn&nMmbRuhAO[j&(MOA2W!"Xn.q0IYiX[SL7YfeA5`&OP'uDR$ZPJ!!OSa,12JoL;qqmeW"\ad=;k$PjPk%oN]B^cmF/F8Ib1Q]3H/>rmfXr"18'P$29?O<;$n5i[HTGl2nX/1r\r`+Sm&;??C]OKDSo^&&Xteh!?+%Bem,LB-GA2!KVQhf654H0L3_n#oW,Brrq_Pql4XY>keU?1lG'7Jt48/*f9YI[eRD!`U%Va75HU,0Ff0KtrEI/]Z1nFH(X0rJTZZg&1pn0"-"F8QSe&A>JIe`g"jF#U=2;<"p&'Uh@;/%L?2W,Q:/Z(U3H6Y"lNA6OVg3?5Dr.LQ:d`2j`&5&mj]LM5H9+:/-FtR*YSK!0C#F)b_R]qn?'9T_/EHb,<5~>endstream +endobj +34 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 466 +>> +stream +GasbU?V;n(&B3Q$;]L1"7FNO,?[WU#^=3C1*u?2;>$'G&hVHZg(Vfj[!s-DQbIufj8nT]cqTHlD6nX-lSVem8ctM];5]-H'P(=tkqV27,38jttjO0IL(5NNuqjV)_[4Xt81rm"+^E,E,L6m#fBmf:bVH6AP4&&g)1EL4HnE]%I!Su(pU2iDE-&'I.;j7@[DI6ZU,%#Y'`0Y$`~>endstream +endobj +35 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1122 +>> +stream +Gb"/(>u0HF'Rf.GgmF'ef9T/\V3o4,pFFoS\h7OR!tQ/.5]te[dj\[\s*\P6.MS"6D1RHO`o@'d4nn9t$R3s9"3&$%*l)J/.c1d[*SPp,L2'!N%)0/CG9rKfe'qC(Tf3P4c'jM.Aj]fN?<318%LcA&WN)Rj`Hu.0PO=QX"L8hX-XJ)kqU^*)F)=:Pd)fc]p+/_Hch`G-Z)Tib4;0(FA,_Uang>aQSmJRUX8bKDjl<^NPLc+;[-J.)V'SC75R-O^BN'3V8E&(R6WBO)kU7"1s>Rq`GNJCZC"eSJiVo(R+Z(lOqp`Kt-2105mUgM"SU%NZ38LXAMi+SV9;P[Kct)ZP&&"fSm@i&96Y&6)k??`J//=P6a+FG7b=q9DohKB,+8CZY7g=0\@gaZ'7((;A*+<0u$Dq'dQarZN93'*/Af6.`D&POW>i2F=EEs9hJt.E+*Y9K,6SYu'C[SU?:kotu,M=oRWYTE?iOd/GP7B(Qjo)G?QRM*JJc5m*#dh;`J\DE.7NWB[c9`7l8cu%2U$C;sVq`^Ka!5OlgJ6n\/=0(N^#F]LI,CI6":CHt(PSqdn,rs('XGsudF*8UG]^*?FK?f(ZI-m,@2B.9.Yn/Hk4g,kVK#k]8%ZVi$QIq:?I=--P$>l-6uA^BbUSueFJd[[e:A-e<.74Gf3?I+!Gg'Yn2/&hp'5sN-_TZ"'usN%(%H1[3V?;c:C^S[U+?R--#W0PMWkUcl!fd#FHfeVXbp-pQ@8^3e$NDSQ\D+=,WgJ7=k>J9-JR3q;Haf11lPAJXG\Oq^3HOmY#q5`UdhM,Z~>endstream +endobj +36 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 545 +>> +stream +GauHJd;GF-'Rf-pNd,;"iF3]RUs15tg@eE]lbm:>9Q6q3YVRuT\n_7/#KG>j;Pe,?1?'-eT6m_hY%.ms1toWE'sL5Kp7?j?8CsM51.?MKGH(2YTtb50!Jus^Ta5@'BG)V'SJ`bo')UhuNq(9tLP4;?,`2GWG`$"4$V/'lpWkZ+8Sj\9LjdbmMc8fY0H3UUO3/RR%:*.[$EoH?F2[qtCCobM2=q4+"d#>8";@Slf#jN7V2u[fOJ_K$t&8+P)2miQ=*%CE[u*cp&n4g9CaAsL`oXe1N+AL5[c&#EaGOG.h\5B?MD>IS1#XS>R,mnCC+'q/Xm"fTu.htac:qa)3!]GN`LiV[;&_cB+]H_EZ)bmk6")n6:T6PP1b(GiRnPrVg`AB!uHF=e?N>.G,2CK4FhU3WWAO(L!lQ[aeegeH+"%W4smo*\3Kj(&~>endstream +endobj +37 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1622 +>> +stream +GauHKD/\/e&H88.EQtDg6PR4;;?s:-;tjkpg*HBB!DQGmi`,+DWVbq(.W2V?`;qE'7l=,70-cXDQ^`9\qsehDHJr*IgXcaOSODp%hR%Aqlr+E3W<81f)]q%0(0")!TMs$22u?f>IX`.]2N2%$E,E9gPBl.@TTf$eLaSECIB2hZkd:]S*^i3suJ!cCkOj9p4O172PUCP5F!R!P4&%HkipQ[-!IPp^dHQUR%;HLu3R8u?>fIJbPR-=u(77\4Fj3!QPHa/36<)$N]f_t34l:R-8nSN_9]@Z5lPd[d6F!L5'QuF?j0QGQ2fXOd\W>KG0@qKi?_0JT14>CoXDi@g>;8%gB8m@t_S`\KO*5ghD'-5-:u!,5U1K>CeeJflGmjZ#&i>g9L#5V);Z5fup_,X+r\S6613\T&a(S&V(nu.QL6V"K,M?[$:fsI?krlVB>TXp()GcMkZ[uhp4bl9AdYG4502o3Qj0QI4`_g89n/]\/u?_ODUibCJi^Cg$[&J\i:`T;Z"Z.PffD/P\PBFTjI1_FV<_N$X:3aU!+]+e//X?XqfiTs.4YQq!7&&h>(^)"mLK4JI].cTV.T1AB"8LPnc/Pab0f.&I]p9\+0DC%'j5?d^b@/of'37(pgG;0=:u"36$;+U,'p!iD+\KcEEZ4"q"8E$;D\Y+HW4$5-kB,TIDbO;9I@jJ!nZ[SsR["UD^r1rpbdD>2:R\(YaR1S]pQAhdlX;lgtlb:3G-u$9lBGa3%+aeln:7D^SN*E.8Q?.?S9%-ZnC=lAF1q[:IQ`>9_;6$n%cuBaJ90n3Pa%o&;>U``1:G_L=nf:6*jPou3UoO85:WDQRGQt@T55\QsgATf)<--(JoXd:BJl%F>nF6&7c&pu$D)54gpK*qi-8?cC^WNi6F;ThPjFkj$/,GL$.@/K9i*JYAAkOl&I`hh,l/(n;TL9>0qtLaX7f@`S:B5B-6*'q86>~>endstream +endobj +38 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1632 +>> +stream +Gatm;gN)%,&:O:SD#G""j3T&r&Xg,jini*Y'5O*Sc#d>*Q6Ck\%74*Z:B/!8hF,ML.4I,EU1!>Z4bqYnQ6*P\H0+cO"*cnYb:=)c!g>_8![iN?8,+buGs(V3bD8,T64sgKiRm[`-p^(fG">$@5mWJm]7*)^8UUC?H@CI5?73rB_[lrA)8^iPK[J+=?EBNC3.o_<-00,kAW5Hp@,s=E7U._f?-Y:%L-\St>Z.5=)1>nu[V@Kd5Xb\]#*LbE%t[[g.pA0[k"NMBJ1Y%Wa6g@fE2+kQ6\_3gZs[\[htTO@6bi!f4-r();ue?J?45fp.S![]^(:sS,i#Aqad>W0SAQpcJ,Yo_S'dd1iIQ-`&X6OpSst0[#\P_rUH+O/*0R=%fD,dJlFOmQD3l;snNPB8.pnM..;\cZ3nqa;E@Jp-G^kI.4ZZiW>^=?L^J,1b?1glX`h0Bu+-"a(t9Hi9G0?Jgp8@Tn)8cA4ghEc7n'$P`$9Cko/43!rV:-4>*%>;X,.0@>08sCGmu9/M122NZV/:Phon=E#Wc0E4duV68EEf*k,ZOZXdU--+d"UWtc_3@)Za&Nn4,X>akO^+dYO?dH(EdS+FL1VX8n*\7IZ8G[KDnYbK-Z8[f(5n8NUA:LY_+_5MldN3GA[O8TllpAN4Z;G3t0YS=HJ*M]W(qh)c2B0)\s>h>ChB7$kdXZn>%_u$oc=,O+&Q,g,t`BJ]JLAb,;pn>YI;hiuFl1YKH1]&pqTci;!0=C-Y,QnZF^'G:Le$`*^P5W1gAYcADup$J,JhEt@7PY8@5AYm=N"tf;3+)OV@`A:"@5Oo?Y_gP]H:5_mCP^^N(Dl-95CH[5r8P?]\qj8$7q!W~>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 +0000001102 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 +0000006563 00000 n +0000008180 00000 n +0000009532 00000 n +0000010414 00000 n +0000011807 00000 n +0000012573 00000 n +0000013924 00000 n +0000015357 00000 n +0000015914 00000 n +0000017128 00000 n +0000017764 00000 n +0000019478 00000 n +trailer +<< +/ID +[] +% ReportLab generated PDF document -- digest (opensource) + +/Info 23 0 R +/Root 22 0 R +/Size 39 +>> +startxref +21202 +%%EOF diff --git a/Les08-Slide-Overzicht.md b/Les08-Slide-Overzicht.md new file mode 100644 index 0000000..8e8ed08 --- /dev/null +++ b/Les08-Slide-Overzicht.md @@ -0,0 +1,186 @@ +# Les 8 β€” Slide-overzicht +## Van In-Memory naar Supabase (10 slides) + +--- + +### Slide 1: Titelslide +**Titel:** Les 8 β€” Van In-Memory naar Supabase +**Ondertitel:** Koppelen van Supabase aan Next.js +**Visual:** Supabase + Next.js logo's, BLUE achtergrond + +--- + +### Slide 2: Terugblik vorige les +**Titel:** Terugblik β€” Waar waren we? + +**Bullets:** +- Stemmen werkt lokaal (in-memory data) +- QuickPoll app heeft 2 pages: / en /poll/[id] +- VoteForm component ziet stemmen onmiddellijk +- Nu: alles naar een echte database + +**Code snippet:** +```javascript +// OUD +const polls = [ + { question: "...", options: [...], votes: [...] } +]; +``` + +--- + +### Slide 3: Planning vandaag +**Titel:** Planning β€” Les 8 (3 uur) + +**Timeline:** +- 09:00-09:15 | Welkom & Uitleg aanpak (15 min) +- 09:15-09:45 | **Uitleg concepten** (30 min) +- 09:45-10:15 | **Zelfstandig: Setup + Queries** (30 min) +- 10:15-10:30 | Pauze (15 min) +- 10:30-10:45 | **Uitleg INSERT queries** (15 min) +- 10:45-11:30 | **Zelfstandig: /create pagina** (45 min) +- 11:30-11:45 | Vragen & Reflectie (15 min) +- 11:45-12:00 | Huiswerk & Afsluiting (15 min) + +**Extra tekst:** "Jullie werken met de Lesopdracht PDF. Alle UI staat erin β€” jullie schrijven de queries!" + +--- + +### 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 +β”œβ”€ id (1) +β”œβ”€ poll_id (1) +β”œβ”€ text ("JS") +β”œβ”€ votes (10) +``` + +--- + +### Slide 5: Supabase Queries +**Titel:** Supabase Queries β€” Vier operaties + +**Vier blokken:** + +1. **SELECT alles** (met relaties) +```typescript +supabase.from("polls") + .select("*, options(*)") +``` + +2. **SELECT één** (filter + single) +```typescript +supabase.from("polls") + .select("*, options(*)") + .eq("id", 5).single() +``` + +3. **INSERT** (nieuw record) +```typescript +supabase.from("polls") + .insert({ question: "..." }) + .select().single() +``` + +4. **RPC** (database functie) +```typescript +supabase.rpc("vote_option", + { option_id: 42 }) +``` + +--- + +### Slide 6: Server vs Client: Wie doet wat? +**Titel:** Server vs Client: Wie doet wat? + +**Twee kolommen:** + +**SERVER Component:** +- `export default async function HomePage() { ... }` +- `const polls = await getPolls()` βœ“ +- Data fetching +- Direct naar database +- TypeScript compile-time + +**CLIENT Component:** +- `'use client'` +- `const [voted, setVoted] = useState(...)` +- Interactief: klikken, typen, formulieren +- useEffect, event handlers +- Browser runtime + +**Zeg:** "Server haalt data, Client maakt het interactief." + +--- + +### Slide 7: Pauze +**Titel:** Pauze + +**Tekst:** Setup + queries klaar? Na de pauze: /create pagina bouwen! + +--- + +### Slide 8: Zelf Doen β€” /create pagina +**Titel:** Zelf Doen β€” /create pagina + +**Ondertitel:** Het formulier staat in de PDF. Jij schrijft de INSERT logica! + +**INSERT voorbeeld:** +```typescript +// 1. Insert poll β†’ krijg id terug +const { data: poll } = await supabase + .from("polls") + .insert({ question }) + .select().single(); + +// 2. Insert options met poll.id +await supabase.from("options").insert([ + { poll_id: poll.id, text: "...", votes: 0 } +]); +``` + +**Stappen:** +1. RLS INSERT policy toevoegen (Stap 4.1 in PDF) +2. handleSubmit invullen (TODO blok in PDF) +3. Testen: poll aanmaken β†’ homepage checken + +--- + +### Slide 9: Huiswerk +**Titel:** Huiswerk + +**Verplicht:** +- /create pagina afmaken (als niet klaar) +- Validatie toevoegen (vraag niet leeg, min 2 opties) + +**Extra:** +- Delete functionaliteit +- Styling verbeteren + +--- + +### Slide 10: Afsluiting +**Titel:** Tot volgende week! + +**Tekst:** +- "Volgende les: Supabase Auth" +- "Inloggen, registreren" +- "Bepalen wie wat mag doen" diff --git a/Les08-Slides.pptx b/Les08-Slides.pptx new file mode 100644 index 0000000..057d5a7 Binary files /dev/null and b/Les08-Slides.pptx differ