Simplify registration flow: mandatory account, deferred payment, email reminders #6
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
EventRegistrationForm.tsxand the surrounding registration components are currently too busy. The form tries to handle authentication nudges, optional account creation (dismissible modal), and a Mollie checkout redirect all in one shot, with varying behavior depending on whether the user is logged in. This creates confusion and edge cases.This issue specifies a simpler, linear flow and three new transactional emails.
New User Flow
Step 1 — Choose role
User sees the existing
TypeSelector(two cards):No sign-in / sign-up prompt at this stage. Remove the "Al een account?" nudge banner from
EventRegistrationForm.tsx.Step 2 — Fill in info
The regular
PerformerForm/WatcherFormfields (name, email, phone, etc.) exactly as today. No changes to the field set itself.Remove the
isLoggedIn/ prefill logic from the forms — no more pre-filling from session and no read-only email field for logged-in users. (The user fills in their own info; the account they create in step 3 will be linked by email.)Step 3 — Bevestigen → mandatory account creation
After the user taps Bevestigen:
submitRegistrationis called as today (creates the DB row, sends confirmation email — see email section below).AccountModalor redirecting straight to Mollie, always redirect the user to/login?signup=1&email=<encoded-email>&next=/account(or a dedicated/registerpage if preferred). Account creation is not optional./account.Step 4 — Payment (Bezoeker only)
On
/account, ifregistration.paymentStatus === "pending"andregistration.registrationType === "watcher", show a prominent "Betaal nu" CTA that callsgetCheckoutUrland redirects to Mollie. This replaces the current direct post-form Mollie redirect.The user can close the browser and pay later — the outstanding payment is shown persistently on
/accountuntil resolved.What needs to change
apps/web/src/components/homepage/EventRegistrationForm.tsxauthClient.useSession()call, theisLoggedIn/ prefill logic (lines 48–71).setSuccessState/onSuccess, do not renderSuccessScreen. Instead, redirect to/login?signup=1&email=<email>&next=/account.successStateinterface andSuccessScreenimport can be removed from this file (SuccessScreen may still be used elsewhere, keep the component itself).apps/web/src/components/registration/WatcherForm.tsxAccountModalinterstitial that currently appears after successful registration.getCheckoutUrlcall +window.locationredirect.onSuccess(token, email, name)(same asPerformerForm) and let the parent handle the redirect.isLoggedIn/ prefill props (prefillFirstName,prefillLastName,prefillEmail,isLoggedIn); the form is always unauthenticated at this point.apps/web/src/components/registration/PerformerForm.tsxprefillFirstName,prefillLastName,prefillEmail,isLoggedInprops.onSuccess), but the parent now handles the account redirect.apps/web/src/routes/account.tsxregistration.paymentStatus === "pending"andregistration.registrationType === "watcher", render a "Betaal je inschrijving" card (or banner) with:orpc.getCheckoutUrl({ token: registration.managementToken })and redirects to the returnedcheckoutUrl.claimRegistrationCredit()call on mount covers the case where the user already paid before creating an account — keep that.apps/web/src/components/registration/SuccessScreen.tsx/manage/<token>page. Check usages first; if only used fromEventRegistrationForm, remove it.New Email Triggers
Three new transactional emails need to be added to
packages/api/src/email.tsand called from the appropriate places.1. Signup confirmation (on submit)
Trigger:
submitRegistrationhandler inpackages/api/src/routers/index.ts— already callssendConfirmationEmail. No new email needed here — this is the existing confirmation email. However, update the template to:2. Payment reminder after 3 days (Bezoeker only, unpaid)
New function:
sendPaymentReminderEmailTrigger: Cron job (
api/cron/reminders.tscallsrunSendReminders()inpackages/api/src/routers/index.ts)Logic:
Email content (Dutch):
/account.3. Payment confirmation (on Mollie webhook success)
New function:
sendPaymentConfirmationEmailTrigger:
apps/web/src/routes/api/webhook/mollie.ts, Branch B (registration payment), afterpaymentStatusis set to"paid".Email content (Dutch):
/accountto view QR.Schema changes
Add one nullable timestamp column to the
registrationtable:Generate and apply a Drizzle migration after adding this field.
Acceptance criteria
/accountshows a "Betaal nu" CTA for watchers withpaymentStatus === "pending"./accountis the final step.claimRegistrationCreditlogic are untouched.paymentReminderSentAtDB column + migration exist.SuccessScreencomponent removed if unused; otherwise kept as-is.All work is now complete and committed in
8221c7e.What was done
Registration flow
EventRegistrationForm.tsx/WatcherForm.tsx/PerformerForm.tsx: removed session/login nudge/SuccessScreen; both roles now callredirectToSignup(email)→/login?signup=1&email=<email>&next=/accountafter successful submit (account creation mandatory)SuccessScreen.tsxdeletedAccount page
account.tsx: addedcheckoutMutation+ "Betaal nu" CTA card shown whenregistrationType === "watcher"ANDpaymentStatus === "pending"ANDmanagementTokenexistsDB
packages/db/src/schema/registrations.ts:paymentReminderSentAtcolumn addedpackages/db/src/migrations/0008_payment_reminder_sent_at.sql: migration createdEmails (
packages/api/src/email.ts)registrationConfirmationHtml/sendConfirmationEmail: now includessignupUrl(built from recipient email as/login?signup=1&email=…&next=/account); "Account aanmaken" CTA replaces checkout CTAsendPaymentReminderEmail: new — sent to watchers with unpaid registration after 3 dayssendPaymentConfirmationEmail: new — sent after Mollie marks registration as paidCron reminders (
packages/api/src/routers/index.ts)runSendReminders()extended: after existing pre-registration reminders, now also queriesregistrationfor watchers wherepaymentStatus = "pending",paymentReminderSentAt IS NULL,createdAt ≤ now − 3 days; sendssendPaymentReminderEmailand stampspaymentReminderSentAtMollie webhook (
apps/web/src/routes/api/webhook/mollie.ts)sendPaymentConfirmationEmail(fire-and-forget, won't block the 200 response to Mollie)