Skip to content

PLAN: Klantportaal Uitbreiding & B2B Functionaliteiten

╔══════════════════════════════════════════════════════════════════════╗
║ ║
║ ██╗ ██╗██╗ █████╗ ███╗ ██╗████████╗ ║
║ ██║ ██╔╝██║ ██╔══██╗████╗ ██║╚══██╔══╝ ║
║ █████╔╝ ██║ ███████║██╔██╗ ██║ ██║ ║
║ ██╔═██╗ ██║ ██╔══██║██║╚██╗██║ ██║ ║
║ ██║ ██╗███████╗██║ ██║██║ ╚████║ ██║ ║
║ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ║
║ ║
║ P O R T A A L U I T B R E I D I N G ║
║ ║
║ Magic e-VERSE Customer Portal ║
║ B2B Features, 2FA, Invoices, Quotes, Multi-Language ║
║ ║
║ Ticket: Klantportaal Uitbreiding ║
║ Aangemaakt: Wayne ║
║ Toegewezen: Michiel ║
║ Status: WACHT OP AKKOORD WAYNE ║
║ ║
╚══════════════════════════════════════════════════════════════════════╝

Alle Magic eVerse tenants hebben in de huidige Next.js storefront al een ruwe account-structuur (Medusa starter: login, orders, adressen, profiel). Deze is echter kaal, niet gestyled in de Magic eVerse huisstijl, en mist cruciale B2B-functionaliteiten zoals facturen, offertes, 2FA en multi-language. Dit plan beschrijft de volledige uitbreiding en oppoetsing van het klantportaal.


src/app/[countryCode]/(main)/account/
├── layout.tsx # Parallel routes: @login / @dashboard
├── loading.tsx # Spinner loading state
├── @login/
│ └── page.tsx # LoginTemplate (login + register toggle)
└── @dashboard/
├── loading.tsx
├── page.tsx # Overview (profile completion %, recente orders)
├── profile/
│ └── page.tsx # Naam, e-mail, telefoon, facturatieadres
├── addresses/
│ └── page.tsx # Adresboek
└── orders/
├── page.tsx # Order lijst + TransferRequestForm
└── details/
└── [id]/
└── page.tsx # Order detail
src/modules/account/
├── components/
│ ├── account-info/index.tsx # Generiek info-blok met edit toggle
│ ├── account-nav/index.tsx # Sidebar: Overview, Profile, Addresses, Orders, Log out
│ ├── address-book/index.tsx # Adreslijst
│ ├── address-card/
│ │ ├── add-address.tsx # Nieuw adres toevoegen
│ │ └── edit-address-modal.tsx # Adres bewerken modal
│ ├── login/index.tsx # E-mail + wachtwoord login form (server action)
│ ├── order-card/index.tsx # Order samenvatting card
│ ├── order-overview/index.tsx # Orders lijst met empty state
│ ├── overview/index.tsx # Dashboard overview
│ ├── profile-billing-address/index.tsx
│ ├── profile-email/index.tsx
│ ├── profile-name/index.tsx
│ ├── profile-password/index.tsx # Wachtwoord wijzigen (commented out in profiel)
│ ├── profile-phone/index.tsx
│ ├── register/index.tsx # Registratieformulier
│ └── transfer-request-form/index.tsx
└── templates/
├── account-layout.tsx # Grid: 240px sidebar + content
└── login-template.tsx # Toggle login/register (client component)
OnderdeelStatusLocatie
E-mail + wachtwoord loginWerkendsdk.auth.login("customer", "emailpass", ...)
RegistratieWerkendsdk.auth.register("customer", "emailpass", ...)
JWT cookieWerkend_medusa_jwt, httpOnly, 7 dagen, strict sameSite
Cart transferWerkendAnonieme cart wordt overgedragen bij login
UitloggenWerkendsdk.auth.logout() + cookie cleanup

Base variabelen (src/themes/base/variables.css):

:root {
--theme-accent: #CF0D65;
--theme-accent-dark: #388e3c;
--theme-accent-light: #81c784;
--theme-dark: #1f2937;
--theme-gray: #6b7280;
--theme-light-gray: #f3f4f6;
--theme-border: #e5e7eb;
--theme-radius-sm: 4px;
--theme-radius-md: 8px;
--theme-radius-lg: 12px;
--theme-shadow-sm / --theme-shadow-md / --theme-shadow-lg
--theme-btn-primary-bg / --theme-btn-primary-text / --theme-btn-primary-hover
/* + backwards-compat aliassen: --brinxx-pink, --brinxx-primary, etc. */
}

Tenant overrides (bijv. src/themes/brinxx/override.css):

:root {
--theme-accent: #e91e63; /* Brinxx Magenta */
--theme-accent-dark: #c2185b;
--theme-topbar-bg: #e91e63;
--theme-nav-bg: #2c3e50;
}

Admin routes (al werkend):

RouteFunctie
/admin/aplt/invoicesCRUD facturen, PDF generatie, nummering F-YYYY-####
/admin/aplt/invoices/pdfPDF download (pdfkit, A4, on-the-fly)
/admin/aplt/invoices/emlE-mail als .eml met PDF bijlage
/admin/aplt/quotationsCRUD offertes, nummering Q-YYYY-####
/admin/aplt/quotations/pdfPDF met productafbeeldingen
/admin/aplt/quotations/confirmOfferte → Order conversie
/admin/aplt/quotations/emlE-mail als .eml
/admin/aplt/ordersCRUD orders, nummering O-YYYY-####
/admin/aplt/orders/statusStatus workflow: pending → confirmed → shipped → delivered
/admin/aplt/credit-notesCredit notes (vol/partieel), nummering C-YYYY-####
/admin/aplt/paymentsBetalingen registreren + alloceren, nummering B-YYYY-####
/admin/aplt/reportsRapportages: aged receivables, revenue, BTW, dashboard KPI
/admin/aplt/document-chainVolledige documentketen traversal

Store routes (bestaand, GEEN auth):

RouteFunctie
/store/aplt/quotationsGET/POST/PUT — open voor n8n
/store/aplt/quotations/linesGET/POST/PUT/DELETE — open voor n8n
/store/aplt/productsGET — productcatalogus
/store/aplt/categoriesGET — categorieën

Database tabellen (al aanwezig):

aplt_quotation_headers / aplt_quotation_lines
aplt_order_headers / aplt_order_lines
aplt_invoice_headers / aplt_invoice_lines / aplt_invoice_vat_summary
aplt_payments / aplt_payment_allocations
aplt_document_relations (source → target, relation_type)
aplt_audit_log
aplt_number_sequences
aplt_customers / aplt_discount_groups / aplt_payment_terms
aplt_cms_settings (bedrijfsinfo per brand_slug)
aplt_settings (verzendkosten, franco-grens)
PackageVersieDoel
Next.js^16.1.6Framework
React19.0.0-rcUI library
Tailwind CSS^3.0.23Styling
@medusajs/js-sdklatestMedusa client
@medusajs/uilatestUI componenten
@magiverse/i18nfile:./packages/magiverse-i18ni18n (lokaal pakket, al aanwezig)
@headlessui/react^2.2.0Accessible UI primitives
@stripe/react-stripe-js^5.3.0Betalingen
pg^8.11.3Direct database queries
meilisearch^0.37.0Zoekfunctie

src/app/[countryCode]/(main)/account/@dashboard/
├── invoices/
│ └── page.tsx ← NIEUW
├── quotes/
│ └── page.tsx ← NIEUW
├── wishlist/
│ └── page.tsx ← NIEUW
└── orders/
└── details/
└── [id]/
└── return/
└── page.tsx ← NIEUW
src/modules/account/components/
├── invoice-list/
│ └── index.tsx ← NIEUW
├── invoice-card/
│ └── index.tsx ← NIEUW
├── quote-list/
│ └── index.tsx ← NIEUW
├── quote-card/
│ └── index.tsx ← NIEUW
├── return-form/
│ └── index.tsx ← NIEUW
├── wishlist/
│ └── index.tsx ← NIEUW
├── two-factor/
│ ├── index.tsx ← NIEUW (2FA settings)
│ ├── otp-input.tsx ← NIEUW (OTP invoer)
│ └── recovery-codes.tsx ← NIEUW (herstelcodes)
├── dashboard-overview/
│ └── index.tsx ← NIEUW (vervangt overview)
├── status-badge/
│ └── index.tsx ← NIEUW (herbruikbaar)
├── order-timeline/
│ └── index.tsx ← NIEUW (visuele tijdlijn)
└── notification-preferences/
└── index.tsx ← NIEUW
src/api/store/aplt/
├── invoices/
│ └── route.ts ← NIEUW (GET, JWT auth)
├── invoices/
│ └── pdf/
│ └── route.ts ← NIEUW (GET, PDF stream)
├── quotes/
│ └── route.ts ← NIEUW (GET, JWT auth)
├── quotes/
│ └── [id]/
│ ├── accept/
│ │ └── route.ts ← NIEUW (POST)
│ └── reject/
│ └── route.ts ← NIEUW (POST)
└── returns/
└── route.ts ← NIEUW (POST, met foto upload)
src/api/store/
├── auth/
│ └── otp/
│ └── route.ts ← NIEUW (POST verify, POST resend)
└── customer/
└── 2fa/
└── route.ts ← NIEUW (GET status, POST enable/disable)
┌─────────────────────────────────────────────────────────────────┐
│ STOREFRONT (Next.js 16) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Facturen │ │ Offertes │ │ Retouren │ │ Wishlist │ │
│ │ Page │ │ Page │ │ Form │ │ Page │ │
│ └─────┬─────┘ └─────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ ┌─────┴──────────────┴──────────────┴───────────────┴────────┐ │
│ │ Server Actions / API calls │ │
│ │ (JWT via _medusa_jwt cookie) │ │
│ └─────────────────────────┬───────────────────────────────────┘ │
└────────────────────────────┼─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ BACKEND (Medusa v2.13.1) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ NIEUWE Store Routes (JWT auth) │ │
│ │ │ │
│ │ GET /store/aplt/invoices → klant-specifiek │ │
│ │ GET /store/aplt/invoices/pdf → PDF stream │ │
│ │ GET /store/aplt/quotes → klant-specifiek │ │
│ │ POST /store/aplt/quotes/:id/accept │ │
│ │ POST /store/aplt/quotes/:id/reject │ │
│ │ POST /store/aplt/returns → retourverzoek │ │
│ │ POST /store/auth/otp → OTP verificatie │ │
│ │ GET /store/customer/2fa → 2FA status │ │
│ └──────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴───────────────────────────────────┐ │
│ │ PostgreSQL (APLT Database) │ │
│ │ │ │
│ │ aplt_invoice_headers / aplt_invoice_lines │ │
│ │ aplt_quotation_headers / aplt_quotation_lines │ │
│ │ aplt_order_headers / aplt_order_lines │ │
│ │ aplt_customers (email match met Medusa customer) │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Fase 1 — Huisstijl & Design System (3-4 dagen)

Section titled “Fase 1 — Huisstijl & Design System (3-4 dagen)”

Alle bestaande account-pagina’s restylen naar de Magic eVerse design language, zodat het klantportaal visueel consistent is met de rest van de storefront.

  1. Account sidebar navigatie restylen

    Huidige account-nav/index.tsx heeft geen iconen en geen visuele actieve state.

    Wijzigingen:

    • Lucide React iconen toevoegen per menu-item (User, Package, MapPin, FileText, Heart, LogOut)
    • Actieve state indicator: linkerborder accent kleur (border-l-2 border-[var(--theme-accent)])
    • Hover effect met bg-[var(--theme-light-gray)] en transition-colors
    • Nieuwe menu-items toevoegen: Facturen, Offertes, Verlanglijst
    • Mobile: sidebar collapst naar horizontale bottom-nav met iconen (geen labels)
    ┌────────────────────────────────────────────────┐
    │ DESKTOP SIDEBAR │ MOBILE BOTTOM-NAV │
    │ │ │
    │ ┌─────────────────────┐ │ ┌─┬─┬─┬─┬─┬─┐ │
    │ │ ● Overzicht │ │ │🏠│📦│📄│💰│❤│⚙│ │
    │ │ Profiel │ │ └─┴─┴─┴─┴─┴─┘ │
    │ │ Adressen │ │ │
    │ │ Bestellingen │ │ │
    │ │ Facturen NIEUW│ │ │
    │ │ Offertes NIEUW│ │ │
    │ │ Verlanglijst NIEUW│ │ │
    │ │ │ │ │
    │ │ ───────────────── │ │ │
    │ │ Uitloggen │ │ │
    │ └─────────────────────┘ │ │
    └────────────────────────────────────────────────┘

    Bestanden:

    • src/modules/account/components/account-nav/index.tsx — restylen
    • src/modules/account/templates/account-layout.tsx — responsive grid aanpassen
  2. Cards en containers restylen

    Alle content-blokken krijgen consistente styling:

    • bg-white rounded-lg shadow-sm border border-[var(--theme-border)]
    • Padding: p-6 (desktop), p-4 (mobile)
    • Headers in cards: text-lg font-semibold text-[var(--theme-dark)]
    • Subtekst: text-sm text-[var(--theme-gray)]

    Bestanden:

    • src/modules/account/components/account-info/index.tsx
    • src/modules/account/components/overview/index.tsx
    • src/modules/account/components/order-card/index.tsx
    • src/modules/account/components/address-book/index.tsx
    • Alle profile-* componenten
  3. Loading skeletons implementeren

    Vervang de huidige spinner (loading.tsx) door skeleton loaders:

    // Skeleton component voorbeeld
    <div className="animate-pulse">
    <div className="h-4 bg-gray-200 rounded w-3/4 mb-4" />
    <div className="h-4 bg-gray-200 rounded w-1/2 mb-4" />
    <div className="h-32 bg-gray-200 rounded mb-4" />
    </div>

    Bestanden:

    • src/app/[countryCode]/(main)/account/loading.tsx — skeleton layout
    • src/app/[countryCode]/(main)/account/@dashboard/loading.tsx — dashboard skeleton
    • Nieuwe loading.tsx per sub-route (orders, invoices, etc.)
  4. Status-badge component

    Herbruikbaar component voor order/factuur/offerte statussen:

    StatusKleurCSS
    Wachtend / DraftOranjebg-amber-100 text-amber-800
    Bevestigd / OpenBlauwbg-blue-100 text-blue-800
    VerzondenIndigobg-indigo-100 text-indigo-800
    Afgeleverd / BetaaldGroenbg-green-100 text-green-800
    Geannuleerd / VervallenRoodbg-red-100 text-red-800

    Nieuw bestand: src/modules/account/components/status-badge/index.tsx

  5. Inter font verificatie

    Controleer dat Inter font correct geladen wordt via next/font/google in de root layout. Tailwind config moet fontFamily.sans overschrijven.

    Bestanden: src/app/layout.tsx, tailwind.config.ts

  6. Tenant-theming verificatie

    Controleer dat NEXT_PUBLIC_THEME correct werkt voor alle 7 tenants. Elke tenant krijgt eigen kleuraccent zonder codewijziging — dit is al opgezet via src/themes/index.ts maar moet getest worden met de nieuwe account componenten.

┌────────────────────────────────────────────────────────┐
│ CHECKLIST FASE 1 │
├────────────────────────────────────────────────────────┤
│ │
│ [ ] Sidebar met iconen en actieve state indicator │
│ [ ] Mobile bottom-nav met iconen │
│ [ ] Alle cards met consistente shadow/border/radius │
│ [ ] Loading skeletons ipv spinners │
│ [ ] Status-badge component herbruikbaar │
│ [ ] Inter font correct geladen │
│ [ ] Tenant theming werkt op account pagina's │
│ [ ] Responsive op mobile (375px+), tablet, desktop │
│ [ ] Geen visuele regressie op bestaande pagina's │
│ │
└────────────────────────────────────────────────────────┘

Fase 2 — Authenticatie & 2FA (4-5 dagen)

Section titled “Fase 2 — Authenticatie & 2FA (4-5 dagen)”

Login flow verbeteren met “onthoud mij”, wachtwoord vergeten, e-mail verificatie, en Two-Factor Authentication via OTP (e-mail of telefoon).

  1. “Onthoud mij” optie

    Checkbox op login formulier. Wanneer aangevinkt: JWT cookie levensduur van 7 → 30 dagen.

    Wijzigingen:

    • src/modules/account/components/login/index.tsx — checkbox toevoegen
    • src/lib/data/cookies.tssetAuthToken() aanpassen: maxAge parameter meegeven
    • src/lib/data/customer.tslogin() action: “remember me” waarde doorgeven
  2. Wachtwoord vergeten — reset flow

    Nieuwe pagina + backend flow:

    • Klant voert e-mail in → backend stuurt reset link met beveiligde token
    • Klant klikt link → pagina met nieuw wachtwoord invoeren
    • Token geldig 15 minuten, eenmalig gebruik

    Nieuwe bestanden:

    • src/app/[countryCode]/(main)/account/forgot-password/page.tsx
    • src/app/[countryCode]/(main)/account/reset-password/page.tsx
    • Backend: Medusa native reset password flow via sdk.auth.resetPassword()
  3. E-mail verificatie bij registratie

    Flow:

    • Klant registreert → e-mail met verificatielink
    • Account is “unverified” tot link geklikt
    • Unverified accounts kunnen inloggen maar zien een banner: “Verifieer je e-mail”

    Wijzigingen:

    • Backend: @nicogorga/medusa-auth-emailpass-verified installeren en configureren
    • src/modules/account/components/register/index.tsx — success state met “controleer je e-mail” bericht
    • src/app/[countryCode]/(main)/account/verify-email/page.tsx — NIEUW, verificatie landing
  4. Two-Factor Authentication (2FA) via OTP

    Eigen OTP Implementatie (Optie B):

    Nieuwe database tabel:

    CREATE TABLE aplt_customer_2fa (
    id SERIAL PRIMARY KEY,
    customer_email VARCHAR(255) NOT NULL UNIQUE,
    is_enabled BOOLEAN DEFAULT FALSE,
    otp_secret VARCHAR(64), -- Random secret voor OTP generatie
    recovery_codes TEXT, -- JSON array van 8 gehashte codes
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
    );
    CREATE TABLE aplt_otp_codes (
    id SERIAL PRIMARY KEY,
    customer_email VARCHAR(255) NOT NULL,
    code VARCHAR(6) NOT NULL,
    expires_at TIMESTAMP NOT NULL, -- +10 minuten
    used BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW()
    );

    Login flow met 2FA:

    ┌──────────────┐ ┌───────────────┐ ┌──────────────┐
    │ E-mail + │ │ 2FA Check │ │ OTP Code │
    │ Wachtwoord │────▶│ Heeft klant │────▶│ Invoeren │
    │ Formulier │ │ 2FA aan? │ │ (6 cijfers) │
    └──────────────┘ └───────┬───────┘ └──────┬───────┘
    │ │
    NEE │ │ CORRECT
    ▼ ▼
    ┌──────────────┐ ┌──────────────┐
    │ Direct │ │ JWT Token │
    │ Ingelogd │ │ Uitgegeven │
    └──────────────┘ └──────────────┘
    FOUT │
    ┌──────────────┐
    │ Herstelcode │
    │ Optie │
    └──────────────┘

    Backend routes:

    • POST /store/auth/otp — Verifieer OTP code
      • Input: { email, code } of { email, recovery_code }
      • Genereert random 6-cijferige code, slaat op in aplt_otp_codes, stuurt via SMTP
      • Code geldig 10 minuten, max 3 pogingen per 15 minuten
    • GET /store/customer/2fa — 2FA status ophalen (JWT auth)
    • POST /store/customer/2fa — 2FA in-/uitschakelen (JWT auth)
      • Enable: genereert 8 herstelcodes, retourneert ze eenmalig
      • Disable: vereist wachtwoord bevestiging

    Frontend componenten:

    • src/modules/account/components/two-factor/index.tsx — 2FA settings in profiel
    • src/modules/account/components/two-factor/otp-input.tsx — 6-cijfer invoerveld (auto-focus, paste support)
    • src/modules/account/components/two-factor/recovery-codes.tsx — Codes tonen + download als .txt

    Wijzigingen login flow:

    • src/modules/account/components/login/index.tsx — Na succesvolle wachtwoord check: als 2FA enabled, toon OTP invoerveld
    • src/lib/data/customer.tslogin() splitsen in twee stappen:
      1. Wachtwoord verificatie → tijdelijk token
      2. OTP verificatie → definitief JWT token
  5. Passkey (Fase 2 — later)

    • Na eerste login een passkey koppelen (Touch ID, Face ID, Windows Hello)
    • Volledig passwordless voor terugkerende klanten
    • Niet in scope van dit ticket — apart ticket aanmaken
┌────────────────────────────────────────────────────────┐
│ CHECKLIST FASE 2 │
├────────────────────────────────────────────────────────┤
│ │
│ [ ] "Onthoud mij" checkbox werkt (30 dagen cookie) │
│ [~] Wachtwoord vergeten flow (admin: ✅, storefront: TODO) │
│ [ ] E-mail verificatie bij registratie │
│ [ ] 2FA in-/uitschakelen via profielpagina │
│ [ ] OTP code via e-mail ontvangen en invoeren │
│ [ ] Herstelcodes genereren en downloaden │
│ [ ] Herstelcode als backup voor OTP werkt │
│ [ ] Max 3 OTP pogingen per 15 minuten (rate limit) │
│ [ ] Admin kan zien welke klanten 2FA hebben │
│ [ ] Login flow werkt correct met EN zonder 2FA │
│ │
└────────────────────────────────────────────────────────┘

Fase 3 — Orders & Ordergeschiedenis (3-4 dagen)

Section titled “Fase 3 — Orders & Ordergeschiedenis (3-4 dagen)”

Orders overzicht en detail pagina’s uitbreiden met filters, zoeken, status-badges, tijdlijn, track & trace, retour en opnieuw bestellen.

  1. Orders overzichtspagina verbeteren

    Huidige order-overview/index.tsx toont een simpele lijst. Uitbreiden met:

    FeatureImplementatie
    Status filter tabsalle / open / verzonden / afgeleverd / geannuleerd — via URL search params
    ZoekbalkOp ordernummer of productnaam — client-side filter of server-side query
    SorteringDatum nieuw → oud (default), status — via dropdown
    Paginering20 per pagina, prev/next knoppen — Medusa SDK offset + limit
    Status-badgesHergebruik status-badge component uit Fase 1

    Bestanden:

    • src/modules/account/components/order-overview/index.tsx — restylen + features
    • src/modules/account/components/order-card/index.tsx — restylen met badge
    • src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx — search params handling
  2. Order detail pagina uitbreiden

    Huidige detail pagina toont basisinfo. Uitbreiden met:

    ┌─────────────────────────────────────────────────────────┐
    │ Order #O-2025-0042 ● Verzonden│
    ├─────────────────────────────────────────────────────────┤
    │ │
    │ ┌───────────────────────────────────────────────────┐ │
    │ │ PRODUCTEN │ │
    │ │ ┌────┐ │ │
    │ │ │ 📷 │ Blauwe Pen Deluxe ×100 €245,00 │ │
    │ │ └────┘ Variant: Blauw/Zilver │ │
    │ │ ┌────┐ │ │
    │ │ │ 📷 │ Notitieboek A5 ×50 €187,50 │ │
    │ │ └────┘ Variant: Hardcover │ │
    │ └───────────────────────────────────────────────────┘ │
    │ │
    │ ┌─────────────────────┐ ┌─────────────────────────┐ │
    │ │ BEZORGADRES │ │ BETALING │ │
    │ │ Bedrijf BV │ │ Bankoverschrijving │ │
    │ │ Straat 1 │ │ │ │
    │ │ 1234 AB Stad │ │ Subtotaal: €432,50 │ │
    │ │ │ │ Verzending: €12,50 │ │
    │ │ LEVERDATUM │ │ BTW 21%: €93,45 │ │
    │ │ 15 maart 2026 │ │ ───────────────────── │ │
    │ │ │ │ Totaal: €538,45 │ │
    │ └─────────────────────┘ └─────────────────────────┘ │
    │ │
    │ ┌───────────────────────────────────────────────────┐ │
    │ │ TIJDLIJN │ │
    │ │ ● Besteld 3 mrt 2026, 14:30 │ │
    │ │ ● Betaald 3 mrt 2026, 14:31 │ │
    │ │ ● Ingepakt 4 mrt 2026, 09:15 │ │
    │ │ ● Verzonden 4 mrt 2026, 16:00 📦 Track & Trace│ │
    │ │ ○ Afgeleverd (verwacht 6 mrt) │ │
    │ └───────────────────────────────────────────────────┘ │
    │ │
    │ ┌─────────────────┐ ┌─────────────────────────────┐ │
    │ │ 🔄 Retour │ │ 🛒 Opnieuw bestellen │ │
    │ │ aanvragen │ │ │ │
    │ └─────────────────┘ └─────────────────────────────┘ │
    └─────────────────────────────────────────────────────────┘

    Nieuwe gegevens:

    • Geschatte leverdatum: uit aplt_order_headers.expected_delivery_date
    • Track & trace link: uit order metadata (tracking_url)
    • Tijdlijn: afleiden uit order status + timestamps in aplt_audit_log
    • Opnieuw bestellen: alle items toevoegen aan cart via addToCart() server action

    Bestanden:

    • src/app/[countryCode]/(main)/account/@dashboard/orders/details/[id]/page.tsx — uitbreiden
    • src/modules/account/components/order-timeline/index.tsx — NIEUW
  3. Opnieuw bestellen functionaliteit

    “Opnieuw bestellen” knop voegt alle orderregels toe aan de huidige cart:

    • Loop door order items
    • Roep addToCart(variantId, quantity) aan per item
    • Redirect naar cart pagina
    • Error handling als product niet meer beschikbaar is

    Wijzigingen:

    • src/lib/data/cart.ts — eventueel bulk addToCart toevoegen
    • Order detail pagina — knop + server action
┌────────────────────────────────────────────────────────┐
│ CHECKLIST FASE 3 │
├────────────────────────────────────────────────────────┤
│ │
│ [ ] Order filter tabs werken (alle/open/verzonden/etc) │
│ [ ] Zoekbalk op ordernummer en productnaam │
│ [ ] Sortering op datum en status │
│ [ ] Paginering (20 per pagina) │
│ [ ] Status-badges met kleurcodering │
│ [ ] Order detail: producten met afbeelding │
│ [ ] Order detail: subtotaal, verzending, BTW, totaal │
│ [ ] Order detail: bezorgadres en betaalmethode │
│ [ ] Order detail: geschatte leverdatum │
│ [ ] Order detail: track & trace link (indien aanwezig) │
│ [ ] Order detail: visuele tijdlijn │
│ [ ] "Opnieuw bestellen" voegt items toe aan cart │
│ [ ] "Retour aanvragen" knop aanwezig (link naar Fase 4) │
│ │
└────────────────────────────────────────────────────────┘

Fase 4 — Facturen, Offertes & Retouren (5-6 dagen)

Section titled “Fase 4 — Facturen, Offertes & Retouren (5-6 dagen)”
  1. Backend: GET /store/aplt/invoices (NIEUW)

    Nieuwe route met JWT klant-autorisatie:

    src/api/store/aplt/invoices/route.ts
    export const AUTHENTICATE = true // Medusa JWT auth
    export async function GET(req, res) {
    // 1. Haal klant e-mail uit JWT token (req.auth_context.actor_id → customer lookup)
    // 2. Zoek aplt_customers record op basis van e-mail
    // 3. Query aplt_invoice_headers WHERE customer_id = klant.id
    // 4. Filter op optionele query params: ?status=open&year=2025
    // 5. Return gefilterde facturen met lines
    }
  2. Backend: GET /store/aplt/invoices/pdf (NIEUW)

    Hergebruik de bestaande PDF generatie logica uit /admin/aplt/invoices/pdf/route.ts:

    src/api/store/aplt/invoices/pdf/route.ts
    export const AUTHENTICATE = true
    export async function GET(req, res) {
    // 1. Haal klant e-mail uit JWT
    // 2. Verifieer dat factuur behoort tot deze klant
    // 3. Genereer PDF (hergebruik bestaande pdfkit logica)
    // 4. Stream als attachment
    }
  3. Frontend: Facturen pagina

    ┌─────────────────────────────────────────────────────────┐
    │ Facturen │
    ├─────────────────────────────────────────────────────────┤
    │ │
    │ Filter: [Alle ▼] Jaar: [2025 ▼] 🔍 Zoek... │
    │ │
    │ ┌───────────────────────────────────────────────────┐ │
    │ │ F-2025-0042 │ 15 feb 2025 │ €538,45 │ ● Open │ │
    │ │ │ │ │ 📥 PDF │ │
    │ ├───────────────────────────────────────────────────┤ │
    │ │ F-2025-0038 │ 2 feb 2025 │ €1.250 │ ●Betaald│ │
    │ │ │ │ │ 📥 PDF │ │
    │ ├───────────────────────────────────────────────────┤ │
    │ │ F-2024-0195 │ 18 dec 2024 │ €890,00 │●Vervall│ │
    │ │ │ │ │ 📥 PDF │ │
    │ └───────────────────────────────────────────────────┘ │
    │ │
    │ [📦 Exporteer geselecteerde als ZIP] │
    │ │
    │ Pagina 1 van 3 [◀ Vorige] [Volgende ▶] │
    └─────────────────────────────────────────────────────────┘

    Features:

    • Lijst facturen met nummer, datum, bedrag, status
    • Status filter: alle, open, betaald, vervallen
    • Jaar filter
    • PDF download per factuur (direct stream, geen pagina-refresh)
    • Meerdere selecteren + exporteer als ZIP (client-side via JSZip of backend endpoint)

    Nieuwe bestanden:

    • src/app/[countryCode]/(main)/account/@dashboard/invoices/page.tsx
    • src/modules/account/components/invoice-list/index.tsx
    • src/modules/account/components/invoice-card/index.tsx
    • src/lib/data/invoices.ts — server actions voor factuur data
  1. Backend: GET /store/aplt/quotes (NIEUW)

    // Klant-specifieke offertes ophalen
    // Filter op status: open, accepted, expired, rejected
    // Include lines voor preview
  2. Backend: POST /store/aplt/quotes/[id]/accept (NIEUW)

    // 1. Verifieer offerte behoort tot klant
    // 2. Controleer vervaldatum niet verstreken
    // 3. Hergebruik bestaande /admin/aplt/quotations/confirm logica:
    // - Kopieer quotation lines naar aplt_order_lines
    // - Maak aplt_document_relations aan
    // - Update quotation status naar 'accepted'
    // 4. Return order_id + order_number
  3. Backend: POST /store/aplt/quotes/[id]/reject (NIEUW)

    // 1. Verifieer offerte behoort tot klant
    // 2. Update status naar 'rejected'
    // 3. Sla afwijsreden op in metadata/notes
    // 4. Log in aplt_audit_log
  4. Frontend: Offertes pagina

    ┌─────────────────────────────────────────────────────────┐
    │ Offertes │
    ├─────────────────────────────────────────────────────────┤
    │ │
    │ ⚠️ Offerte Q-2025-0117 verloopt over 2 dagen! │
    │ │
    │ ┌───────────────────────────────────────────────────┐ │
    │ │ Q-2025-0117 │ 1 mrt 2025 │ €2.450 │ ● Open │ │
    │ │ Geldig tot: 6 mrt 2025 │ │
    │ │ [✅ Accepteren] [❌ Afwijzen] [📥 PDF] │ │
    │ ├───────────────────────────────────────────────────┤ │
    │ │ Q-2025-0098 │ 15 feb 2025 │ €890 │●Geaccept. │ │
    │ │ Omgezet naar order O-2025-0043 │ │
    │ │ [📥 PDF] │ │
    │ └───────────────────────────────────────────────────┘ │
    └─────────────────────────────────────────────────────────┘

    Features:

    • Banner bij offertes die binnen 3 dagen verlopen
    • Accepteren → bevestigingsdialoog → conversie naar order
    • Afwijzen → reden invoeren (verplicht) → bevestiging
    • PDF download
    • Vervaldatum duidelijk zichtbaar
    • Link naar resulterende order bij geaccepteerde offertes

    Nieuwe bestanden:

    • src/app/[countryCode]/(main)/account/@dashboard/quotes/page.tsx
    • src/modules/account/components/quote-list/index.tsx
    • src/modules/account/components/quote-card/index.tsx
    • src/lib/data/quotes.ts — server actions
  1. Frontend: Retourformulier

    Nieuw formulier bereikbaar via order detail pagina:

    ┌─────────────────────────────────────────────────────────┐
    │ Retour aanvragen — Order #O-2025-0042 │
    ├─────────────────────────────────────────────────────────┤
    │ │
    │ Selecteer producten om te retourneren: │
    │ │
    │ [✓] Blauwe Pen Deluxe ×100 │
    │ Reden: [Beschadigd ▼] │
    │ Aantal: [50] │
    │ │
    │ [ ] Notitieboek A5 ×50 │
    │ │
    │ Foto's (optioneel, max 3, max 5MB per stuk): │
    │ [📷 Upload foto] [📷 Upload foto] [📷 Upload foto] │
    │ │
    │ Toelichting: [________________________] │
    │ │
    │ [📤 Retourverzoek indienen] │
    └─────────────────────────────────────────────────────────┘

    Retourredenen:

    • Beschadigd ontvangen
    • Verkeerd product geleverd
    • Past niet / voldoet niet aan verwachting
    • Defect / niet werkend
    • Anders (vrij tekstveld)

    Backend: Medusa native returns API (POST /store/orders/:id/returns of sdk.store.return.create())

    Foto upload: Via FormData naar een nieuwe /store/aplt/returns route die bestanden opslaat in /mnt/data/return_photos/ (of S3-compatible storage)

    Nieuwe bestanden:

    • src/app/[countryCode]/(main)/account/@dashboard/orders/details/[id]/return/page.tsx
    • src/modules/account/components/return-form/index.tsx
    • src/lib/data/returns.ts — server actions
