Eighty
← Blog

Van idee naar SaaS: bouw een slimme offertetool voor freelancers met Claude Code

9 mei 2026/7 min lezen/Door de Eighty redactie

Van idee naar SaaS: een slimme offertetool voor freelancers

Elke vrijdag hetzelfde gedoe. Je opent een leeg Word-document, typt je uurtarief in, rekent handmatig uit hoeveel uur iets kost, plakt een logo erbij en hoopt dat de klant snel betaalt. Freelancers en ZZP'ers verspillen gemiddeld twee uur per week aan offertes die er toch niet professioneel uitzien.

Deze zaterdag bouwen we daar iets op. Geen grote SaaS met tien features, maar een werkende MVP die je morgen al aan je eerste gebruiker kunt laten zien. We gebruiken Claude Code, Supabase en Stripe. Geen developer-achtergrond nodig.


Het probleem en de doelgroep

Doelgroep: Freelancers en ZZP'ers (designers, tekstschrijvers, consultants, coaches) die werken op uurtarief of projectbasis.

Kernpijn:

  • Offertes maken kost te veel tijd
  • Het ziet er niet professioneel uit
  • Je weet niet wanneer de klant de offerte heeft geopend
  • Opvolgen voelt ongemakkelijk als je niet weet of ze hem al hebben gelezen
  • Betaling loopt altijd via een aparte factuur-app

Wat we bouwen: Een web-app waar je als freelancer in twee minuten een offerte maakt, een unieke link deelt, ziet wanneer de klant hem opent, en de klant direct akkoord kan gaan en een aanbetaling kan doen via Stripe.


Stap 1: Zet je project op met Claude Code

Open Claude Code in een lege map. Begin met de basis:

Maak een nieuw Next.js 14 project aan met TypeScript en Tailwind CSS. Gebruik de App Router. Maak ook meteen een .env.local bestand aan met placeholder-waarden voor NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, STRIPE_SECRET_KEY en STRIPE_WEBHOOK_SECRET. Voeg een CLAUDE.md toe met daarin uitgelegd dat dit een offertetool is voor freelancers, gebouwd met Next.js, Supabase en Stripe.

Na deze prompt heb je een werkende Next.js-structuur en een CLAUDE.md die Claude in volgende sessies bijhoudt wat het project doet. Dat bespaart je straks veel context-herhaling.

De CLAUDE.md die Claude aanmaakt (controleer dit)

# Offertetool voor freelancers

## Wat dit is
Een web-app waarmee freelancers snel professionele offertes maken en delen.
Klanten kunnen offertes online bekijken, accorderen en een aanbetaling doen.

## Stack
- Next.js 14 met App Router en TypeScript
- Supabase voor database en authenticatie
- Stripe voor betalingen
- Tailwind CSS voor styling

## Conventies
- Alle database-calls via Supabase client
- Server Actions voor formulier-afhandeling
- Stripe Checkout voor betalingen
- Geen aparte backend server

Stap 2: Bouw het databaseschema in Supabase

Ga naar supabase.com, maak een gratis project aan en open de SQL-editor. Geef Claude Code dit verzoek:

Schrijf de SQL voor een Supabase-database met de volgende tabellen: users (id, email, naam, bedrijfsnaam, uurtarief, logo_url, btw_nummer), offertes (id, user_id, klant_naam, klant_email, titel, omschrijving, regels als JSONB, subtotaal, btw_percentage, totaal, status als enum met waarden 'concept', 'verstuurd', 'bekeken', 'geaccordeerd', 'betaald', aangemaakt_op, verloopdatum, stripe_payment_intent_id, unieke_token), offerte_events (id, offerte_id, event_type, aangemaakt_op, ip_adres). Voeg ook Row Level Security toe zodat gebruikers alleen hun eigen offertes zien.

Kopieer de SQL die Claude genereert en plak hem in de Supabase SQL-editor. Klik op Run.

-- Voorbeeld van wat Claude genereert (jij hoeft dit niet zelf te typen)
CREATE TYPE offerte_status AS ENUM (
  'concept', 'verstuurd', 'bekeken', 'geaccordeerd', 'betaald'
);

CREATE TABLE offertes (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id),
  klant_naam TEXT NOT NULL,
  klant_email TEXT NOT NULL,
  titel TEXT NOT NULL,
  regels JSONB NOT NULL DEFAULT '[]',
  subtotaal DECIMAL(10,2),
  btw_percentage INTEGER DEFAULT 21,
  totaal DECIMAL(10,2),
  status offerte_status DEFAULT 'concept',
  aangemaakt_op TIMESTAMPTZ DEFAULT NOW(),
  verloopdatum DATE,
  stripe_payment_intent_id TEXT,
  unieke_token UUID DEFAULT gen_random_uuid()
);

Stap 3: Bouw de offerte-editor

Dit is de kern van de app. De freelancer vult hier klantgegevens en losse regels in (omschrijving, aantal uur, tarief). Claude Code bouwt dit component voor je:

Bouw een React component OfferteEditor in components/OfferteEditor.tsx. Het formulier heeft velden voor: klant naam, klant e-mail, offerte titel, verloopdatum. Daaronder een dynamische lijst van 'regels' waarbij elke regel een omschrijving, aantal (uren of stuks) en prijs-per-eenheid heeft. Voeg een knop toe om een regel toe te voegen en een knop om een regel te verwijderen. Bereken automatisch het subtotaal, btw (21% als standaard, aanpasbaar) en totaal. Gebruik Tailwind voor styling en TypeScript types. Sla de staat op met useState.

Na deze prompt heb je een werkende editor. Dan voeg je de opslag toe:

