npm run set-demo wipes the database, keeping the site settings and the admin account, and stages six upcoming events with registrations, payments, tickets, a waitlist, camp cabins, coupons, costs and funnel stats for showing the platform to other churches. Documented in backend/DEMO_DATA.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.1 KiB
Demo data
npm run set-demo replaces everything in the database with a realistic set of demo data,
for showing the platform to other churches. It stages six upcoming events with
registrations, payments, tickets and people, so every dashboard, report and check-in
screen has something to show.
This deletes real data. Run it only against a database you're happy to wipe, such as a dedicated demo deployment. It is not something to run on a live church site.
Running it
cd backend
npm run set-demo # shows the database it will wipe and asks you to type "demo"
npm run set-demo -- --yes # no prompt (for scripts)
The script uses DATABASE_URL from backend/.env. To point it at another database
for one run, set it in the environment first. A value already in the environment wins
over .env:
DATABASE_URL="postgresql://user:pass@host:5432/hope_demo" npm run set-demo
It takes a few seconds and is safe to re-run as often as you like. Each run wipes the previous demo and creates it again.
Before you run it
- The admin account must already exist. By default that's
joshua@crosscode.co.za. To keep a different account, setDEMO_ADMIN_EMAIL:If the account isn't found, the script stops before changing anything.DEMO_ADMIN_EMAIL="admin@example.org" npm run set-demo - Migrations must be up to date (
npm run prisma:deploy).
What it keeps and what it clears
| Kept | Cleared |
|---|---|
Site settings (AppSetting): org name, branding, SMTP, Yoco, WhatsApp, legal pages |
Every event, with its options, variants, early-bird tiers, sections, forms, costs and cash-ups |
| The admin account, including its login history, trusted devices and 2FA | Every other user |
| Migration history | Registrations, payments, tickets, scans, coupons, waitlists, consents, cabins, feedback, analytics, audit log |
Uploaded files on disk (public/uploads) |
Queued scheduled emails/WhatsApps (data/scheduled-emails.json) |
The wipe and the new data are written in one database transaction. If anything fails, nothing changes.
What it creates
All dates are worked out from the day you run it, so the events are always upcoming. Registrations are spread over the past month, as if sign-ups have been coming in. The data is generated from a fixed seed, so every run looks the same.
| When (from today) | Event | Shows off |
|---|---|---|
| ~1½ weeks, Sat 07:30 | Men's Breakfast: Built to Last | Sold out (60 places), 5 people on the waitlist, card / cash / EFT payments recorded by staff, 2 cancelled registrations |
| ~2½ weeks, Sat 10:00 | Family Fun Day | Several ticket types (adult, child, family pass), a non-ticket add-on (lunch pack), a fixed-amount coupon (FUNDAY20), entry and food-stall check-in sections |
| ~5 weeks, Fri–Sat | Rooted Women's Conference | Two early-bird tiers (one already expired, one still open) plus a Saturday-only ticket, T-shirt variants (S–XXL), a required sign-up form (dietary needs, workshop choice, prayer requests), a 20% group coupon (ROOTED20) used by 4 registrations |
| ~7 weeks, Mon–Thu | Ignite Summer Camp | Named attendees (campers aged 12–17), signed parental consent, gender collected, deposits and part-payments, campers placed in boys' and girls' cabins with staff leaders, sponsor-a-spot donations towards a 10-spot goal |
| ~8 weeks, Sun 19:00 | Carols by Candlelight | A free event with group tickets (1–6 people), plus general donations |
| ~14 weeks, Fri–Sun | Marriage Enrichment Weekend | Room-type variants with limited stock, early-bird prices per room type, a per-person spa add-on, R1 000 EFT deposits |
Each event also gets:
- A cover photo (from Unsplash, which
next.configalready allows) and a contact person. - Costs, both once-off and per item, so the profit view and cash-up have figures.
- About 30 days of funnel analytics (views → started → submitted → paid) by source (WhatsApp, Facebook, Instagram, email, direct) and device.
A typical run gives roughly:
| Users | 130 (plus the admin) |
| Registrations | ~220 (paid, part-paid, pending and cancelled) |
| Payments | ~180, including donations and sponsorships |
| Tickets | ~245 |
| Named campers | ~45 |
| Funnel events | ~1 700 |
Paid registrations have tickets with QR codes, the same way the app issues them once payment is complete. Part-paid and pending ones don't have tickets yet.
Logging in
Every demo account uses the password Demo@1234.
| Role | Good for showing | |
|---|---|---|
| Supervisor | lerato.khumalo@example.com |
Event setup, reports, cash-up, coupons |
| Staff | sipho.dlamini@example.com |
At-the-door payments and check-in scanning |
| Staff | anri.botha@example.com |
|
| Staff | daniel.pillay@example.com |
The other 126 people are ordinary members (role user), with emails like
thandi.mokoena@example.com. Log in as one of them to show the member view: their
registrations, tickets and payments. Any registrant's email from an event's attendee
list works.
Your own admin account keeps its existing password.
Safety
- Nothing is sent. The script writes straight to the database and never calls the email, WhatsApp or Yoco code.
- Nothing can reach a real person later. Demo people use
@example.comaddresses (a reserved domain that can't receive mail) and have no phone numbers, so reminders or broadcasts sent during a demo go nowhere. All parental consents are already signed, so no consent reminders go out. - Card payments have made-up Yoco ids (
demo_…). They don't exist in Yoco, so leave the demo site's Yoco settings in test mode or empty.
Customising
Everything lives in scripts/set-demo.js. There is one
build…() function per event (buildMensBreakfast, buildYouthCamp, …), and
main() decides which ones run. To change an event's name, prices, ticket types or
number of registrations, edit its function. To add an event, write a new build…()
and add it to the list in main().