┌────────────────────────────────────────────────────────┐
│ CHECKLIST FASE 4 │
├────────────────────────────────────────────────────────┤
│ │
│ FACTUREN: │
│ [ ] Store route /store/aplt/invoices werkt met JWT │
│ [ ] Facturen gefilterd op ingelogde klant │
│ [ ] PDF download werkt vanuit portaal │
│ [ ] Filter op status en jaar │
│ [ ] ZIP export van meerdere facturen │
│ │
│ OFFERTES: │
│ [ ] Store route /store/aplt/quotes werkt met JWT │
│ [ ] Offertes gefilterd op ingelogde klant │
│ [ ] Accepteren converteert naar order │
│ [ ] Afwijzen met reden werkt │
│ [ ] Banner bij bijna-verlopen offertes │
│ [ ] PDF download werkt │
│ │
│ RETOUREN: │
│ [ ] Retourformulier met product selectie │
│ [ ] Retourredenen dropdown │
│ [ ] Foto upload (max 3, max 5MB) │
│ [ ] Bevestigingsscherm na indienen │
│ [ ] Retourverzoek zichtbaar in Medusa admin │
│ │
└────────────────────────────────────────────────────────┘

Fase 5 — Profiel & Dashboard Uitbreiding (2-3 dagen)

Section titled “Fase 5 — Profiel & Dashboard Uitbreiding (2-3 dagen)”
  1. Inline bewerken

    Huidige profiel componenten (profile-name, profile-email, profile-phone, profile-billing-address) gebruiken al account-info met een edit toggle. Restylen zodat de edit-state inline is (geen apart formulier/modal).

  2. Profielfoto upload (optioneel)

    • Upload naar /uploads/avatars/{customer_id}.jpg
    • Opslaan in customer metadata: { avatar_url: "/uploads/avatars/..." }
    • Max 2MB, alleen JPG/PNG
    • Circulaire preview met fallback initialen
  3. Taalvoorkeur instellen

    • Dropdown: NL / EN / DE
    • Opslaan in customer metadata: { preferred_language: "nl" }
    • Gebruikt @magiverse/i18n (al aanwezig als lokaal pakket)
    • Bij laden: check customer metadata → cookie → Accept-Language header
  4. E-mail notificatievoorkeuren

    Checkboxes voor:

    • Orderupdates (default: aan)
    • Offerte notificaties (default: aan)
    • Nieuwsbrief (default: uit)

    Opslaan in customer metadata: { notifications: { orders: true, quotes: true, newsletter: false } }

  5. 2FA sectie in profiel

    Verwijzing naar componenten uit Fase 2:

    • Status indicator (aan/uit)
    • In-/uitschakelen toggle
    • Herstelcodes downloaden knop
    • Link naar volledige 2FA instellingen
  6. Account verwijderen (GDPR)

    • “Account verwijderen” knop onderaan profielpagina
    • Bevestigingsflow: wachtwoord invoeren + e-mail verificatie
    • Soft-delete: markeer account als deleted, anonimiseer persoonsgegevens na 30 dagen
    • E-mail bevestiging van verwijdering
  1. Welkomstbanner

    ┌─────────────────────────────────────────────────────────┐
    │ Welkom terug, Jan! 👋 │
    │ Hier is een overzicht van je account. │
    └─────────────────────────────────────────────────────────┘
  2. Statistieken cards

    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
    │ 📦 12 │ │ 📋 2 │ │ 💰 €1.250 │
    │ Bestellingen│ │ Open │ │ Te betalen │
    │ │ │ Offertes │ │ Facturen │
    └──────────────┘ └──────────────┘ └──────────────┘

    Data ophalen via de nieuwe store routes (parallel server-side fetch).

  3. Recente orders (laatste 3)

    Compacte order cards met status-badge, datum, bedrag.

  4. Snelkoppelingen

    [🛒 Nieuwe bestelling] [📋 Offerte aanvragen] [📄 Facturen] [👤 Profiel]
  5. Waarschuwingsbanners

    • Offerte bijna verlopen: “Je offerte Q-2025-0117 verloopt over 2 dagen”
    • Factuur vervallen: “Je hebt 1 openstaande factuur”
    • E-mail niet geverifieerd: “Verifieer je e-mailadres”
┌────────────────────────────────────────────────────────┐
│ CHECKLIST FASE 5 │
├────────────────────────────────────────────────────────┤
│ │
│ [ ] Profiel: inline bewerken werkt │
│ [ ] Profiel: taalvoorkeur opslaan en laden │
│ [ ] Profiel: notificatievoorkeuren │
│ [ ] Profiel: 2FA sectie met status indicator │
│ [ ] Profiel: account verwijderen flow (GDPR) │
│ [ ] Dashboard: welkomstbanner met naam │
│ [ ] Dashboard: 3 statistieken cards │
│ [ ] Dashboard: recente orders met status │
│ [ ] Dashboard: snelkoppeling knoppen │
│ [ ] Dashboard: waarschuwingsbanners │
│ │
└────────────────────────────────────────────────────────┘

  1. Account vertalingen toevoegen

    Nieuwe vertaalsleutels voor alle account-pagina’s:

    {
    "account": {
    "nav": {
    "overview": "Overzicht",
    "profile": "Profiel",
    "addresses": "Adressen",
    "orders": "Bestellingen",
    "invoices": "Facturen",
    "quotes": "Offertes",
    "wishlist": "Verlanglijst",
    "logout": "Uitloggen"
    },
    "dashboard": {
    "welcome": "Welkom terug, {name}!",
    "orders_count": "Bestellingen",
    "open_quotes": "Open offertes",
    "outstanding_invoices": "Te betalen facturen"
    },
    "orders": {
    "title": "Bestellingen",
    "search_placeholder": "Zoek op ordernummer of product...",
    "filter_all": "Alle",
    "filter_open": "Open",
    "filter_shipped": "Verzonden",
    "filter_delivered": "Afgeleverd",
    "filter_cancelled": "Geannuleerd",
    "reorder": "Opnieuw bestellen",
    "request_return": "Retour aanvragen"
    },
    "invoices": {
    "title": "Facturen",
    "download_pdf": "Download PDF",
    "export_zip": "Exporteer als ZIP",
    "status_open": "Open",
    "status_paid": "Betaald",
    "status_overdue": "Vervallen"
    },
    "quotes": {
    "title": "Offertes",
    "accept": "Accepteren",
    "reject": "Afwijzen",
    "expires_in": "Verloopt over {days} dagen",
    "expired": "Verlopen",
    "converted_to_order": "Omgezet naar order {number}"
    },
    "returns": {
    "title": "Retour aanvragen",
    "select_products": "Selecteer producten om te retourneren",
    "reason": "Reden",
    "reason_damaged": "Beschadigd ontvangen",
    "reason_wrong": "Verkeerd product geleverd",
    "reason_not_as_expected": "Voldoet niet aan verwachting",
    "reason_defective": "Defect / niet werkend",
    "reason_other": "Anders",
    "upload_photos": "Foto's uploaden",
    "submit": "Retourverzoek indienen"
    },
    "profile": {
    "title": "Profiel",
    "language_preference": "Taalvoorkeur",
    "notifications": "E-mail notificaties",
    "delete_account": "Account verwijderen",
    "two_factor": "Twee-factor authenticatie"
    },
    "auth": {
    "login": "Inloggen",
    "register": "Registreren",
    "remember_me": "Onthoud mij",
    "forgot_password": "Wachtwoord vergeten?",
    "otp_title": "Verificatiecode invoeren",
    "otp_description": "Er is een code naar je e-mail gestuurd",
    "use_recovery_code": "Gebruik een herstelcode"
    }
    }
    }

    Vertalingen in NL, EN, DE toevoegen aan de @magiverse/i18n translation files.

  2. next-intl evaluatie

  3. Taalswitch in header

    • Vlag-icoontje in storefront header (al aanwezig via react-country-flag dependency)
    • Opgeslagen in cookie (magiverse_language) en customer metadata
    • Taaldetectie volgorde: customer metadata → cookie → browser Accept-Language → nl default
  4. Tolgee evaluatie (voor productinhoud)

    Zie: i18n Integration Plan voor de volledige Tolgee strategie.


Fase 7 — Wishlist & E-mail Templates (4 dagen)

Section titled “Fase 7 — Wishlist & E-mail Templates (4 dagen)”
  1. Plugin installatie

    medusa-plugin-wishlist (RSC-Labs, MIT, Medusa 2.0+)

    Backend installatie in magic_development:

    Terminal window
    cd /mnt/data/magic_omniverse/magic_commerce/magic_development/backend
    npm install medusa-plugin-wishlist

    Configuratie in medusa-config.ts:

    plugins: [
    // ... bestaande plugins
    { resolve: "medusa-plugin-wishlist" }
    ]
  2. Hart-icoontje op productpagina

    • Toggle hart-icoon (leeg/gevuld) op elke product card en product detail pagina
    • Unauthenticated: redirect naar login
    • Authenticated: toggle via wishlist API

    Bestanden om aan te passen:

    • src/modules/products/components/product-card/ — hart-icoon toevoegen
    • src/modules/products/templates/product-info/ — hart-icoon toevoegen
  3. Verlanglijst pagina

    ┌─────────────────────────────────────────────────────────┐
    │ Verlanglijst (4 items) │
    ├─────────────────────────────────────────────────────────┤
    │ │
    │ ┌────┐ Blauwe Pen Deluxe €2,45/stuk [🗑️] │
    │ │ 📷 │ Min. 100 stuks │
    │ └────┘ │
    │ ┌────┐ Notitieboek A5 Hardcover €3,75/stuk [🗑️] │
    │ │ 📷 │ Min. 50 stuks │
    │ └────┘ │
    │ │
    │ [🛒 Alles toevoegen aan winkelwagen] │
    │ [📋 Omzetten naar offerte-aanvraag] ← B2B feature │
    │ [🔗 Deel verlanglijst] │
    └─────────────────────────────────────────────────────────┘

    Features:

    • Product verwijderen van verlanglijst
    • “Voeg alles toe aan cart” knop
    • B2B: “Omzetten naar offerte-aanvraag” — maakt een quotation aan via /store/aplt/quotations
    • Deelbare link via JWT-token (publieke toegang zonder login)

    Nieuwe bestanden:

    • src/app/[countryCode]/(main)/account/@dashboard/wishlist/page.tsx
    • src/modules/account/components/wishlist/index.tsx
    • src/lib/data/wishlist.ts — server actions
  1. React Email + Resend setup

    Terminal window
    npm install @react-email/components resend

    E-mail templates in tenant-huisstijl met dezelfde --theme-* variabelen (inline CSS voor e-mail compatibility).

  2. Templates aanmaken/restylen

    E-mailTriggerTemplate
    OrderbevestigingOrder aangemaaktemails/order-confirmation.tsx
    VerzendbevestigingOrder status → shippedemails/shipping-confirmation.tsx
    Factuur beschikbaarFactuur aangemaaktemails/invoice-available.tsx
    Offerte bijna verlopenCron: 3 dagen voor vervaldatumemails/quote-expiring.tsx
    Retourverzoek ontvangenRetour ingediendemails/return-received.tsx
    2FA OTP codeLogin met 2FAemails/otp-code.tsx
    Wachtwoord gewijzigdWachtwoord updateemails/password-changed.tsx
    Welkom / E-mail verificatieRegistratieemails/welcome-verify.tsx

    Locatie: src/emails/ in de backend of storefront (afhankelijk van waar de e-mail wordt verstuurd)

  3. Template structuur

    Elke e-mail template:

    • Logo bovenaan (tenant-specifiek via NEXT_PUBLIC_THEME)
    • Accent kleur als header bar
    • Content in clean cards
    • Footer met bedrijfsgegevens + afmeldlink
    • Responsive (mobile-first)

  1. Testen op beta.brinxx.nl

    • Alle flows handmatig doorlopen op de beta omgeving
    • Mobile testing (iOS Safari, Android Chrome)
    • Cross-browser (Chrome, Firefox, Safari, Edge)
    • 2FA flow met echte e-mail
  2. Performance check

    • Lighthouse score > 80 op account pagina’s
    • Geen onnodige re-renders (React DevTools profiler)
    • PDF download < 3 seconden
  3. Security review

    • JWT autorisatie op alle nieuwe store routes
    • Klant kan ALLEEN eigen data zien (facturen, offertes, orders)
    • OTP brute-force bescherming (rate limiting)
    • CSRF bescherming op state-changing endpoints
    • File upload validatie (type, grootte)
  4. Distributie naar alle tenants

    Na goedkeuring op beta.brinxx.nl:

    Terminal window
    # Gebruik bestaande tenant-sync workflow
    # Zie: /plans/brinxx-sync-plan voor het sync proces

    Volgorde:

    1. magic_development (template)
    2. magic_brinxx (beta → productie)
    3. magic_default, magic_demo (test tenants)
    4. magic_jodasign, magic_logohorloge, magic_bovisales, magic_desluis (productie tenants)
  5. Post-deploy verificatie

    Per tenant controleren:

    • Login + 2FA flow
    • Dashboard overview met data
    • Facturen/offertes laden correct
    • PDF download werkt
    • Taalswitch werkt
    • Mobile responsive OK

FeaturePackageMedusa vereisteLicentieStatus
i18n (UI)@magiverse/i18n-InternAl aanwezig in storefront
2FA / OTPEigen implementatie via SMTP--Te bouwen
2FA / OTP (alternatief)@perseidesjs/auth-otp>= 2.3.0CommercieelEvalueren kosten
E-mail verificatie@nicogorga/medusa-auth-emailpass-verified>= 2.10.3MITTe installeren
Passkey (fase 2)@tsc_tech/medusa-plugin-auth-passkey>= 2.4.0MITLater ticket
Wishlistmedusa-plugin-wishlist (RSC-Labs)2.0+MITTe installeren
E-mail templates@react-email/components + Resend-MITTe installeren
Iconenlucide-react-MITTe installeren
PDF (backend)pdfkit-MITAl aanwezig
ZIP exportjszip-MITAl aanwezig in backend

┌─────────────────────────────────────────────────────────────────┐
│ FASE │ INHOUD │ GESCHATTE DUUR │
├──────────┼─────────────────────────────────────┼────────────────┤
│ Fase 1 │ Huisstijl & Design System │ 3-4 dagen │
│ Fase 2 │ Authenticatie & 2FA │ 4-5 dagen │
│ Fase 3 │ Orders & Ordergeschiedenis │ 3-4 dagen │
│ Fase 4 │ Facturen, Offertes & Retouren │ 5-6 dagen │
│ Fase 5 │ Profiel & Dashboard uitbreiding │ 2-3 dagen │
│ Fase 6 │ Multi-language (account pagina's) │ 3-4 dagen │
│ Fase 7 │ Wishlist + E-mail templates │ 4 dagen │
│ Fase 8 │ Testing & uitrol alle tenants │ 2-3 dagen │
├──────────┼─────────────────────────────────────┼────────────────┤
│ TOTAAL │ │ 26-33 dagen │
└──────────┼─────────────────────────────────────┴────────────────┘
Afhankelijkheden:
Fase 1 ──────────▶ Fase 3 (styling nodig voor orders)
Fase 1 ──────────▶ Fase 4 (styling nodig voor facturen/offertes)
Fase 1 ──────────▶ Fase 5 (styling nodig voor dashboard)
Fase 2 ──────────▶ Fase 5 (2FA componenten in profiel)
Fase 4 ──────────▶ Fase 5 (factuur/offerte data voor dashboard)
Fase 1-7 ────────▶ Fase 8 (alles moet klaar zijn voor testing)
Fase 6 kan parallel met Fase 3-5 (onafhankelijke i18n laag)

RisicoImpactMitigatie
@perseidesjs/auth-otp is te duurVertraging 2FAEigen OTP via SMTP bouwen (Optie B, aanbevolen)
APLT store routes bestaan nog nietFase 4 blokkeertBackend routes als eerste oppakken in Fase 4
Klant-matching via e-mail faaltKlant ziet geen dataValidatie toevoegen: als aplt_customers geen match heeft, toon “Neem contact op” bericht
Multi-tenant theming breektVisuele bugs per tenantTheme testen op minimaal 3 tenants (brinxx, default, jodasign) in elke fase
PDF generatie traag voor klantSlechte UXLoading state tonen, PDF async genereren als het > 3s duurt
File upload kwetsbaarhedenBeveiligingsrisicoStrikte validatie: alleen JPG/PNG, max 5MB, virus scan overwegen
2FA lock-outKlant kan niet inloggenHerstelcodes (8 stuks) + admin override mogelijkheid
Medusa v2.13.1 breaking changesPlugin incompatibiliteitPlugins testen in development vóór installatie op tenants



Aangemaakt door: Wayne | Toegewezen aan: Michiel | Akkoord Wayne vereist voor start ontwikkeling

Plan opgesteld: 4 maart 2026 | Geschatte doorlooptijd: 26-33 werkdagen