Voeg een 'Sla op als concept' knop toe aan de OfferteEditor. Gebruik een Next.js Server Action in app/actions/offerte.ts om de offerte op te slaan in Supabase. Retourneer de id van de nieuwe offerte zodat we naar de detail-pagina kunnen navigeren.


Stap 4: De klantpagina met track-and-trace

Elke offerte krijgt een unieke URL die de freelancer naar de klant stuurt. Als de klant die opent, slaan we dat op.

Maak een pagina app/offerte/[token]/page.tsx die de offerte ophaalt op basis van het token. Toon de offerte netjes opgemaakt: bedrijfsnaam en logo van de freelancer bovenin, klantgegevens, een tabel met alle regels, subtotaal, btw en totaalbedrag. Voeg onderaan een grote groene knop toe: 'Akkoord en 50% aanbetaling doen'. Als de pagina laadt, stuur dan een Server Action aan die een rij aanmaakt in offerte_events met event_type 'bekeken' en update de offerte status naar 'bekeken' als die nog op 'verstuurd' stond.

Zo ziet de freelancer in z'n dashboard precies wanneer de klant de offerte heeft geopend.


Stap 5: Stripe-betaling koppelen

Dit is het stuk waar de meeste niet-developers afhaken. Claude Code maakt het gewoon voor je:

Maak een Server Action createStripeCheckout in app/actions/stripe.ts. Deze ontvangt een offerte-id, haalt de offerte op uit Supabase, berekent 50% van het totaal, en maakt een Stripe Checkout Session aan. Gebruik stripe.checkout.sessions.create met mode 'payment', het berekende bedrag in eurocenten, success_url naar /offerte/[token]/bedankt en cancel_url terug naar de offerte-pagina. Sla de session id op in de offerte in Supabase. Retourneer de checkout URL.

// Wat Claude genereert in app/actions/stripe.ts
'use server'

import Stripe from 'stripe'
import { createClient } from '@/lib/supabase/server'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function createStripeCheckout(offerteId: string) {
  const supabase = createClient()
  
  const { data: offerte } = await supabase
    .from('offertes')
    .select('*')
    .eq('id', offerteId)
    .single()
  
  const aanbetalingBedrag = Math.round((offerte.totaal *0.5)* 100)
  
  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [{
      price_data: {
        currency: 'eur',
        product_data: { name: `Aanbetaling: ${offerte.titel}` },
        unit_amount: aanbetalingBedrag,
      },
      quantity: 1,
    }],
    success_url: `${process.env.NEXT_PUBLIC_URL}/offerte/${offerte.unieke_token}/bedankt`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/offerte/${offerte.unieke_token}`,
    metadata: { offerte_id: offerteId },
  })
  
  return session.url
}

Voeg daarna de webhook toe zodat de status automatisch bijgewerkt wordt als de betaling succesvol is:

Maak een webhook endpoint in app/api/stripe/webhook/route.ts. Luister naar het event checkout.session.completed. Als dat binnenkomt, haal dan de offerte_id op uit de metadata en update de offerte status naar 'betaald' in Supabase. Voeg ook een rij toe in offerte_events met event_type 'betaald'.


Stap 6: Het freelancer-dashboard

De freelancer wil een overzicht zien van alle offertes en hun status:

Bouw een dashboard-pagina op app/dashboard/page.tsx. Haal alle offertes op van de ingelogde gebruiker uit Supabase, gesorteerd op aangemaakt_op (nieuwste eerst). Toon ze als een overzichtstabel met kolommen: klant naam, titel, totaalbedrag, status (als gekleurde badge: grijs voor concept, blauw voor verstuurd, oranje voor bekeken, groen voor geaccordeerd, donkergroen voor betaald) en datum. Voeg een knop toe 'Nieuwe offerte' die naar /offerte/nieuw navigeert. Voeg per rij een kopieer-link-knop toe die de unieke offerte-URL naar het klembord kopieert.


Stap 7: Deploy naar Vercel

Maak een vercel.json aan met de juiste configuratie voor dit Next.js project. Geef me ook een stap-voor-stap instructie welke environment variables ik moet instellen in het Vercel dashboard voor Supabase en Stripe.

Na de deploy ga je naar Stripe en stel je de webhook in op https://jouwdomein.vercel.app/api/stripe/webhook.


Wat zou je daarna nog willen toevoegen?

De MVP die je nu hebt gebouwd werkt. Maar er zijn logische volgende stappen:

  • PDF-export: de klant of freelancer wil de offerte kunnen downloaden. Prompt: "Voeg een 'Download als PDF' knop toe met behulp van de @react-pdf/renderer library."
  • E-mailnotificaties: de freelancer krijgt een mailtje als de klant de offerte bekijkt. Gebruik Resend met een simpele Server Action.
  • Herinnering na 3 dagen: een cron-job via Vercel Cron die automatisch een follow-up mailtje stuurt als de klant nog niet heeft gereageerd.
  • Meerdere btw-tarieven: handig voor freelancers die ook producten leveren (6% of 0%).
  • Terugkerende klanten: bewaar klantgegevens zodat je bij een volgende offerte alleen de naam hoeft in te vullen.
  • Eigen domein per freelancer: whitelabel-mogelijkheid als je dit als betaalde SaaS wilt aanbieden aan meerdere freelancers.

Met de basis die je vandaag hebt gebouwd kun je morgen al je eerste beta-gebruiker onboarden. Vraag een freelancer in je netwerk of ze het willen proberen, kijk waar ze vastlopen en bouw van daaruit verder. Dat is hoe goede SaaS groeit.


Wil je dit zelf leren?

Bij Eighty leer ik je Claude Code in het Nederlands gebruiken, van installatie tot een werkend SaaS-product. Wekelijks een nieuwe module, persoonlijke begeleiding.