feat: Mollie betaalflow, inschrijfpagina, SEO en deploy
Betaalflow via Tally formulier (e-mailverzameling) + Mollie Payment Links. Inschrijfpagina (/inschrijven), bedankt-pagina (/bedankt), OpenGraph tags, favicon, Umami analytics, base path config en deploy script toegevoegd. Site live op frankmeeuwsen.com/workshopclaudecode/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
13eca07719
commit
e791e06f1d
16 changed files with 347 additions and 25 deletions
41
LOGBOEK.md
41
LOGBOEK.md
|
|
@ -66,7 +66,40 @@
|
||||||
- Workshop details (datum/tijd/locatie) compact weergegeven met iconen
|
- Workshop details (datum/tijd/locatie) compact weergegeven met iconen
|
||||||
|
|
||||||
### Volgende sessie
|
### Volgende sessie
|
||||||
- [ ] Inschrijfformulier/betaallink toevoegen
|
- [x] Inschrijfformulier/betaallink toevoegen
|
||||||
- [ ] Deploy naar productie
|
- [x] Deploy naar productie
|
||||||
- [ ] SEO meta tags toevoegen (Open Graph, Twitter Cards)
|
- [x] SEO meta tags toevoegen (Open Graph, Twitter Cards)
|
||||||
- [ ] Formulier validatie en betalingsflow
|
- [x] Formulier validatie en betalingsflow
|
||||||
|
|
||||||
|
## 2026-02-10 - Sessie 4: Mollie betaalflow, SEO en deploy
|
||||||
|
|
||||||
|
### Wat is gebouwd
|
||||||
|
- Mollie Payment Links aangemaakt (test + live) voor workshop betaling
|
||||||
|
- Tally formulier geintegreerd voor e-mailverzameling voor inschrijving
|
||||||
|
- Inschrijfpagina (/inschrijven) met embedded Tally formulier
|
||||||
|
- Bedankt-pagina (/bedankt) voor na succesvolle betaling
|
||||||
|
- Payment config bestand (src/config/payment.js) voor centrale URL configuratie
|
||||||
|
- CTA-knoppen (Pricing, FinalCTA, StickyBar) gelinkt naar inschrijfpagina via React Router Link
|
||||||
|
- Hero CTA scrollt nog steeds naar pricing sectie
|
||||||
|
- OpenGraph en Twitter Card meta tags in index.html
|
||||||
|
- OG-afbeelding (1200x630) gemaakt in Canva met site-kleuren en headline
|
||||||
|
- SVG favicon (coral CC icoon)
|
||||||
|
- Umami analytics script toegevoegd (zelfde ID als frankmeeuwsen.com)
|
||||||
|
- Canonical URL ingesteld
|
||||||
|
- Base path config (/workshopclaudecode/) alleen voor production builds
|
||||||
|
- .htaccess voor SPA-routing in submap
|
||||||
|
- Deploy script (deploy.sh) voor build + upload naar Coolify Docker container
|
||||||
|
- Site live gedeployd op https://frankmeeuwsen.com/workshopclaudecode/
|
||||||
|
|
||||||
|
### Technische beslissingen
|
||||||
|
- Tally als tussenstap voor e-mailverzameling (geen backend nodig, AVG-compliant, gratis)
|
||||||
|
- Betaalflow: CTA -> /inschrijven (Tally embed) -> Mollie betaalpagina -> /bedankt
|
||||||
|
- Vite base path conditioneel: / voor dev, /workshopclaudecode/ voor build
|
||||||
|
- React Router basename via import.meta.env.BASE_URL (werkt automatisch in beide modes)
|
||||||
|
- Hero afbeelding pad via import.meta.env.BASE_URL voor correcte submap-verwijzing
|
||||||
|
- Deploy via rsync + docker cp naar WordPress container op Coolify
|
||||||
|
|
||||||
|
### Volgende sessie
|
||||||
|
- [ ] Testimonials toevoegen zodra beschikbaar
|
||||||
|
- [ ] Structured data (JSON-LD Event schema) toevoegen
|
||||||
|
- [ ] Eerste testbetaling via live link verifiëren
|
||||||
|
|
|
||||||
BIN
content/Social image CC workshop.png
Normal file
BIN
content/Social image CC workshop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 674 KiB |
26
deploy.sh
Executable file
26
deploy.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Deploy script voor Claude Code Workshop sales page
|
||||||
|
# Bouwt de site en deployt naar de Coolify WordPress container
|
||||||
|
#
|
||||||
|
# Gebruik: ./deploy.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SERVER="coolify"
|
||||||
|
CONTAINER="wordpress-d0wko4gskokosssogcw8040g"
|
||||||
|
REMOTE_PATH="/var/www/html/workshopclaudecode"
|
||||||
|
TMP_PATH="/tmp/workshopclaudecode"
|
||||||
|
|
||||||
|
echo "1/4 - Building..."
|
||||||
|
npm run build --silent
|
||||||
|
|
||||||
|
echo "2/4 - Uploading naar server..."
|
||||||
|
rsync -avz --quiet dist/ "$SERVER:$TMP_PATH/"
|
||||||
|
|
||||||
|
echo "3/4 - Kopieren naar container..."
|
||||||
|
ssh "$SERVER" "docker cp $TMP_PATH/. $CONTAINER:$REMOTE_PATH/ && docker exec $CONTAINER chown -R www-data:www-data $REMOTE_PATH/"
|
||||||
|
|
||||||
|
echo "4/4 - Opruimen..."
|
||||||
|
ssh "$SERVER" "rm -rf $TMP_PATH"
|
||||||
|
|
||||||
|
echo "Done! https://frankmeeuwsen.com/workshopclaudecode/"
|
||||||
25
index.html
25
index.html
|
|
@ -2,11 +2,34 @@
|
||||||
<html lang="nl">
|
<html lang="nl">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Claude Code Workshop - Van nieuwsgierig naar praktisch aan de slag</title>
|
<title>Claude Code Workshop - Van nieuwsgierig naar praktisch aan de slag</title>
|
||||||
<meta name="description" content="Leer Claude Code in 1 ochtend. Van installatie tot werkende applicaties. Kleine groep, hands-on, 6 maart 2026 in Utrecht." />
|
<meta name="description" content="Leer Claude Code in 1 ochtend. Van installatie tot werkende applicaties. Kleine groep, hands-on, 6 maart 2026 in Utrecht." />
|
||||||
|
|
||||||
|
<!-- Canonical -->
|
||||||
|
<link rel="canonical" href="https://frankmeeuwsen.com/workshopclaudecode/" />
|
||||||
|
|
||||||
|
<!-- OpenGraph -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:locale" content="nl_NL" />
|
||||||
|
<meta property="og:title" content="Claude Code Workshop - Maak zelf de tools die je nu inhuurt" />
|
||||||
|
<meta property="og:description" content="In 1 ochtend van nieuwsgierig naar praktisch aan de slag met Claude Code. Kleine groep, hands-on. 6 maart 2026 in Utrecht." />
|
||||||
|
<meta property="og:url" content="https://frankmeeuwsen.com/workshopclaudecode/" />
|
||||||
|
<meta property="og:image" content="https://frankmeeuwsen.com/workshopclaudecode/og-image.png" />
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:site_name" content="Frank Meeuwsen" />
|
||||||
|
|
||||||
|
<!-- Twitter Card -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="Claude Code Workshop - Maak zelf de tools die je nu inhuurt" />
|
||||||
|
<meta name="twitter:description" content="In 1 ochtend van nieuwsgierig naar praktisch aan de slag met Claude Code. Kleine groep, hands-on. 6 maart 2026 in Utrecht." />
|
||||||
|
<meta name="twitter:image" content="https://frankmeeuwsen.com/workshopclaudecode/og-image.png" />
|
||||||
|
|
||||||
|
<!-- Umami Analytics -->
|
||||||
|
<script defer src="https://umami.dutchstack.nl/script.js" data-website-id="bceaa80a-f2be-4215-8421-3a78d14601c3"></script>
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|
|
||||||
13
public/.htaccess
Normal file
13
public/.htaccess
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# SPA routing: stuur alle requests naar index.html
|
||||||
|
# zodat React Router de routes /bedankt, /inschrijven etc. kan afhandelen
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /workshopclaudecode/
|
||||||
|
|
||||||
|
# Als het bestand of directory bestaat, serveer het direct
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
|
||||||
|
# Alles anders naar index.html
|
||||||
|
RewriteRule ^ index.html [QSA,L]
|
||||||
|
</IfModule>
|
||||||
4
public/favicon.svg
Normal file
4
public/favicon.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<rect width="32" height="32" rx="6" fill="#F25C3D"/>
|
||||||
|
<text x="16" y="22" font-family="system-ui, sans-serif" font-weight="700" font-size="18" fill="white" text-anchor="middle">CC</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 257 B |
BIN
public/og-image.png
Normal file
BIN
public/og-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 674 KiB |
|
|
@ -2,9 +2,12 @@
|
||||||
* FinalCTA.jsx - Afsluitende call-to-action sectie
|
* FinalCTA.jsx - Afsluitende call-to-action sectie
|
||||||
*
|
*
|
||||||
* Coral-500 achtergrond met witte tekst.
|
* Coral-500 achtergrond met witte tekst.
|
||||||
* Bevat datum, locatie en primaire CTA button (wit met coral tekst).
|
* Bevat datum, locatie en CTA button die naar Mollie betaalpagina linkt.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
|
|
||||||
function FinalCTA() {
|
function FinalCTA() {
|
||||||
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">
|
||||||
|
|
@ -55,16 +58,16 @@ function FinalCTA() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button - wit met coral tekst */}
|
{/* CTA Button - naar inschrijfpagina */}
|
||||||
<a
|
<Link
|
||||||
href="#inschrijven"
|
to={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"
|
||||||
>
|
>
|
||||||
Doe mee op 6 maart
|
Doe mee op 6 maart
|
||||||
<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>
|
||||||
</a>
|
</Link>
|
||||||
|
|
||||||
{/* Contact info */}
|
{/* Contact info */}
|
||||||
<p className="mt-8 text-coral-200">
|
<p className="mt-8 text-coral-200">
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ function Hero() {
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<img
|
<img
|
||||||
src="/frank-workshop-claude-code.jpg"
|
src={`${import.meta.env.BASE_URL}frank-workshop-claude-code.jpg`}
|
||||||
alt="Frank Meeuwsen geeft een workshop over Claude Code"
|
alt="Frank Meeuwsen geeft een workshop over Claude Code"
|
||||||
className="rounded-2xl shadow-lg w-full"
|
className="rounded-2xl shadow-lg w-full"
|
||||||
style={{ filter: 'brightness(1.12) contrast(1.05) saturate(0.95)' }}
|
style={{ filter: 'brightness(1.12) contrast(1.05) saturate(0.95)' }}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
* Pricing.jsx - "Investering" sectie
|
* Pricing.jsx - "Investering" sectie
|
||||||
*
|
*
|
||||||
* Toont de prijs, inclusief lijst en urgency element.
|
* Toont de prijs, inclusief lijst en urgency element.
|
||||||
* Bevat primaire CTA button naar inschrijving.
|
* Bevat primaire CTA button die naar Mollie betaalpagina linkt.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
|
|
||||||
function Pricing() {
|
function Pricing() {
|
||||||
// Wat is inbegrepen
|
// Wat is inbegrepen
|
||||||
const included = [
|
const included = [
|
||||||
|
|
@ -113,13 +116,13 @@ function Pricing() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button */}
|
{/* CTA Button - naar inschrijfpagina */}
|
||||||
<a
|
<Link
|
||||||
href="#inschrijven"
|
to={PAYMENT_CONFIG.SIGNUP_URL}
|
||||||
className="btn-primary w-full text-center block"
|
className="btn-primary w-full text-center block"
|
||||||
>
|
>
|
||||||
Doe mee op 6 maart
|
Doe mee op 6 maart
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { PAYMENT_CONFIG } from '../config/payment';
|
||||||
|
|
||||||
function StickyBar() {
|
function StickyBar() {
|
||||||
// State om te bepalen of de bar zichtbaar moet zijn
|
// State om te bepalen of de bar zichtbaar moet zijn
|
||||||
|
|
@ -63,12 +65,12 @@ function StickyBar() {
|
||||||
<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>
|
€399 <span className="text-sm font-normal text-warm-500">excl. BTW</span>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<Link
|
||||||
href="#inschrijven"
|
to={PAYMENT_CONFIG.SIGNUP_URL}
|
||||||
className="px-6 py-2 bg-coral-500 text-white font-semibold rounded-lg shadow-sm hover:bg-coral-600 transition-colors"
|
className="px-6 py-2 bg-coral-500 text-white font-semibold rounded-lg shadow-sm hover:bg-coral-600 transition-colors"
|
||||||
>
|
>
|
||||||
Inschrijven
|
Inschrijven
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -89,12 +91,12 @@ function StickyBar() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: CTA */}
|
{/* Right: CTA */}
|
||||||
<a
|
<Link
|
||||||
href="#inschrijven"
|
to={PAYMENT_CONFIG.SIGNUP_URL}
|
||||||
className="flex-shrink-0 px-6 py-3 bg-coral-500 text-white font-semibold rounded-lg shadow-sm hover:bg-coral-600 transition-colors"
|
className="flex-shrink-0 px-6 py-3 bg-coral-500 text-white font-semibold rounded-lg shadow-sm hover:bg-coral-600 transition-colors"
|
||||||
>
|
>
|
||||||
Inschrijven
|
Inschrijven
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
11
src/config/payment.js
Normal file
11
src/config/payment.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* payment.js - Betaalflow configuratie
|
||||||
|
*
|
||||||
|
* CTA-knoppen linken naar de inschrijfpagina met embedded Tally formulier.
|
||||||
|
* Na het formulier redirect Tally naar de Mollie betaalpagina.
|
||||||
|
* Test/live Mollie link wordt ingesteld in Tally's redirect URL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const PAYMENT_CONFIG = {
|
||||||
|
SIGNUP_URL: '/inschrijven',
|
||||||
|
};
|
||||||
|
|
@ -5,14 +5,18 @@ import './index.css'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
import Privacy from './pages/Privacy.jsx'
|
import Privacy from './pages/Privacy.jsx'
|
||||||
import Terms from './pages/Terms.jsx'
|
import Terms from './pages/Terms.jsx'
|
||||||
|
import ThankYou from './pages/ThankYou.jsx'
|
||||||
|
import Signup from './pages/Signup.jsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter basename={import.meta.env.BASE_URL}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
<Route path="/privacy" element={<Privacy />} />
|
<Route path="/privacy" element={<Privacy />} />
|
||||||
<Route path="/voorwaarden" element={<Terms />} />
|
<Route path="/voorwaarden" element={<Terms />} />
|
||||||
|
<Route path="/inschrijven" element={<Signup />} />
|
||||||
|
<Route path="/bedankt" element={<ThankYou />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
|
|
|
||||||
90
src/pages/Signup.jsx
Normal file
90
src/pages/Signup.jsx
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
/**
|
||||||
|
* Signup.jsx - Inschrijfpagina met embedded Tally formulier
|
||||||
|
*
|
||||||
|
* Bezoekers komen hier via de CTA-knoppen op de sales page.
|
||||||
|
* Na het invullen van het formulier redirect Tally naar de Mollie betaalpagina.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen bg-warm-50">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-white border-b border-warm-200 py-6">
|
||||||
|
<div className="container-page">
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className="text-coral-500 hover:text-coral-600 transition-colors inline-flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
Terug naar workshop
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<main className="container-page py-12">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<h1 className="heading-hero mb-4 text-center">Inschrijven</h1>
|
||||||
|
<p className="text-center text-warm-600 mb-10">
|
||||||
|
Vul je gegevens in en je wordt doorgestuurd naar de betaalpagina.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Embedded Tally formulier */}
|
||||||
|
<div className="card">
|
||||||
|
<iframe
|
||||||
|
data-tally-src="https://tally.so/embed/0Q6v8A?alignLeft=1&hideTitle=1&transparentBackground=1&dynamicHeight=1"
|
||||||
|
loading="lazy"
|
||||||
|
width="100%"
|
||||||
|
height="300"
|
||||||
|
frameBorder="0"
|
||||||
|
title="Inschrijving Claude Code Workshop"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Workshop samenvatting */}
|
||||||
|
<div className="mt-8 text-center text-sm text-warm-500 space-y-1">
|
||||||
|
<p>Claude Code Workshop | Vrijdag 6 maart 2026 | 9:00 - 14:00 | Utrecht</p>
|
||||||
|
<p>EUR 399 excl. BTW (EUR 482,79 incl. BTW)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="bg-warm-900 text-warm-400 py-6">
|
||||||
|
<div className="container-page text-center text-sm">
|
||||||
|
© {new Date().getFullYear()} Frank Meeuwsen. Alle rechten voorbehouden.
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Signup;
|
||||||
108
src/pages/ThankYou.jsx
Normal file
108
src/pages/ThankYou.jsx
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
/**
|
||||||
|
* ThankYou.jsx - Bedankt-pagina na succesvolle betaling
|
||||||
|
*
|
||||||
|
* Bezoekers komen hier terecht na betaling via Mollie.
|
||||||
|
* Toont bevestiging, workshop details en vervolgstappen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
function ThankYou() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-warm-50">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-white border-b border-warm-200 py-6">
|
||||||
|
<div className="container-page">
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className="text-coral-500 hover:text-coral-600 transition-colors inline-flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
Terug naar homepage
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<main className="container-page py-12">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
{/* Checkmark icoon */}
|
||||||
|
<div className="mx-auto w-20 h-20 bg-teal-100 rounded-full flex items-center justify-center mb-8">
|
||||||
|
<svg className="w-10 h-10 text-teal-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="heading-hero mb-4">Je bent erbij!</h1>
|
||||||
|
<p className="text-xl text-warm-600 mb-10">
|
||||||
|
Je inschrijving voor de Claude Code Workshop is bevestigd.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Workshop details */}
|
||||||
|
<div className="card text-left mb-10">
|
||||||
|
<h2 className="heading-3 mb-4">Workshop details</h2>
|
||||||
|
<div className="space-y-3 text-warm-600">
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<span>Vrijdag 6 maart 2026</span>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>9:00 - 14:00 uur</span>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<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" />
|
||||||
|
</svg>
|
||||||
|
<span>Utrecht</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vervolgstappen */}
|
||||||
|
<div className="card text-left mb-10">
|
||||||
|
<h2 className="heading-3 mb-4">Wat gebeurt er nu?</h2>
|
||||||
|
<ol className="space-y-4 text-warm-600">
|
||||||
|
<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>Je ontvangt een bevestigingsmail met je factuur.</span>
|
||||||
|
</li>
|
||||||
|
<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>Een week voor de workshop ontvang je een mail met praktische informatie en voorbereidingsinstructies.</span>
|
||||||
|
</li>
|
||||||
|
<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>Op 6 maart neem je je laptop mee en gaan we aan de slag!</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact */}
|
||||||
|
<p className="text-warm-500">
|
||||||
|
Vragen? Mail naar{' '}
|
||||||
|
<a href="mailto:frank@frankmeeuwsen.com" className="text-coral-500 hover:text-coral-600">
|
||||||
|
frank@frankmeeuwsen.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="bg-warm-900 text-warm-400 py-6">
|
||||||
|
<div className="container-page text-center text-sm">
|
||||||
|
© {new Date().getFullYear()} Frank Meeuwsen. Alle rechten voorbehouden.
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ThankYou;
|
||||||
|
|
@ -2,6 +2,8 @@ import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
// base path alleen voor production build, lokaal blijft het op /
|
||||||
|
export default defineConfig(({ command }) => ({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
})
|
base: command === 'build' ? '/workshopclaudecode/' : '/',
|
||||||
|
}))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue