diff --git a/CLAUDE.md b/CLAUDE.md index 5b2cf99..790a75a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,6 +70,25 @@ Deployment flow: `./preflight.sh` → `npm run build` → rsync naar server → - **Live URL:** https://frankmeeuwsen.com/workshopclaudecode/ - **SSH key:** `~/.ssh/id_rsa_no_pass` (passphrase-loos, specifiek voor automated deploys) +## Actief project: Configuratie centraliseren + +Zie `PRD.md` voor het volledige bouwplan. Korte samenvatting: + +**Doel:** Alle workshopdetails (datum, tijd, locatie, prijs, e-mail) centraliseren in `src/config/workshop.js` zodat een nieuwe editie een one-stop-change is. + +| Fase | Beschrijving | Status | +|------|-------------|--------| +| Fase 1 | Snelle fixes + config completeren (bug Footer, ongebruikte assets, Tally ID in config, ESLint blokkerend) | Afgerond | +| Fase 2 | Workshop details centraliseren in workshop.js - 7 componenten bijwerken | Afgerond | +| Fase 3 | Code deduplicatie - gedeeld TallyForm component | Afgerond | +| Fase 4 | Deployment en kwaliteit - post-deploy check, valuta-inconsistentie | Afgerond | + +### Openstaande punten (volgende sessie) + +- **Signup.jsx workshopsamenvatting:** Regel ~75 bevat hardcoded datum/tijd/locatie ("Vrijdag 3 april 2026 | 9:00 - 14:00 | Utrecht"). Kan naar WORKSHOP_CONFIG als die pagina hergebruikt wordt. + +--- + ## Conventions - Components use static data arrays + `.map()` for list rendering (benefits, FAQ items, timeline) diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..441c928 --- /dev/null +++ b/PRD.md @@ -0,0 +1,163 @@ +# PRD: Configuratie centraliseren - Claude Code Workshop Sales Page + +## Context + +De sales page heeft workshopdetails (datum, tijd, locatie, prijs, e-mail) hardcoded staan in 8+ componenten tegelijk. Als de workshop een nieuwe datum krijgt, moet je nu 20+ plekken aanpassen in 8 bestanden - en de kans op fouten is groot. De audit bevestigt dit: Footer.jsx zegt al "Max 7" terwijl alles "Max 8" is. + +Doel: één plek om de workshop te updaten, alle componenten lezen daaruit. + +--- + +## Wat het doet (gewone taal) + +Je wijzigt de datum van de workshop. Nu open je `src/config/workshop.js`, typt de nieuwe datum, en alle tekst op de pagina klopt automatisch: de hero, de sticky balk, de footer, de FAQ, de pricing-sectie, de e-maillink. Geen "zoek en vervang" meer door 8 bestanden. Geen inconsistentie meer zoals "Max 7 vs Max 8". + +--- + +## Hoe het eruitziet (de config-interface) + +Na implementatie ziet `src/config/workshop.js` er zo uit: + +```js +export const WORKSHOP_CONFIG = { + // Beschikbaarheid + totalSpots: 8, + availableSpots: 0, + isSoldOut: true, + + // Workshopdetails (pas hier aan voor nieuwe editie) + date: 'vrijdag 3 april 2026', + dateShort: '3 april', + time: '9:00 - 14:00', + timeStart: '9:00', + timeEnd: '14:00', + location: 'Utrecht', + venue: 'Wonders of Work, Utrecht', + maxParticipants: 8, + + // Prijs + price: '€399', + priceExclBtw: 'excl. BTW', + + // Trainer + email: 'frank@frankmeeuwsen.com', +}; +``` + +En `src/config/payment.js` bevat: + +```js +export const PAYMENT_CONFIG = { + SIGNUP_URL: '/inschrijven', + WAITLIST_URL: '/wachtlijst-inschrijven', + SIGNUP_TALLY_ID: 'XxGBrV', // was hardcoded in Signup.jsx + WAITLIST_TALLY_ID: 'kdyPJZ', +}; +``` + +Componenten importeren wat ze nodig hebben: +```js +import { WORKSHOP_CONFIG } from '../config/workshop'; +const { date, price, email } = WORKSHOP_CONFIG; +``` + +--- + +## Bouwfasen + +### Fase 1 - Snelle fixes en config completeren (minimum waarde) + +**Wat:** Los de bekende bugs op, verwijder rommel, zet de Tally form ID in config. + +**Wijzigingen:** +- `Footer.jsx:25` - "Max 7" corrigeren naar "Max 8" (bug fix) +- `public/og-image.old.png` verwijderen (700KB ongebruikt) +- `public/vite.svg` verwijderen (ongebruikt) +- `src/config/payment.js` - voeg `SIGNUP_TALLY_ID: 'XxGBrV'` toe +- `src/pages/Signup.jsx` - vervang hardcoded form ID door `PAYMENT_CONFIG.SIGNUP_TALLY_ID` +- `preflight.sh` - maak ESLint-fouten blokkerend (stop script bij lint-fouten) + +**Resultaat:** Geen bugs, geen rommel, form ID beheerbaar vanuit config. + +**Verificatie:** `./preflight.sh` slaagt, Footer toont "Max 8", Signup.jsx heeft geen hardcoded Tally ID meer. + +--- + +### Fase 2 - Workshop details centraliseren (kern van het project) + +**Wat:** Breid `workshop.js` uit met alle workshopdetails en laat alle componenten daaruit lezen. + +**Wijzigingen in `src/config/workshop.js`:** +- Voeg toe: `date`, `dateShort`, `time`, `timeStart`, `timeEnd`, `location`, `venue`, `maxParticipants`, `price`, `priceExclBtw`, `email` + +**Componenten bijwerken (hardcoded waarden vervangen door config-import):** +- `Hero.jsx` - datum, tijd, locatie (3 plekken) +- `FinalCTA.jsx` - datum, tijd, max deelnemers (3 plekken) +- `Footer.jsx` - datum, tijd, locatie/venue, email (5 plekken) +- `StickyBar.jsx` - datum, locatie, prijs (4 plekken) +- `Pricing.jsx` - prijs, max deelnemers (3 plekken) +- `Program.jsx` - tijden in de samenvatting-tekst (1 plek) +- `FAQ.jsx` - prijs, refund verwijzingen (2 plekken) + +**Resultaat:** Eén config-bestand om een nieuwe editie te lanceren. Geen inconsistente waarden meer. + +**Verificatie:** Wijzig `date` in workshop.js en verifieer dat Hero, Footer, StickyBar en FinalCTA allemaal de nieuwe datum tonen. `./preflight.sh` slaagt. + +--- + +### Fase 3 - Code deduplicatie + +**Wat:** Verwijder dubbele code in de inschrijfpagina's. + +**Wijzigingen:** +- Maak `src/components/TallyForm.jsx` - gedeeld component met `formId` prop +- Vervangt identieke Tally embed-logica in `Signup.jsx` en `WaitlistSignup.jsx` + +**Resultaat:** Tally embed-logica op één plek beheerbaar. + +**Verificatie:** Inschrijfflow en wachtlijstflow werken beide nog correct. `./preflight.sh` slaagt. + +--- + +### Fase 4 - Deployment en kwaliteitsverbetering + +**Wat:** Maak het deployen veiliger en los kleine inconsistenties op. + +**Wijzigingen:** +- `deploy.sh` - voeg post-deploy HTTP-check toe (curl naar live URL, verwacht HTTP 200) +- Valutainconsistentie: `FAQ.jsx:34` en `Installatie.jsx:60` uniformeren naar "€" + +**Resultaat:** Deploy geeft automatisch feedback of de live URL bereikbaar is. Alle valuta consistent. + +**Verificatie:** Deploy uitvoeren en curl-check zien slagen in de output. + +--- + +## Kritieke bestanden + +| Bestand | Rol | +|---------|-----| +| `src/config/workshop.js` | Centrale config - wordt uitgebreid | +| `src/config/payment.js` | Betaalconfig - SIGNUP_TALLY_ID toevoegen | +| `src/components/Hero.jsx` | Hardcoded datum/tijd/locatie | +| `src/components/Footer.jsx` | Hardcoded datum/tijd/venue/email + bug "Max 7" | +| `src/components/StickyBar.jsx` | Hardcoded datum/locatie/prijs | +| `src/components/FinalCTA.jsx` | Hardcoded datum/tijd/max deelnemers | +| `src/components/Pricing.jsx` | Hardcoded prijs/max deelnemers | +| `src/components/FAQ.jsx` | Hardcoded prijs + valutainconsistentie | +| `src/pages/Signup.jsx` | Hardcoded Tally form ID | +| `preflight.sh` | ESLint niet blokkerend | +| `deploy.sh` | Geen post-deploy check | +| `public/og-image.old.png` | Ongebruikt, 700KB | +| `public/vite.svg` | Ongebruikt | + +--- + +## Status bouwfasen + +| Fase | Beschrijving | Status | +|------|-------------|--------| +| Fase 1 | Snelle fixes + config completeren | Nog niet gestart | +| Fase 2 | Workshop details centraliseren | Nog niet gestart | +| Fase 3 | Code deduplicatie | Nog niet gestart | +| Fase 4 | Deployment en kwaliteit | Nog niet gestart | diff --git a/content/superpowers-main.zip b/content/superpowers-main.zip new file mode 100644 index 0000000..25348f6 Binary files /dev/null and b/content/superpowers-main.zip differ diff --git a/content/worksheet.md b/content/worksheet.md index 6feb813..647c4b8 100644 --- a/content/worksheet.md +++ b/content/worksheet.md @@ -3,6 +3,11 @@ title: Workshop Materiaal subtitle: Alles wat je nodig hebt tijdens de Claude Code Workshop --- +## Download het materiaal van de workshop +- [Slides workshop (PDF)](20260403-slides-Claude-Code-workshop-3-april.pdf) +- [Tips en learning (PDF)](tips-learnings-Claude-Code-Workshop-3-april.pdf) +- [Broncode introtimer](introtimer.zip) + ## Download Superpowers Superpowers is een plugin voor Claude Code die extra mogelijkheden toevoegt. Denk aan gestructureerd plannen, test-driven development en slimmer debuggen. Tijdens de workshop gebruiken we deze plugin. @@ -40,6 +45,47 @@ Beperkingen: - Python alleen als Node.js echt niet volstaat ::: +## Mijn personal preferences +Zet dit in Instellingen/Settings > Account > Personal Preferences + +:::command[Start prompt] +### Professionele Identiteit +- Zelfstandig ondernemer op gebied van AI, automation, workflows, digitale vaardigheden en slimmer werken, voor MKB in Nederland +- Pionier blogger (sinds 2000) en auteur van "Bloghelden" (release 2010) +- Liefhebber van PKM (Personal Knowledge Management) in Obsidian +- Gebruikt veel Claude Code en Claude Cowork +- Hobbyist programmeur die projecten bouwt voor plezier, niet perfectionisme + +### Sterke Punten +- Verhalend denken en contentcreatie (tekst, audio, beeld) +- Bruggen bouwen tussen technologie en toegankelijke communicatie +- Lange termijnvisie op digitale ontwikkelingen en ethiek +- Experimenteren met nieuwe platforms en tools +- Balans tussen professionaliteit en persoonlijke interesses + +### Schrijfwijze +- Gebruik mijn voornaam (Frank) om me aan te spreken +- ⁠Vermijd Amerikaanse stijlkenmerken, zoals overdreven enthousiasme en hoofdletters in titels. +- Als ik je vraag om mij te interviewen of vragen te stellen, dan MOET je die vragen één voor één stellen in plaats van in 1x bij elkaar. +- Hou je antwoorden duidelijk en to the point +- Houd de toon nuchter en in lijn met Nederlandse schrijfstijl. +- Gebruik bullet points als het nodig is voor verduidelijking +- Geen emoji's. +- Gebruik geen — (Em-dash) in je antwoorden +- Het is niet nodig om je antwoord af te sluiten met een vraag, tenzij de prompt of de instructies daar expliciet om vragen +- Refereer niet te expliciet naar mijn sterke punten of professionele identiteit. Zie dat als een gegeven. +- Informeel en bondig, op B1-taalniveau en inclusief +- Praat niet te veel met mijn ideeën mee, maar ben eerlijk en kritisch op mijn ideeën. +- Als ik redeneerfouten maak, te kort door de bocht iets stel, noem dat dan expliciet 'lui denkwerk.' + +### Voorkeuren +- Ik gebruik vi als standaard editor, geen nano +- Ik gebruik markdown als standaard schrijftaal +- Mijn projecten staan altijd in /Users/frank/Projecten. +- Losse scripts staan in /Users/frank/Documents/Hobbies/scripts +::: + + ## Tips tijdens het bouwen ### Sessiemanagement diff --git a/deploy.sh b/deploy.sh index 1640469..5418d2e 100755 --- a/deploy.sh +++ b/deploy.sh @@ -26,4 +26,13 @@ ssh "$SERVER" "docker cp $TMP_PATH/. $CONTAINER:$REMOTE_PATH/ && docker exec $CO echo "4/4 - Opruimen..." ssh "$SERVER" "rm -rf $TMP_PATH" -echo "Done! https://frankmeeuwsen.com/workshopclaudecode/" +echo "5/5 - Post-deploy check..." +LIVE_URL="https://frankmeeuwsen.com/workshopclaudecode/" +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$LIVE_URL") +if [ "$HTTP_STATUS" = "200" ]; then + echo "OK - Live URL bereikbaar (HTTP $HTTP_STATUS)" +else + echo "WAARSCHUWING - Live URL geeft HTTP $HTTP_STATUS: $LIVE_URL" +fi + +echo "Done! $LIVE_URL" diff --git a/eslint.config.js b/eslint.config.js index 4fa125d..6feb1e8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh' import { defineConfig, globalIgnores } from 'eslint/config' export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(['dist', '.claude/']), { files: ['**/*.{js,jsx}'], extends: [ diff --git a/preflight.sh b/preflight.sh index 84ea2fc..d35b758 100755 --- a/preflight.sh +++ b/preflight.sh @@ -19,7 +19,8 @@ echo "[1/4] ESLint..." if npm run lint --silent 2>&1; then echo " OK" else - echo " WAARSCHUWING: lint errors gevonden (niet-blokkerend)" + echo " FOUT: lint errors gevonden" + ERRORS=$((ERRORS + 1)) fi echo "" diff --git a/public/20260403-slides-Claude-Code-workshop-3-april.pdf b/public/20260403-slides-Claude-Code-workshop-3-april.pdf new file mode 100644 index 0000000..bf289f6 Binary files /dev/null and b/public/20260403-slides-Claude-Code-workshop-3-april.pdf differ diff --git a/public/introtimer.zip b/public/introtimer.zip new file mode 100644 index 0000000..3806bbf Binary files /dev/null and b/public/introtimer.zip differ diff --git a/public/tips-learnings-Claude-Code-Workshop-3-april.pdf b/public/tips-learnings-Claude-Code-Workshop-3-april.pdf new file mode 100644 index 0000000..1a34a2b Binary files /dev/null and b/public/tips-learnings-Claude-Code-Workshop-3-april.pdf differ diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/CookieBanner.jsx b/src/components/CookieBanner.jsx index c0e300b..e06e7b9 100644 --- a/src/components/CookieBanner.jsx +++ b/src/components/CookieBanner.jsx @@ -5,21 +5,14 @@ * Slaat voorkeur op in localStorage zodat de banner maar 1x verschijnt. */ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Link } from 'react-router-dom'; const STORAGE_KEY = 'cookie-banner-dismissed'; function CookieBanner() { - const [visible, setVisible] = useState(false); - - // Check bij laden of de banner al eerder is weggeklikt - useEffect(() => { - const dismissed = localStorage.getItem(STORAGE_KEY); - if (!dismissed) { - setVisible(true); - } - }, []); + // Lazy initializer: leest localStorage eénmalig bij mount (geen effect nodig) + const [visible, setVisible] = useState(() => !localStorage.getItem(STORAGE_KEY)); const dismiss = () => { localStorage.setItem(STORAGE_KEY, 'true'); diff --git a/src/components/FAQ.jsx b/src/components/FAQ.jsx index 1f87953..9b75e8b 100644 --- a/src/components/FAQ.jsx +++ b/src/components/FAQ.jsx @@ -6,6 +6,9 @@ */ import { useState } from 'react'; +import { WORKSHOP_CONFIG } from '../config/workshop'; + +const { price } = WORKSHOP_CONFIG; function FAQ() { // State om bij te houden welke items open zijn @@ -31,7 +34,7 @@ function FAQ() { }, { question: "Wat is een Claude Pro of Max account en waarom heb ik dat nodig?", - answer: "Dit is een betaald abonnement bij Anthropic (het bedrijf achter Claude). Pro kost ongeveer 20 euro per maand, Max ongeveer 100 euro. Je hebt dit nodig om Claude Code te kunnen gebruiken. Aanmelden kan via claude.ai." + answer: "Dit is een betaald abonnement bij Anthropic (het bedrijf achter Claude). Pro kost ongeveer €20 per maand, Max ongeveer €100. Je hebt dit nodig om Claude Code te kunnen gebruiken. Aanmelden kan via claude.ai." }, { question: "Moet ik iets voorbereiden?", @@ -50,7 +53,7 @@ function FAQ() { answer: "De workshop begint bij de basis, maar gaat vrij snel naar de interessantere features zoals agents en skills. Je werkt aan je eigen tempo en project, dus ook met voorkennis haal je er genoeg uit." }, { - question: "Is 399 euro niet veel voor een halve dag?", + question: `Is ${price} niet veel voor een halve dag?`, answer: "Je betaalt niet voor een halve dag, maar voor het overslaan van weken zelf uitzoeken. De meeste deelnemers besparen die investering binnen een maand door tools die ze zelf bouwen in plaats van inhuren. Plus: je hebt daarna toegang tot de community voor vragen." }, { diff --git a/src/components/FinalCTA.jsx b/src/components/FinalCTA.jsx index 389a880..17d3bdf 100644 --- a/src/components/FinalCTA.jsx +++ b/src/components/FinalCTA.jsx @@ -10,7 +10,7 @@ import { PAYMENT_CONFIG } from '../config/payment'; import { WORKSHOP_CONFIG } from '../config/workshop'; function FinalCTA() { - const { availableSpots, isSoldOut } = WORKSHOP_CONFIG; + const { availableSpots, isSoldOut, date, dateShort, timeStart, timeEnd, location, maxParticipants, email } = WORKSHOP_CONFIG; return (
@@ -28,14 +28,14 @@ function FinalCTA() {
{/* Headline */}

- Op 3 april werk je anders + Op {dateShort} werk je anders

{/* Closing text */}

De volgende workshop is op{' '} - vrijdag 3 april 2026 - {' '}in Utrecht. We starten om 9:00, rond 14:00 ga je naar huis met + {date} + {' '}in {location}. We starten om {timeStart}, rond {timeEnd} ga je naar huis met je eigen werkende project.

@@ -45,7 +45,7 @@ function FinalCTA() { - Maximaal 8 deelnemers per workshop + Maximaal {maxParticipants} deelnemers per workshop
@@ -72,7 +72,7 @@ function FinalCTA() { to={isSoldOut ? PAYMENT_CONFIG.WAITLIST_URL : PAYMENT_CONFIG.SIGNUP_URL} className="inline-flex items-center gap-2 px-8 py-4 bg-white text-coral-600 font-semibold text-lg rounded-xl shadow-lg hover:bg-coral-50 hover:shadow-xl active:bg-coral-100 transition-all duration-200" > - {isSoldOut ? 'Zet me op de wachtlijst' : 'Doe mee op 3 april'} + {isSoldOut ? 'Zet me op de wachtlijst' : `Doe mee op ${dateShort}`} @@ -81,7 +81,7 @@ function FinalCTA() { {/* Contact info */}

Vragen? Mail naar{' '} - + Frank . Ik help je graag. diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index 6367eba..764abbd 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -6,6 +6,9 @@ */ import { Link } from 'react-router-dom'; +import { WORKSHOP_CONFIG } from '../config/workshop'; + +const { maxParticipants, dateFull, time, venue, email } = WORKSHOP_CONFIG; function Footer() { // Huidig jaar voor copyright @@ -22,7 +25,7 @@ function Footer() {

Van nieuwsgierig naar praktisch aan de slag met Claude Code. - Max 7 deelnemers, persoonlijke begeleiding. + Max {maxParticipants} deelnemers, persoonlijke begeleiding.

@@ -36,20 +39,20 @@ function Footer() { - Vrijdag 3 april 2026 + {dateFull}
  • - 9:00 - 14:00 + {time}
  • - Wonders of Work, Utrecht + {venue}
  • @@ -61,21 +64,21 @@ function Footer() {

    Vragen? Mail naar{' '} - - frank@frankmeeuwsen.com + + {email} {' '}en ik help je graag!