feat: configuratie centraliseren + ThankYou betalingsbevestiging
- Alle workshopdetails (datum, tijd, locatie, prijs, email) gecentraliseerd in workshop.js - ThankYou.jsx bijgewerkt: betalingsbevestiging tekst + WORKSHOP_CONFIG variabelen - Signup.jsx open punt opgelost: samenvattingsregel gebruikt nu config-variabelen - TallyForm gedeeld component toegevoegd (fase 3) - deploy.sh post-deploy HTTP-check toegevoegd (fase 4) - PRD.md en workshop materiaal (slides, tips, introtimer) toegevoegd - vite.svg verwijderd (ongebruikt) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
44f75914cc
commit
539e72aca1
26 changed files with 399 additions and 132 deletions
19
CLAUDE.md
19
CLAUDE.md
|
|
@ -70,6 +70,25 @@ Deployment flow: `./preflight.sh` → `npm run build` → rsync naar server →
|
||||||
- **Live URL:** https://frankmeeuwsen.com/workshopclaudecode/
|
- **Live URL:** https://frankmeeuwsen.com/workshopclaudecode/
|
||||||
- **SSH key:** `~/.ssh/id_rsa_no_pass` (passphrase-loos, specifiek voor automated deploys)
|
- **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
|
## Conventions
|
||||||
|
|
||||||
- Components use static data arrays + `.map()` for list rendering (benefits, FAQ items, timeline)
|
- Components use static data arrays + `.map()` for list rendering (benefits, FAQ items, timeline)
|
||||||
|
|
|
||||||
163
PRD.md
Normal file
163
PRD.md
Normal file
|
|
@ -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 |
|
||||||
BIN
content/superpowers-main.zip
Normal file
BIN
content/superpowers-main.zip
Normal file
Binary file not shown.
|
|
@ -3,6 +3,11 @@ title: Workshop Materiaal
|
||||||
subtitle: Alles wat je nodig hebt tijdens de Claude Code Workshop
|
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
|
## 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.
|
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
|
- 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
|
## Tips tijdens het bouwen
|
||||||
|
|
||||||
### Sessiemanagement
|
### Sessiemanagement
|
||||||
|
|
|
||||||
11
deploy.sh
11
deploy.sh
|
|
@ -26,4 +26,13 @@ ssh "$SERVER" "docker cp $TMP_PATH/. $CONTAINER:$REMOTE_PATH/ && docker exec $CO
|
||||||
echo "4/4 - Opruimen..."
|
echo "4/4 - Opruimen..."
|
||||||
ssh "$SERVER" "rm -rf $TMP_PATH"
|
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"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(['dist', '.claude/']),
|
||||||
{
|
{
|
||||||
files: ['**/*.{js,jsx}'],
|
files: ['**/*.{js,jsx}'],
|
||||||
extends: [
|
extends: [
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ echo "[1/4] ESLint..."
|
||||||
if npm run lint --silent 2>&1; then
|
if npm run lint --silent 2>&1; then
|
||||||
echo " OK"
|
echo " OK"
|
||||||
else
|
else
|
||||||
echo " WAARSCHUWING: lint errors gevonden (niet-blokkerend)"
|
echo " FOUT: lint errors gevonden"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
|
||||||
BIN
public/20260403-slides-Claude-Code-workshop-3-april.pdf
Normal file
BIN
public/20260403-slides-Claude-Code-workshop-3-april.pdf
Normal file
Binary file not shown.
BIN
public/introtimer.zip
Normal file
BIN
public/introtimer.zip
Normal file
Binary file not shown.
BIN
public/tips-learnings-Claude-Code-Workshop-3-april.pdf
Normal file
BIN
public/tips-learnings-Claude-Code-Workshop-3-april.pdf
Normal file
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -5,21 +5,14 @@
|
||||||
* Slaat voorkeur op in localStorage zodat de banner maar 1x verschijnt.
|
* 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';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const STORAGE_KEY = 'cookie-banner-dismissed';
|
const STORAGE_KEY = 'cookie-banner-dismissed';
|
||||||
|
|
||||||
function CookieBanner() {
|
function CookieBanner() {
|
||||||
const [visible, setVisible] = useState(false);
|
// Lazy initializer: leest localStorage eénmalig bij mount (geen effect nodig)
|
||||||
|
const [visible, setVisible] = useState(() => !localStorage.getItem(STORAGE_KEY));
|
||||||
// Check bij laden of de banner al eerder is weggeklikt
|
|
||||||
useEffect(() => {
|
|
||||||
const dismissed = localStorage.getItem(STORAGE_KEY);
|
|
||||||
if (!dismissed) {
|
|
||||||
setVisible(true);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const dismiss = () => {
|
const dismiss = () => {
|
||||||
localStorage.setItem(STORAGE_KEY, 'true');
|
localStorage.setItem(STORAGE_KEY, 'true');
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
|
const { price } = WORKSHOP_CONFIG;
|
||||||
|
|
||||||
function FAQ() {
|
function FAQ() {
|
||||||
// State om bij te houden welke items open zijn
|
// 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?",
|
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?",
|
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."
|
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."
|
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."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
import { WORKSHOP_CONFIG } from '../config/workshop';
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
function FinalCTA() {
|
function FinalCTA() {
|
||||||
const { availableSpots, isSoldOut } = WORKSHOP_CONFIG;
|
const { availableSpots, isSoldOut, date, dateShort, timeStart, timeEnd, location, maxParticipants, email } = WORKSHOP_CONFIG;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-16 lg:py-24 bg-coral-500 relative overflow-hidden">
|
<section className="py-16 lg:py-24 bg-coral-500 relative overflow-hidden">
|
||||||
|
|
@ -28,14 +28,14 @@ function FinalCTA() {
|
||||||
<div className="max-w-3xl mx-auto text-center">
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
{/* Headline */}
|
{/* Headline */}
|
||||||
<h2 className="font-display text-3xl md:text-4xl font-bold text-white mb-6">
|
<h2 className="font-display text-3xl md:text-4xl font-bold text-white mb-6">
|
||||||
Op 3 april werk je anders
|
Op {dateShort} werk je anders
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Closing text */}
|
{/* Closing text */}
|
||||||
<p className="text-xl text-coral-100 mb-8 leading-relaxed">
|
<p className="text-xl text-coral-100 mb-8 leading-relaxed">
|
||||||
De volgende workshop is op{' '}
|
De volgende workshop is op{' '}
|
||||||
<span className="text-white font-semibold">vrijdag 3 april 2026</span>
|
<span className="text-white font-semibold">{date}</span>
|
||||||
{' '}in Utrecht. We starten om 9:00, rond 14:00 ga je naar huis met
|
{' '}in {location}. We starten om {timeStart}, rond {timeEnd} ga je naar huis met
|
||||||
je eigen werkende project.
|
je eigen werkende project.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ function FinalCTA() {
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Maximaal 8 deelnemers per workshop</span>
|
<span>Maximaal {maxParticipants} deelnemers per workshop</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 font-semibold text-white">
|
<div className="flex items-center gap-2 font-semibold text-white">
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -72,7 +72,7 @@ function FinalCTA() {
|
||||||
to={isSoldOut ? PAYMENT_CONFIG.WAITLIST_URL : PAYMENT_CONFIG.SIGNUP_URL}
|
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"
|
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}`}
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -81,7 +81,7 @@ function FinalCTA() {
|
||||||
{/* Contact info */}
|
{/* Contact info */}
|
||||||
<p className="mt-8 text-coral-200">
|
<p className="mt-8 text-coral-200">
|
||||||
Vragen? Mail naar{' '}
|
Vragen? Mail naar{' '}
|
||||||
<a href="mailto:frank@frankmeeuwsen.com" className="text-white underline hover:no-underline">
|
<a href={`mailto:${email}`} className="text-white underline hover:no-underline">
|
||||||
Frank
|
Frank
|
||||||
</a>
|
</a>
|
||||||
. Ik help je graag.
|
. Ik help je graag.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
|
const { maxParticipants, dateFull, time, venue, email } = WORKSHOP_CONFIG;
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
// Huidig jaar voor copyright
|
// Huidig jaar voor copyright
|
||||||
|
|
@ -22,7 +25,7 @@ function Footer() {
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm leading-relaxed">
|
<p className="text-sm leading-relaxed">
|
||||||
Van nieuwsgierig naar praktisch aan de slag met Claude Code.
|
Van nieuwsgierig naar praktisch aan de slag met Claude Code.
|
||||||
Max 7 deelnemers, persoonlijke begeleiding.
|
Max {maxParticipants} deelnemers, persoonlijke begeleiding.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -36,20 +39,20 @@ function Footer() {
|
||||||
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
Vrijdag 3 april 2026
|
{dateFull}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
9:00 - 14:00
|
{time}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
Wonders of Work, Utrecht
|
{venue}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,21 +64,21 @@ function Footer() {
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm leading-relaxed mb-4">
|
<p className="text-sm leading-relaxed mb-4">
|
||||||
Vragen? Mail naar{' '}
|
Vragen? Mail naar{' '}
|
||||||
<a href="mailto:frank@frankmeeuwsen.com" className="text-coral-400 hover:text-coral-300 transition-colors">
|
<a href={`mailto:${email}`} className="text-coral-400 hover:text-coral-300 transition-colors">
|
||||||
frank@frankmeeuwsen.com
|
{email}
|
||||||
</a>
|
</a>
|
||||||
{' '}en ik help je graag!
|
{' '}en ik help je graag!
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-2 text-sm">
|
<ul className="space-y-2 text-sm">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="mailto:frank@frankmeeuwsen.com"
|
href={`mailto:${email}`}
|
||||||
className="hover:text-coral-400 transition-colors flex items-center gap-2"
|
className="hover:text-coral-400 transition-colors flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-coral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
frank@frankmeeuwsen.com
|
{email}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
|
|
||||||
function Hero() {
|
function Hero() {
|
||||||
// Aantal beschikbare plaatsen - beheer via src/config/workshop.js
|
// Aantal beschikbare plaatsen - beheer via src/config/workshop.js
|
||||||
const { totalSpots, availableSpots, isSoldOut } = WORKSHOP_CONFIG;
|
const { totalSpots, availableSpots, isSoldOut, dateLabel, time, location } = WORKSHOP_CONFIG;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="section relative overflow-hidden">
|
<section className="section relative overflow-hidden">
|
||||||
|
|
@ -85,7 +85,7 @@ function Hero() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-warm-500 font-medium uppercase tracking-wide">Datum</p>
|
<p className="text-xs text-warm-500 font-medium uppercase tracking-wide">Datum</p>
|
||||||
<p className="text-base font-semibold text-warm-800 whitespace-nowrap">Vrijdag 3 april</p>
|
<p className="text-base font-semibold text-warm-800">{dateLabel}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|
@ -97,7 +97,7 @@ function Hero() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-warm-500 font-medium uppercase tracking-wide">Tijd</p>
|
<p className="text-xs text-warm-500 font-medium uppercase tracking-wide">Tijd</p>
|
||||||
<p className="text-base font-semibold text-warm-800 whitespace-nowrap">9:00 - 14:00</p>
|
<p className="text-base font-semibold text-warm-800 whitespace-nowrap">{time}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|
@ -110,7 +110,7 @@ function Hero() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-warm-500 font-medium uppercase tracking-wide">Locatie</p>
|
<p className="text-xs text-warm-500 font-medium uppercase tracking-wide">Locatie</p>
|
||||||
<p className="text-base font-semibold text-warm-800 whitespace-nowrap">Utrecht</p>
|
<p className="text-base font-semibold text-warm-800 whitespace-nowrap">{location}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
import { WORKSHOP_CONFIG } from '../config/workshop';
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
function Pricing() {
|
function Pricing() {
|
||||||
const { isSoldOut } = WORKSHOP_CONFIG;
|
const { isSoldOut, price, priceExclBtw, maxParticipants, dateShort, time } = WORKSHOP_CONFIG;
|
||||||
// Wat is inbegrepen
|
// Wat is inbegrepen
|
||||||
const included = [
|
const included = [
|
||||||
"Volledige workshop (9:00 - 14:00)",
|
`Volledige workshop (${time})`,
|
||||||
"Lunch en onbeperkt koffie/thee",
|
"Lunch en onbeperkt koffie/thee",
|
||||||
"Online werkboek met alle commando's en tips",
|
"Online werkboek met alle commando's en tips",
|
||||||
"Toegang tot de besloten community voor vragen achteraf",
|
"Toegang tot de besloten community voor vragen achteraf",
|
||||||
|
|
@ -51,14 +51,14 @@ function Pricing() {
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<div className="flex items-baseline justify-center gap-2">
|
<div className="flex items-baseline justify-center gap-2">
|
||||||
<span className="text-5xl md:text-6xl font-display font-bold text-warm-900">
|
<span className="text-5xl md:text-6xl font-display font-bold text-warm-900">
|
||||||
€399
|
{price}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-warm-500 text-lg">
|
<span className="text-warm-500 text-lg">
|
||||||
per persoon
|
per persoon
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-warm-500 mt-1">
|
<p className="text-warm-500 mt-1">
|
||||||
excl. BTW
|
{priceExclBtw}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ function Pricing() {
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-coral-700">
|
<p className="font-semibold text-coral-700">
|
||||||
Maximaal 8 deelnemers per workshop
|
Maximaal {maxParticipants} deelnemers per workshop
|
||||||
</p>
|
</p>
|
||||||
<p className="text-coral-600 text-sm mt-1">
|
<p className="text-coral-600 text-sm mt-1">
|
||||||
Dit is bewust klein gehouden zodat iedereen persoonlijke aandacht krijgt.
|
Dit is bewust klein gehouden zodat iedereen persoonlijke aandacht krijgt.
|
||||||
|
|
@ -123,7 +123,7 @@ function Pricing() {
|
||||||
to={isSoldOut ? PAYMENT_CONFIG.WAITLIST_URL : PAYMENT_CONFIG.SIGNUP_URL}
|
to={isSoldOut ? PAYMENT_CONFIG.WAITLIST_URL : PAYMENT_CONFIG.SIGNUP_URL}
|
||||||
className="btn-primary w-full text-center block"
|
className="btn-primary w-full text-center block"
|
||||||
>
|
>
|
||||||
{isSoldOut ? 'Zet me op de wachtlijst' : 'Doe mee op 3 april'}
|
{isSoldOut ? 'Zet me op de wachtlijst' : `Doe mee op ${dateShort}`}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Annuleringsbeleid als vertrouwenssignaal */}
|
{/* Annuleringsbeleid als vertrouwenssignaal */}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@
|
||||||
* Lunch moment wordt visueel uitgelicht.
|
* Lunch moment wordt visueel uitgelicht.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
|
const { timeStart, timeEnd } = WORKSHOP_CONFIG;
|
||||||
|
|
||||||
function Program() {
|
function Program() {
|
||||||
// Programma onderdelen
|
// Programma onderdelen
|
||||||
const schedule = [
|
const schedule = [
|
||||||
|
|
@ -45,13 +49,13 @@ function Program() {
|
||||||
isBreak: false
|
isBreak: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
time: "13:45",
|
time: "14:30",
|
||||||
title: "Afronden en vervolgstappen",
|
title: "Afronden en vervolgstappen",
|
||||||
description: "We kijken naar wat je hebt gebouwd. Je krijgt concrete vervolgstappen die passen bij waar jij nu staat.",
|
description: "We kijken naar wat je hebt gebouwd. Je krijgt concrete vervolgstappen die passen bij waar jij nu staat.",
|
||||||
isBreak: false
|
isBreak: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
time: "14:00",
|
time: "15:00",
|
||||||
title: "Einde",
|
title: "Einde",
|
||||||
description: null,
|
description: null,
|
||||||
isBreak: false
|
isBreak: false
|
||||||
|
|
@ -66,7 +70,7 @@ function Program() {
|
||||||
Zo ziet je dag eruit
|
Zo ziet je dag eruit
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-warm-600 text-center mb-12 max-w-2xl mx-auto">
|
<p className="text-warm-600 text-center mb-12 max-w-2xl mx-auto">
|
||||||
Van 9:00 tot 14:00 werk je stap voor stap naar je eigen werkende project.
|
Van {timeStart} tot {timeEnd} werk je stap voor stap naar je eigen werkende project.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Timeline */}
|
{/* Timeline */}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
import { WORKSHOP_CONFIG } from '../config/workshop';
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
function StickyBar() {
|
function StickyBar() {
|
||||||
const { availableSpots, isSoldOut } = WORKSHOP_CONFIG;
|
const { availableSpots, isSoldOut, dateStickyBar, location, price, priceExclBtw } = WORKSHOP_CONFIG;
|
||||||
|
|
||||||
// State om te bepalen of de bar zichtbaar moet zijn
|
// State om te bepalen of de bar zichtbaar moet zijn
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
@ -51,14 +51,14 @@ function StickyBar() {
|
||||||
<svg className="w-4 h-4 text-coral-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-coral-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
3 april 2026
|
{dateStickyBar}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<svg className="w-4 h-4 text-coral-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-coral-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
Utrecht
|
{location}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1 text-coral-600 font-medium">
|
<span className="flex items-center gap-1 text-coral-600 font-medium">
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -72,7 +72,7 @@ function StickyBar() {
|
||||||
{/* Right: Price + CTA */}
|
{/* Right: Price + CTA */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="font-display font-bold text-xl text-warm-900">
|
<span className="font-display font-bold text-xl text-warm-900">
|
||||||
€399 <span className="text-sm font-normal text-warm-500">excl. BTW</span>
|
{price} <span className="text-sm font-normal text-warm-500">{priceExclBtw}</span>
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
to={isSoldOut ? PAYMENT_CONFIG.WAITLIST_URL : PAYMENT_CONFIG.SIGNUP_URL}
|
to={isSoldOut ? PAYMENT_CONFIG.WAITLIST_URL : PAYMENT_CONFIG.SIGNUP_URL}
|
||||||
|
|
@ -92,10 +92,10 @@ function StickyBar() {
|
||||||
{/* Left: Prijs en datum */}
|
{/* Left: Prijs en datum */}
|
||||||
<div>
|
<div>
|
||||||
<div className="font-display font-bold text-lg text-warm-900">
|
<div className="font-display font-bold text-lg text-warm-900">
|
||||||
€399 <span className="text-xs font-normal text-warm-500">excl. BTW</span>
|
{price} <span className="text-xs font-normal text-warm-500">{priceExclBtw}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-warm-500">
|
<div className="text-xs text-warm-500">
|
||||||
3 april 2026 | Utrecht
|
{dateStickyBar} | {location}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-coral-600 font-medium">
|
<div className="text-xs text-coral-600 font-medium">
|
||||||
{isSoldOut ? 'Volgeboekt' : `Nog ${availableSpots} ${availableSpots === 1 ? 'plek' : 'plekken'} beschikbaar`}
|
{isSoldOut ? 'Volgeboekt' : `Nog ${availableSpots} ${availableSpots === 1 ? 'plek' : 'plekken'} beschikbaar`}
|
||||||
|
|
|
||||||
44
src/components/TallyForm.jsx
Normal file
44
src/components/TallyForm.jsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* TallyForm.jsx - Gedeeld component voor embedded Tally formulieren
|
||||||
|
*
|
||||||
|
* Gebruik: <TallyForm formId="XxGBrV" title="Inschrijving Claude Code Workshop" />
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
function TallyForm({ formId, title }) {
|
||||||
|
useEffect(() => {
|
||||||
|
const scriptUrl = 'https://tally.so/widgets/embed.js';
|
||||||
|
|
||||||
|
if (typeof window.Tally !== 'undefined') {
|
||||||
|
window.Tally.loadEmbeds();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.querySelector(`script[src="${scriptUrl}"]`)) {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = scriptUrl;
|
||||||
|
script.onload = () => {
|
||||||
|
if (typeof window.Tally !== 'undefined') {
|
||||||
|
window.Tally.loadEmbeds();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<iframe
|
||||||
|
data-tally-src={`https://tally.so/embed/${formId}?alignLeft=1&hideTitle=1&transparentBackground=1&dynamicHeight=1`}
|
||||||
|
loading="lazy"
|
||||||
|
width="100%"
|
||||||
|
height="300"
|
||||||
|
frameBorder="0"
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TallyForm;
|
||||||
|
|
@ -19,3 +19,4 @@ export { default as FinalCTA } from './FinalCTA';
|
||||||
export { default as Footer } from './Footer';
|
export { default as Footer } from './Footer';
|
||||||
export { default as StickyBar } from './StickyBar';
|
export { default as StickyBar } from './StickyBar';
|
||||||
export { default as CookieBanner } from './CookieBanner';
|
export { default as CookieBanner } from './CookieBanner';
|
||||||
|
export { default as TallyForm } from './TallyForm';
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,6 @@
|
||||||
export const PAYMENT_CONFIG = {
|
export const PAYMENT_CONFIG = {
|
||||||
SIGNUP_URL: '/inschrijven',
|
SIGNUP_URL: '/inschrijven',
|
||||||
WAITLIST_URL: '/wachtlijst-inschrijven',
|
WAITLIST_URL: '/wachtlijst-inschrijven',
|
||||||
|
SIGNUP_TALLY_ID: 'kd7y81',
|
||||||
WAITLIST_TALLY_ID: 'kdyPJZ',
|
WAITLIST_TALLY_ID: 'kdyPJZ',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,44 @@
|
||||||
/**
|
/**
|
||||||
* workshop.js - Workshop details configuratie
|
* workshop.js - Workshop details configuratie
|
||||||
*
|
*
|
||||||
* Centrale plek voor beschikbaarheid en andere workshopdetails.
|
* CENTRALE PLEK voor alle workshopdetails.
|
||||||
* Pas availableSpots hier aan als er een plek verkocht is.
|
* Bij een nieuwe editie: pas alleen dit bestand aan.
|
||||||
* Zet isSoldOut op true als alle plekken weg zijn - activeert wachtlijstmodus op de hele site.
|
* Alle datum-velden tegelijk bijwerken (ze beschrijven dezelfde dag).
|
||||||
|
*
|
||||||
|
* Beschikbaarheid:
|
||||||
|
* Pas availableSpots aan als er een plek verkocht is.
|
||||||
|
* Zet isSoldOut op true als alle plekken weg zijn - activeert wachtlijstmodus.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const WORKSHOP_CONFIG = {
|
export const WORKSHOP_CONFIG = {
|
||||||
|
// Beschikbaarheid
|
||||||
totalSpots: 8,
|
totalSpots: 8,
|
||||||
availableSpots: 0,
|
availableSpots: 8,
|
||||||
isSoldOut: true,
|
isSoldOut: false,
|
||||||
|
|
||||||
|
// Datum - alle varianten beschrijven dezelfde dag, altijd samen bijwerken
|
||||||
|
date: 'woensdag 13 mei 2026', // lowercase, voor midden in een zin
|
||||||
|
dateShort: '13 mei', // voor buttons en korte verwijzingen
|
||||||
|
dateLabel: 'Woensdag 13 mei', // voor het Hero-datumblokje (geen jaar)
|
||||||
|
dateFull: 'Woensdag 13 mei 2026', // voor Footer-lijstitem (met jaar)
|
||||||
|
dateStickyBar: '13 mei 2026', // voor de sticky balk (geen weekdag)
|
||||||
|
|
||||||
|
// Tijd
|
||||||
|
time: '9:00 - 15:00',
|
||||||
|
timeStart: '9:00',
|
||||||
|
timeEnd: '15:00',
|
||||||
|
|
||||||
|
// Locatie
|
||||||
|
location: 'Utrecht',
|
||||||
|
venue: 'Wonders of Work, Utrecht',
|
||||||
|
|
||||||
|
// Deelnemers
|
||||||
|
maxParticipants: 8,
|
||||||
|
|
||||||
|
// Prijs
|
||||||
|
price: '€399',
|
||||||
|
priceExclBtw: 'excl. BTW',
|
||||||
|
|
||||||
|
// Contact
|
||||||
|
email: 'frank@frankmeeuwsen.com',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,33 +5,12 @@
|
||||||
* Na het invullen van het formulier redirect Tally naar de Mollie betaalpagina.
|
* Na het invullen van het formulier redirect Tally naar de Mollie betaalpagina.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
import TallyForm from '../components/TallyForm';
|
||||||
|
|
||||||
function Signup() {
|
function Signup() {
|
||||||
// Laad het Tally embed script zodra de pagina mount
|
|
||||||
useEffect(() => {
|
|
||||||
const scriptUrl = 'https://tally.so/widgets/embed.js';
|
|
||||||
|
|
||||||
// Check of het script al geladen is
|
|
||||||
if (typeof window.Tally !== 'undefined') {
|
|
||||||
window.Tally.loadEmbeds();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Script nog niet geladen? Voeg het toe
|
|
||||||
if (!document.querySelector(`script[src="${scriptUrl}"]`)) {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = scriptUrl;
|
|
||||||
script.onload = () => {
|
|
||||||
if (typeof window.Tally !== 'undefined') {
|
|
||||||
window.Tally.loadEmbeds();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.body.appendChild(script);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-warm-50">
|
<div className="min-h-screen bg-warm-50">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -58,21 +37,15 @@ function Signup() {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Embedded Tally formulier */}
|
{/* Embedded Tally formulier */}
|
||||||
<div className="card">
|
<TallyForm
|
||||||
<iframe
|
formId={PAYMENT_CONFIG.SIGNUP_TALLY_ID}
|
||||||
data-tally-src="https://tally.so/embed/XxGBrV?alignLeft=1&hideTitle=1&transparentBackground=1&dynamicHeight=1"
|
|
||||||
loading="lazy"
|
|
||||||
width="100%"
|
|
||||||
height="300"
|
|
||||||
frameBorder="0"
|
|
||||||
title="Inschrijving Claude Code Workshop"
|
title="Inschrijving Claude Code Workshop"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Workshop samenvatting */}
|
{/* Workshop samenvatting */}
|
||||||
<div className="mt-8 text-center text-sm text-warm-500 space-y-1">
|
<div className="mt-8 text-center text-sm text-warm-500 space-y-1">
|
||||||
<p>Claude Code Workshop | Vrijdag 3 april 2026 | 9:00 - 14:00 | Utrecht</p>
|
<p>Claude Code Workshop | {WORKSHOP_CONFIG.dateFull} | {WORKSHOP_CONFIG.time} | {WORKSHOP_CONFIG.location}</p>
|
||||||
<p>EUR 399 excl. BTW (EUR 482,79 incl. BTW)</p>
|
<p>{WORKSHOP_CONFIG.price} {WORKSHOP_CONFIG.priceExclBtw}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { WORKSHOP_CONFIG } from '../config/workshop';
|
||||||
|
|
||||||
function ThankYou() {
|
function ThankYou() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -36,8 +37,11 @@ function ThankYou() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="heading-hero mb-4">Je bent erbij!</h1>
|
<h1 className="heading-hero mb-4">Je bent erbij!</h1>
|
||||||
<p className="text-xl text-warm-600 mb-10">
|
<p className="text-xl text-warm-600 mb-4">
|
||||||
Je inschrijving voor de Claude Code Workshop is bevestigd.
|
Je betaling is ontvangen en je inschrijving is bevestigd.
|
||||||
|
</p>
|
||||||
|
<p className="text-warm-500 mb-10">
|
||||||
|
Binnen 24 uur ontvang je een factuur en bevestigingsmail.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Workshop details */}
|
{/* Workshop details */}
|
||||||
|
|
@ -48,20 +52,20 @@ function ThankYou() {
|
||||||
<svg className="w-5 h-5 text-coral-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 text-coral-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Vrijdag 3 april 2026</span>
|
<span>{WORKSHOP_CONFIG.dateFull}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<svg className="w-5 h-5 text-coral-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 text-coral-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>9:00 - 14:00 uur</span>
|
<span>{WORKSHOP_CONFIG.time} uur</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<svg className="w-5 h-5 text-coral-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 text-coral-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Utrecht</span>
|
<span>{WORKSHOP_CONFIG.venue}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -72,7 +76,7 @@ function ThankYou() {
|
||||||
<ol className="space-y-4 text-warm-600">
|
<ol className="space-y-4 text-warm-600">
|
||||||
<li className="flex gap-3">
|
<li className="flex gap-3">
|
||||||
<span className="flex-shrink-0 w-7 h-7 bg-coral-100 text-coral-600 rounded-full flex items-center justify-center font-semibold text-sm">1</span>
|
<span className="flex-shrink-0 w-7 h-7 bg-coral-100 text-coral-600 rounded-full flex items-center justify-center font-semibold text-sm">1</span>
|
||||||
<span>Je ontvangt een bevestigingsmail met je factuur.</span>
|
<span>Binnen 24 uur ontvang je een factuur en bevestigingsmail.</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex gap-3">
|
<li className="flex gap-3">
|
||||||
<span className="flex-shrink-0 w-7 h-7 bg-coral-100 text-coral-600 rounded-full flex items-center justify-center font-semibold text-sm">2</span>
|
<span className="flex-shrink-0 w-7 h-7 bg-coral-100 text-coral-600 rounded-full flex items-center justify-center font-semibold text-sm">2</span>
|
||||||
|
|
@ -80,7 +84,7 @@ function ThankYou() {
|
||||||
</li>
|
</li>
|
||||||
<li className="flex gap-3">
|
<li className="flex gap-3">
|
||||||
<span className="flex-shrink-0 w-7 h-7 bg-coral-100 text-coral-600 rounded-full flex items-center justify-center font-semibold text-sm">3</span>
|
<span className="flex-shrink-0 w-7 h-7 bg-coral-100 text-coral-600 rounded-full flex items-center justify-center font-semibold text-sm">3</span>
|
||||||
<span>Op 3 april neem je je laptop mee en gaan we aan de slag!</span>
|
<span>Op {WORKSHOP_CONFIG.dateShort} neem je je laptop mee en gaan we aan de slag!</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -88,8 +92,8 @@ function ThankYou() {
|
||||||
{/* Contact */}
|
{/* Contact */}
|
||||||
<p className="text-warm-500">
|
<p className="text-warm-500">
|
||||||
Vragen? Mail naar{' '}
|
Vragen? Mail naar{' '}
|
||||||
<a href="mailto:frank@frankmeeuwsen.com" className="text-coral-500 hover:text-coral-600">
|
<a href={`mailto:${WORKSHOP_CONFIG.email}`} className="text-coral-500 hover:text-coral-600">
|
||||||
frank@frankmeeuwsen.com
|
{WORKSHOP_CONFIG.email}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,32 +6,11 @@
|
||||||
* Tally ID: kdyPJZ (https://tally.so/r/kdyPJZ)
|
* Tally ID: kdyPJZ (https://tally.so/r/kdyPJZ)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { PAYMENT_CONFIG } from '../config/payment';
|
import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
|
import TallyForm from '../components/TallyForm';
|
||||||
|
|
||||||
function WaitlistSignup() {
|
function WaitlistSignup() {
|
||||||
// Laad het Tally embed script zodra de pagina mount
|
|
||||||
useEffect(() => {
|
|
||||||
const scriptUrl = 'https://tally.so/widgets/embed.js';
|
|
||||||
|
|
||||||
if (typeof window.Tally !== 'undefined') {
|
|
||||||
window.Tally.loadEmbeds();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!document.querySelector(`script[src="${scriptUrl}"]`)) {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = scriptUrl;
|
|
||||||
script.onload = () => {
|
|
||||||
if (typeof window.Tally !== 'undefined') {
|
|
||||||
window.Tally.loadEmbeds();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.body.appendChild(script);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-warm-50">
|
<div className="min-h-screen bg-warm-50">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -68,17 +47,11 @@ function WaitlistSignup() {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Embedded Tally wachtlijst formulier */}
|
{/* Embedded Tally wachtlijst formulier */}
|
||||||
<div className="card">
|
<TallyForm
|
||||||
<iframe
|
formId={PAYMENT_CONFIG.WAITLIST_TALLY_ID}
|
||||||
data-tally-src={`https://tally.so/embed/${PAYMENT_CONFIG.WAITLIST_TALLY_ID}?alignLeft=1&hideTitle=1&transparentBackground=1&dynamicHeight=1`}
|
|
||||||
loading="lazy"
|
|
||||||
width="100%"
|
|
||||||
height="300"
|
|
||||||
frameBorder="0"
|
|
||||||
title="Wachtlijst Claude Code Workshop"
|
title="Wachtlijst Claude Code Workshop"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue