AI is the headline, not a hidden feature.
THREE STATES:
1. NOT STARTED → dark hero:
'Let AI improve your messages'
'AI writes a different version of each message and tests
both with real donors. The better one wins automatically.'
[Start optimising] ← one button, AI does all 4 steps
2. TESTING → dark hero with pulse:
'AI is testing 4 experiments'
Each message shows side-by-side: Yours vs AI's version
Live conversion rates, progress bar to verdict
'Pick winners & start new round' button
3. OPTIMISED → dark hero with trophy:
'Messages optimised · 47 sent · 94% delivered'
[New round] ← keeps improving forever
INSIDE THE CONVERSATION:
A/B tests show as split cards within the chat:
┌──────────────────────────────┐
│ ✨ AI is testing this message │
├──────────────┬───────────────┤
│ Yours │ ✨ AI │
│ Hi Ahmed.. │ Ahmed, 47.. │
│ 33% │ 54% 🏆 │
│ 8/24 sent │ 14/26 sent │
├──────────────┴───────────────┤
│ ▓▓▓▓▓▓▓▓▓░░░ 72% │
│ AI version converts 21% better│
└──────────────────────────────┘
Normal messages (no test): click to edit inline.
Everything else: AI handles it.
807 → 394 lines. Removed everything that isn't the answer to
'What do my donors get?'
REMOVED:
- Step timeline tabs (4 across the top)
- Channel tabs (WhatsApp / Email / SMS)
- A/B variant toggle buttons
- AI rewrite toolbar (8 buttons)
- Variable chips panel
- Channel strategy matrix
- Delivery matrix table
- Strategy presets
- Template name editor
- Subject line editor
- Character counter
- Formatting cheatsheet
- Live feed accordion
- Stats bar
- Scheduled reminders list
- Message history feed
WHAT REMAINS:
One WhatsApp conversation showing all 4 messages.
That's the entire page.
- Click a message → it becomes editable inline (green bubble → textarea)
- Hover → '✨ Try a different approach' appears (AI generates variant B)
- A/B tests show as stacked bubbles with conversion rates
- '🏆 Pick winners' button appears when tests are running
- 'Change timing' link at the bottom (expandable, 3 dropdowns)
- Status line: 'Working · 47 sent · 94% delivered'
The phone mockup is the full-width page content, not a sidebar.
The input bar says 'Donors can reply: PAID · HELP · CANCEL'
Timestamp dividers: 'Instantly', 'Day 2 · if not paid', etc.
This is what Aaisha wants to see: her donors' experience.
All AI features now use Gemini 2.0 Flash via the existing API key.
Falls back to OpenAI if OPENAI_API_KEY is set instead.
Falls back to heuristics if neither key exists.
Gemini free tier: 15 RPM, 1M tokens/day, 1500 RPD
At PNPL's scale this is effectively unlimited and costs £0.
Changed:
- src/lib/ai.ts: chat() → tries Gemini first, OpenAI fallback
- src/app/api/automations/ai/route.ts: same dual-provider pattern
- docker-compose.yml: GEMINI_API_KEY added to app environment
All 11 AI features now work:
- Smart amount suggestions, message generation, fuzzy matching
- Column mapping, event parsing, impact stories, daily digest
- Nudge composer, donor classification, anomaly detection
- A/B variant generation, rewrites, auto-winner evaluation
THE AUTOMATION ENGINE IS NOW SELF-IMPROVING.
## Core: AI generates challenger variants
Click '✨ AI: Test a new approach' → GPT-4o-mini analyzes variant A
and creates variant B using a fundamentally DIFFERENT psychological
approach. Not a rephrase — a different strategy:
- Social proof ('47 others have already paid')
- Urgency (deadline framing)
- Impact storytelling ('£50 = 3 weeks of food')
- Personal connection (heavy name usage)
- Brevity (strip to minimum)
- Gratitude-first (lead with thanks)
- Loss framing ('pledge at risk of being unfulfilled')
- Community ('join 23 others who completed this week')
AI explains WHY: 'This variant uses social proof instead of a
gentle reminder — peer pressure converts better for step 2.'
## Core: Automatic winner promotion
Click '🏆 Pick winners' → system evaluates ALL running A/B tests:
1. Checks minimum sample size (20 sends per variant)
2. Runs z-test for statistical significance (90% confidence)
3. Promotes winner to variant A (resets counters)
4. Deletes loser
5. AUTOMATICALLY generates a NEW AI challenger
The cycle never stops. Messages continuously evolve.
## Core: AI rewrite toolbar
8 one-click AI rewrites for any template:
✂️ Make shorter · 💛 Make warmer · ⏰ Add urgency
👥 Add social proof · 💚 Add impact story · 🎯 Strip to essentials
🇵🇰 Translate to Urdu · 🇸🇦 Translate to Arabic
All rewrites preserve {{variable}} placeholders.
All use GPT-4o-mini (~/usr/bin/bash.15/1M tokens).
## UI: A/B Stats Card (below phone mockup)
- Side-by-side conversion rates with trophy icon on winner
- Progress bar to verdict (% of minimum sample collected)
- Lift calculation: 'Variant B converts 63% better'
- Real-time during test: 'A: 33% → B: 54% ★'
## UI: Winner Results Banner
After 'Pick winners' runs:
- Green banner: '🏆 Winners promoted — Gentle reminder · WhatsApp
→ Variant B wins (54% vs 33%) ✨ New AI challenger created'
- Gray banner if not enough data: 'Need 20+ sends per variant'
## API: /api/automations/ai (POST)
Actions:
- generate_variant: AI creates challenger B with strategy reasoning
- rewrite: AI rewrites template with specific instruction
- check_winners: evaluate all tests, promote, regenerate
## Architecture
The system is a GENETIC ALGORITHM for messaging:
1. Start with default templates (generation 0)
2. AI creates a challenger (mutation)
3. Traffic splits 50/50 (fitness test)
4. Winner survives, loser dies (selection)
5. AI creates new challenger (next generation)
6. Repeat forever → messages get better over time
COMPLETE RETHINK — from monitoring dashboard to message design studio.
## The Big Idea
Aaisha doesn't need a dashboard that says 'is it working?'
She needs a studio where she can SEE what Ahmed sees on his phone,
EDIT the words, TEST different approaches, and DESIGN cross-channel
sequences. The WhatsApp phone mockup is the star.
## New: Phone Mockups (3 channels)
- WhatsApp: green bubbles, blue ticks, org avatar, chat wallpaper,
full formatting (*bold*, _italic_, `code`, ━━━ dividers)
- Email: macOS mail client chrome, From header, subject line
- SMS: iOS Messages style, grey bubbles, contact avatar
## New: Template Editor
- Editable templates per step (receipt, day 2, 7, 14) per channel
- Live preview in phone mockup as you type
- Variable insertion chips: {{name}}, {{amount}}, {{reference}}, etc.
- Subject line editor for email channel
- Character count + SMS segment counter
## New: A/B Testing
- Create Variant B of any step/channel message
- 50/50 split traffic automatically
- Track sent count + conversion rate (paid after receiving)
- Side-by-side stats: 'A: 33% paid, B: 54% paid ★'
- Delete variant to revert to single message
## New: Channel Strategy Matrix
- 3 presets: Waterfall (default), Belt & Suspenders, Escalation
- Visual matrix: steps × channels with status indicators
- 1st = primary, fb = fallback, + = parallel send
- Waterfall: WhatsApp → SMS → Email (most cost-effective)
- Belt & Suspenders: all channels for receipts + final
- Escalation: start gentle (WA only), add channels as urgency increases
## New: Customizable Timing
- Each step's delay is editable inline (dropdown next to phone)
- Default: Day 2, Day 7, Day 14
- Can change to any schedule: Day 1, Day 3, Day 21, Day 28
## Schema: 2 new models
- MessageTemplate: per-org editable templates with A/B variants
(step, channel, variant, body, subject, splitPercent, sentCount, convertedCount)
- AutomationConfig: per-org timing + strategy + channel matrix
## API: /api/automations (GET/PATCH/DELETE)
- GET seeds defaults on first load (12 templates: 4 steps × 3 channels)
- PATCH upserts templates and config
- DELETE removes variant B and resets A to 100%
## Default templates (src/lib/templates.ts)
Extracted from hardcoded whatsapp.ts + reminders.ts into editable templates:
- WhatsApp: receipt, gentle, impact, final (with emoji + formatting)
- Email: receipt, gentle, impact, final (with cancel/pledge URLs)
- SMS: receipt, gentle, impact, final (160-char optimized)
## Architecture
templates.ts → resolvePreview() fills {{variables}} with examples
templates.ts → resolveTemplate() fills {{variables}} with real data
messaging.ts → sendToDonor() routes via channel waterfall
automations/route.ts → seeds + CRUD for templates + config
## Visual: Step timeline at top
4 tabs across the top with emoji, timing, description
Active step is dark (111827), others are white
Click to switch — editor and phone update together
## Layout
[Step Timeline — 4 tabs across top]
[Phone Mockup (left) | Editor (right)]
[Channel Strategy — expandable matrix]
[Live Feed — condensed stats + scheduled + messages]
THE STAR OF THE SHOW — the automation engine is now visible.
## New: Unified Messaging Layer (src/lib/messaging.ts)
Channel waterfall: WhatsApp → SMS → Email
- sendToDonor() routes to best available channel
- Respects donor consent flags (whatsappOptIn, emailOptIn)
- Falls back automatically if primary channel fails
- Every attempt logged to AnalyticsEvent for dashboard
## New: Email Integration (src/lib/email.ts)
Bring-your-own-key: charity pastes their Resend or SendGrid API key
- Resend: free 3,000 emails/month
- SendGrid: free 100/day
- Messages come from THEIR domain (donations@mymosque.org)
- Plain text auto-converted to clean HTML
## New: SMS Integration (src/lib/sms.ts)
Bring-your-own-key: charity pastes their Twilio credentials
- Pay-as-you-go (~3p per SMS)
- UK number normalization (07xxx → +447xxx)
- Reaches donors without WhatsApp or email
## New: /dashboard/automations — the visible engine
A. Dark hero stats: Messages this week per channel + delivery rate
B. Live channels: WhatsApp/Email/SMS with status, features, stats
C. The Pipeline: visual 4-step automation sequence
- What triggers, what's sent, which channels, waterfall explanation
D. Scheduled reminders: upcoming messages with timing
E. Message feed: recent messages with channel icon, status, time
## New: /api/messaging/status — dashboard data endpoint
Returns channels, stats (7 day), history (50 recent), pending reminders
## New: /api/messaging/test — send test message to admin
## Schema: 8 new Organization columns
emailProvider, emailApiKey, emailFromAddress, emailFromName
smsProvider, smsAccountSid, smsAuthToken, smsFromNumber
## Settings: 2 new channel rows in the checklist
- Email: provider selector (Resend/SendGrid) + API key + from address
- SMS: Twilio credentials + from number
Both follow the same checklist expand/collapse pattern
## Nav: Automations added between Money and Reports
Home → Collect → Money → Automations → Reports → Settings
## Stats tracking
Messages logged as AnalyticsEvent:
message.whatsapp.receipt.sent
message.email.reminder_1.failed
message.sms.reminder_2.sent
Donor PII masked in logs (last 4 digits of phone, email obfuscated)
THE INSIGHT:
Settings pages feel like work because they're designed as FORMS.
6 identical white boxes stacked vertically = 'I have to fill all this in?'
But Aaisha's mental model is a CHECKLIST:
'Am I set up? What's left? Let me fix the one thing that's missing.'
THE PATTERN:
Each setting has 3 visual states:
✓ CONFIGURED → single summary line
'Bank account · Barclays · ****5678' [Edit]
Clicking Edit expands the form inline.
○ NEEDS SETUP → expanded with instructions + form
The first unconfigured item auto-expands.
→ EDITING → expanded form with Save/Cancel
Save auto-collapses back. Green flash confirms.
THE RESULT:
- Everything configured? Page is SHORT. Green dots, one-liners.
- Something missing? That section is expanded and loud.
- No 'wall of forms' feeling.
- Only one section open at a time (accordion).
HEADER:
Old: Dark stats bar with 5 cells of status dots
New: Thin progress bar + human sentence
'You're all set' / '2 things left before you go live'
Counts only essentials (WhatsApp, bank, charity name).
LAYOUT:
Old: 6 separate bordered boxes, each with header + form
New: Single bordered container, divide-y between items
Each item is a clickable ROW that expands/collapses
Blue left-border accent on expanded form
Status dot: green=done, amber=needed, gray=optional
SPECIFICS:
- WhatsApp: collapsed to 'Connected · +447xxx · Receipts, reminders'
Expands to show QR or features grid
- Bank: collapsed to 'Barclays · ****5678'
Expands to form + live 'What donors see' preview
- Charity: collapsed to name + color swatch
Expands to form + pledge page header preview
- Stripe: collapsed to 'Connected' or 'Optional'
Expands to key field + webhook setup
- Team: collapsed to '3 members · 1 leader'
Expands to member list + invite flow
- GoCardless: dimmed when collapsed (advanced), expands normally
NEW COMPONENTS:
- SettingRow: generic expand/collapse row pattern
- WhatsAppRow: special case with QR polling
- TeamRow: special case with member list + invite
- SaveRow: Save + Cancel buttons, auto-collapse on save
- Field: reusable input (unchanged)
Model: PNPL never touches the money. Each charity connects their own
Stripe account by pasting their API key in Settings. When a donor
chooses card payment, they're redirected to Stripe Checkout. The money
lands in the charity's Stripe balance.
## Schema
- Organization.stripeSecretKey (new column)
- Organization.stripeWebhookSecret (new column)
## New/rewritten files
- src/lib/stripe.ts — getStripeForOrg(secretKey), per-org client
- src/app/api/stripe/checkout/route.ts — uses org's key, not env var
- src/app/api/stripe/webhook/route.ts — tries all org webhook secrets
- src/app/p/[token]/steps/card-payment-step.tsx — redirect to Stripe
Checkout (no fake card form — Stripe handles PCI)
## Settings page
- New 'Card payments' section between Bank and Charity
- Instructions: how to get your Stripe API key
- Webhook setup in collapsed <details> (optional, for auto-confirm)
- 'Card payments live' green banner when connected
- Readiness bar shows Stripe status (5 columns now)
## Pledge flow
- PaymentStep shows card option ONLY if org has Stripe configured
- hasStripe flag passed from /api/qr/[token] → PaymentStep
- Secret key never exposed to frontend (only boolean hasStripe)
## How it works
1. Charity pastes sk_live_... in Settings → Save
2. Donor opens pledge link → sees 'Bank Transfer', 'Direct Debit', 'Card'
3. Donor picks card → enters name + email → redirects to Stripe Checkout
4. Stripe processes payment → money in charity's Stripe balance
5. (Optional) Webhook auto-confirms pledge as paid
Payment options:
- Bank Transfer: zero fees (default, always available)
- Direct Debit via GoCardless: 1% + 20p (if org configured)
- Card via Stripe: standard Stripe fees (if org configured)
Stripe was wired up but never used:
- No STRIPE_SECRET_KEY in .env
- Card payment step had a 'simulated fallback' that pretended to charge
- Stripe fees (1.4% + 20p) contradict '100% goes to charity' brand promise
- Bank transfer is the primary rail, GoCardless (DD) is the secondary
Removed:
- src/lib/stripe.ts (Stripe client, checkout sessions, webhooks)
- src/app/api/stripe/checkout/route.ts
- src/app/api/stripe/webhook/route.ts
- src/app/p/[token]/steps/card-payment-step.tsx (263 lines)
- 'stripe' and '@stripe/stripe-js' npm packages
- Card option from PaymentStep (payment-step.tsx)
- Card references from confirmation-step.tsx, success/page.tsx
- Stripe from landing page integrations grid
- Stripe from privacy policy sub-processors
- Stripe from terms of service payment references
Type Rail changed: 'bank' | 'gocardless' | 'card' → 'bank' | 'gocardless'
Pledge flow bundle: 19.5kB → 18.2kB (-1.3kB)
Payment options donors now see:
1. Bank Transfer (recommended, zero fees)
2. Direct Debit via GoCardless (1% + 20p, hassle-free)
Before: Mediocre — shadcn <Input>, no visual hierarchy, no readiness
indicator, no donor preview, inconsistent headers, flat team list.
After: Every element matches the brand system used in Collect/Money/Reports.
Changes:
1. READINESS BAR (dark hero section)
- 4-cell gap-px grid on #111827 background
- Green/gray dots: WhatsApp ✓, Bank ✗, Charity ✓, Team: 2 members
- Aaisha sees instantly what's configured and what's missing
2. SECTION HEADERS (consistent pattern)
- All sections: colored icon box + title + description
- border-b separator matching Reports/Money pattern
- WhatsApp: green icon box. Bank: green when configured.
3. FIELD COMPONENT (no more shadcn Input)
- Reusable <Field> with uppercase tracking-wide label
- border-2 focus:border-[#1E40AF] (sharp, no rounded)
- Consistent height (h-10) and padding across all inputs
4. BANK ACCOUNT — DONOR PREVIEW
- New: shows exactly what donors see after pledging
- Grid layout with bank name, sort code, account, reference
- 'What donors see after pledging' preview card
- Context tip: 'Changes apply to new pledges immediately'
5. CHARITY — BRAND PREVIEW
- Shows logo mark (first letter in brand color square) + name
- Color picker is now a swatch + hex input
- 'Preview — pledge page header' section
6. TEAM MANAGEMENT
- Role cards with icon boxes and colored badges
- Gap-px grid for WhatsApp features (connected state)
- Credentials grid layout (not prose)
- Empty state with icon + helpful text
- Role icons: Crown (admin), Users (leader), Eye (staff/volunteer)
- Color-coded: blue admin, amber leader, gray staff
7. WHATSAPP PANEL
- Connected: gap-px 3-column grid (Receipts/Reminders/Chatbot)
- Not connected: border-l-2 accent list, PAID/HELP/CANCEL in mono
- QR scanning: border-l-2 instructions
- onStatusChange callback feeds the readiness bar
8. DIRECT DEBIT
- Custom <details> with ChevronRight rotation
- border-l-2 contextual tip ('most charities don't need this')
9. SAVE BUTTONS
- Extracted <SaveBtn> component
- Green flash on save (bg-[#16A34A])
- 'Save changes' / 'Saving…' / 'Saved' states
## New: Community Leader role
Who: Imam Yusuf, Sister Mariam, Uncle Tariq — the person who rallies
their mosque, WhatsApp group, neighbourhood to pledge.
Not an admin. Not a volunteer. A logged-in coordinator who needs
more than a live feed but less than full admin access.
/dashboard/community — their scoped dashboard:
- 'How are WE doing?' — their stats vs the whole appeal (dark hero section)
- Contribution percentage bar
- Their links with full share buttons (Copy/WhatsApp/Email/QR)
- Create new links (auto-tagged with their name)
- Leaderboard: 'How communities compare' with 'You' badge
- Read-only pledge list (no status changes, no bank details)
Navigation changes for community_leader role:
- Sees: My Community → Share Links → Reports (3 items)
- Does NOT see: Home, Money, Settings, New Appeal button
- Does NOT see: Bank details, WhatsApp config, reconciliation
## New: Team management API + UI
GET/POST/PATCH/DELETE /api/team — CRUD for team members
- Only org_admin/super_admin can invite
- Temp password generated on invite (shown once)
- Copy credentials or send via WhatsApp button
- Role selector with descriptions (Admin, Community Leader, Staff, Volunteer)
- Role change via dropdown, remove with trash icon
- Can't change own role or remove self
## Settings page redesign
Reordered by Aaisha's thinking:
1. WhatsApp (unchanged — most important)
2. Team (NEW — 'who has access? invite community leaders')
3. Bank account
4. Charity details
5. Direct Debit (collapsed in <details>)
Team section shows:
- All members with role icons (Crown/Users/Eye)
- Inline role change dropdown
- Remove button
- Invite form with role cards and descriptions
- Credentials shown once with copy + WhatsApp share buttons
## Admin page redesign
Brand-consistent: no more shadcn Card/Badge/Table
- Dark hero section with 7 platform stats
- Pipeline status breakdown (gap-px grid)
- Pill tab switcher (not shadcn Tabs)
- Grid tables matching the rest of the dashboard
- Role badges color-coded (blue super, green admin, amber leader)
6 files changed, 4 new routes/pages
The reconcile feature was hidden behind a text link at the bottom of
the Money page. That's backwards. Reconciliation IS the Money page.
Aaisha's actual thought process:
1. People pledged
2. Some say they paid
3. 'Did the money actually arrive?' → opens bank website, downloads CSV
4. 'Let me match it' → THIS SHOULD BE RIGHT HERE, not behind a link
5. '8 out of 10 matched' → pledges auto-move to 'received'
Changes:
- Full bank statement upload area is NOW embedded directly in /dashboard/money
With icon, description, drop zone — always visible, not a link
- When CSV is selected: file name + detected bank format shown inline
Column mapping is collapsed by default (auto-detected) but expandable
- 'Match payments' button is blue, full-width, prominent
- Results appear INLINE below the upload area:
- Summary stats (gap-px grid): rows, incoming, matched, possible, auto-confirmed
- Green success banner when pledges are auto-confirmed
- Full match results table with confidence icons
- 'Upload another' button to reset
- After matching: dashboard data auto-refreshes to show updated pledge statuses
- 'Said they paid' section now says 'Upload a bank statement above to confirm'
instead of linking to a separate page
- /dashboard/reconcile now redirects to /dashboard/money (backward compat)
- Contextual sections (confirm/nudge) hide when match results are showing
to avoid visual clutter
Architecture is now:
Stats → MATCH PAYMENTS → Confirm these → Need a nudge → All pledges table
Not: Stats → table → small link at bottom → navigate away → separate page
## Money page (/dashboard/money) — context-aware inbox
The key insight: Aaisha's #1 Money question changes over time.
Day 1: 'Did anyone pledge?' → Recent section
Day 3: 'Are they paying?' → Confirm section
Day 10: 'Who hasn't paid?' → Nudge section
Day 30: 'Give me the spreadsheet' → she goes to Reports
Changes:
- Contextual 'Confirm these payments' section (amber)
Shows when there are 'said they paid' pledges
One-click green 'Confirm' button on each row
Links to bank statement upload
Only appears on 'all' filter (not when already filtering)
- Contextual 'These people need a nudge' section (red)
Shows when there are overdue pledges
One-click green 'Nudge' WhatsApp button + 'Paid' quick button
Shows days since pledge for urgency
- Stats bar redesigned: 5 clickable stat cells (gap-px)
Each acts as a filter toggle with active underline
Color-coded: amber for 'said paid', red for overdue, green for received
- Filter pills replace shadcn Tabs (smaller, more buttons fit)
Pill buttons instead of tab strip — works better on mobile
- Table kept for Fatima (power user who scans everything)
Same columns, actions, pagination as before
- Match payments CTA promoted: full-width card with icon + description
No longer a text link hidden at the bottom
## Reports page (/dashboard/reports) — Fatima's dashboard
The key insight: Fatima (treasurer) logs in monthly.
She should NOT need to visit any other page.
Changes:
- Financial summary hero (dark section)
Total promised, total received, outstanding, collection rate
Progress bar with percentage
Same visual language as leaderboard hero
- Status breakdown with visual bars
Horizontal bars showing distribution: paid/waiting/initiated/overdue
Percentage labels
- Per-appeal breakdown table
Each appeal: pledges, promised, received, collection rate
Total row at bottom for multi-appeal orgs
Rate color-coded: green ≥70%, amber ≥40%, gray below
- Gift Aid section with PREVIEW
Shows number of eligible declarations + reclaimable amount
before downloading — Fatima can see if it's worth running
'25p for every £1' callout
- Downloads: Full CSV + Gift Aid CSV
Same download functionality, better presentation
- API/Zapier section redesigned
Two endpoint examples (pledges + dashboard)
Clearer documentation for Zapier/Make integration
- Activity log section
Shows recent system activity (audit trail)
Scrollable, max 20 entries
2 pages rewritten (~38k bytes)
Core insight: The primary object is the LINK, not the appeal.
Aaisha doesn't think 'manage appeals' — she thinks 'share my link'.
## Collect page (/dashboard/collect) — complete rewrite
- Flattened hierarchy: single-appeal orgs see links directly (no card to click)
- Multi-appeal orgs: quiet appeal switcher at top, links below
- Inline link creation: just type a name + press Enter (no dialog)
- Quick preset buttons: 'Table 1', 'WhatsApp Group', 'Instagram', etc.
- Share buttons are THE primary CTA on every link card (Copy, WhatsApp, Email, Share)
- Each link shows: clicks, pledges, amount raised, conversion rate
- Embedded mini-leaderboard when 3+ links have pledges
- Contextual tips when pledges < 5 ('give each volunteer their own link')
- New appeal creation is inline, auto-creates 'Main link'
## Appeal detail page (/dashboard/events/[id]) — brand redesign
- Sharp edges, gap-px grids, typography-as-hero
- Same link card component with share-first design
- Embedded leaderboard section
- Inline link creation (same as Collect)
- Clone appeal button
- Appeal details in collapsed <details> (context, not hero)
- Download all QR codes link
- Public progress page link
## Leaderboard page — brand redesign
- Total raised as hero number (dark section)
- Progress bars relative to leader
- Medal badges for top 3
- Conversion rate badges
- Auto-refresh every 10 seconds (live event mode)
## Route cleanup
- /dashboard/events re-exports /dashboard/collect (backward compat)
- Old events/page.tsx removed (was duplicate)
5 files changed, 3 pages redesigned
New: /dashboard/welcome — guided first-time setup
- Step 1: 'What are you raising for?' (starts with what excites them)
- Step 2: 'Where should donors send money?' (natural follow-up)
- Step 3: 'Want auto-reminders?' (WhatsApp as bonus, skippable)
- Step 4: 'Here's your link!' (dark section with copy/WhatsApp/share)
- Auto-creates event + first pledge link during flow
- User holds a shareable link within 90 seconds of signing up
Updated: /dashboard (context-aware home)
- State 1 (no events): auto-redirects to /dashboard/welcome
- State 2 (0 pledges): shows pledge link + share buttons prominently
- State 3 (has pledges): shows stats + feed
- State 4 (has 'said paid'): amber prompt to upload bank statement
- State 5 (100% collected): celebration banner
- No more onboarding checklist — dashboard adapts instead
- Event name as page header (not generic 'Home')
- Event switcher for multi-event orgs
Updated: /signup → redirects to /dashboard/welcome (not /dashboard)
Persona spec: docs/PERSONA_JOURNEY_SPEC.md
Navigation: goal-oriented, not feature-oriented
- Overview → Home
- Campaigns → Collect ('I want people to pledge')
- Pledges → Money ('Where's the money?')
- Exports → Reports ('My treasurer needs numbers')
- Old routes still work via re-exports
Terminology: human language, not SaaS jargon
- new → Waiting
- initiated → Said they paid
- paid → Received ✓
- overdue → Needs a nudge
- Campaign → Appeal
- QR Source → Pledge link
- Reconcile → Match payments
- Rail → Payment method
- Pipeline by Status → How pledges are doing
- Conversion rate → % who pledged
- CRM Export Pack → Full data download
Visual identity: brand-consistent dashboard
- Sharp edges (no rounded-lg cards)
- Gap-px grids for stats (brand signature pattern)
- Left-border accents (brand signature pattern)
- Midnight/Paper/Promise Blue 60-30-10 color rule
- Typography as hero (big bold numbers, not card-heavy)
- No emoji in UI chrome
- Brand-consistent status badges (colored bg + text, not shadcn Badge)
- Consistent header typography (text-3xl font-black tracking-tight)
Pages rewritten: layout, home, events (collect), pledges (money),
exports (reports), reconcile, settings
Reconcile: auto-detects bank CSV format via presets + AI before upload
UX spec: docs/UX_OVERHAUL_SPEC.md
- Removed all .pi/ (agents, themes, extensions, skills, observatory)
- Removed CLAUDE.md (belongs in parent pi repo)
- Added .pi/ and CLAUDE.md to .gitignore
- Added .pi/ to pledge-now-pay-later/.gitignore
- Shorten quote 03 from 'Can I split it across a few months?' to 'Can I pay monthly?' for column symmetry
- Add nbsp between 'money' and 'arriving' to prevent orphan line break
ROOT CAUSE: each card was wrapped in its own div (min-h-[85vh]),
scoping sticky to that wrapper — cards could NEVER overlap.
FIX: flatMap returns all sticky divs + h-4 spacers as direct
siblings under the same parent (mt-14). Sticky now works
correctly — each card overlaps the previous with 20px peek.
- Removed all shadows (border-gray-100 only)
- z-index: 1-4 (was 10-40, conflicting with nav z-40)
- top: 72/92/112/132px (20px stagger)
- h-4 spacers between cards (no big white gaps)
- Regenerated dinner image: dark navy table, candlelight, £5,000
pledge card — zero white space (was white tablecloth)
STACKING EFFECT:
- Cards use position: sticky with increasing top offset (72-120px)
- z-index layering (10-40) so later cards stack on top
- pb-36 between cards for scroll breathing room
- will-change-transform for smooth compositing
WHITE SPACE FIX:
- Removed border border-gray-200 (was creating visible white gap)
- Replaced with shadow-[0_2px_8px_rgba(0,0,0,0.08)] for depth
- Switched from CSS Grid to flexbox (flex-row/flex-row-reverse)
- Image fills full card height via flexbox stretch
LAYOUT:
- md:w-7/12 for image, md:w-5/12 for text
- min-h-[400px] on desktop for substantial card presence
- Alternating image left/right preserved for visual rhythm
PERSONA OVERHAUL:
- Personas now defined by WHAT THEY DID, not job titles
- 'Charity Manager' -> 'You organized the dinner'
- 'Personal Fundraiser' -> 'You shared the link'
- 'Volunteer' -> 'You were on the ground'
- 'Organisation/Programme Manager' -> 'You claim the Gift Aid'
SECTION HEADING:
- Brand core insight: 'People don't break promises. Systems do.'
- Eyebrow: 'THE PLEDGE GAP'
- Sub: 'We built the missing system between I'll donate and the money arriving.'
PAIN STATS (visual anchors):
- £50,000 pledged / £22,000 collected (the gap)
- 23 said I'll donate / 8 actually did
- 40 pledges collected / 0 updates received
- 200 rows, 47 typos / 6 hours every quarter
COPY: Emotionally precise, tells each persona's specific story
PHOTOGRAPHY (4 cinematic moment shots):
- Dinner aftermath: empty table with lone pledge card, chandeliers
- Phone: hands on WhatsApp at kitchen table, warm light
- Volunteer: seen from behind, walking between gala tables with cards
- Desk still life: laptop spreadsheet, papers, tea, window light
- All 2:1 wide aspect, 2.7MB -> 260KB optimized
HEADLINE:
- 3 balanced lines: 'Turn I'll donate' / 'into money' / 'in the bank.'
- Removed that orphaned 'money' on its own line
- <br className='hidden lg:block'> controls breaks on desktop only
IMAGE:
- Hero container: max-w-5xl -> max-w-7xl (image 25% wider)
- Stat strip widened to match
- Much more of the gala scene visible, phone prominent
DEPLOY SPEED (deploy.sh):
- Persistent /opt/pnpl/ build dir (no temp dir creation/deletion)
- BuildKit with cache mounts (npm + .next/cache)
- No more docker builder prune / docker rmi (preserves cache!)
- Installed docker-buildx v0.31.1 on server
- Before: ~245s (4+ min) After: ~29s (cached) / ~136s (first)
- Use: cd pledge-now-pay-later && bash deploy.sh
- Grid: items-start → md:items-stretch (both columns same height)
- Image: aspect-[4/5] → md:aspect-auto md:h-full (fills column)
- Mobile keeps aspect-[3/4] for stacked layout
- Bottom of image now lines up with buttons/trust line
HERO REDESIGN:
- bg-gray-950 full-bleed dark hero (was white text-on-white)
- Split layout: 7-col massive headline + 5-col documentary photo
- Gala photo (02) as hero — warm tungsten pops against dark bg
- border-l-2 promise-blue eyebrow accent (signature pattern 1)
- gap-px stat strip: 30-50%, 60s, £0, 2 min (signature pattern 2)
- stagger-children animation on text column
- Delayed fade-up on image column (opacity: 0 → fadeUp after 250ms)
- Trust line with vertical pipe separators
- Merges old hero + hero image + stat section into 1 cinematic opening
NAV:
- Wordmark hidden on mobile (sm:inline), shows P mark only
- shrink-0 on logo, whitespace-nowrap on nav buttons
PERSONA CARDS:
- Charity Manager card now uses mosque photo (08) for variety
- Hover color: text-trust-blue → text-promise-blue (proper token)
Brand compliant: 0 violations (no gradients, rounded-2xl, backdrop-blur)
Main page (pledge.quikcue.com):
- Hero: 'Turn I'll donate into money in the bank'
- 30-50% stat in dark section (single number, maximum impact)
- 4 persona cards linking to /for/* pages
- 4-step how-it-works (tightened from previous)
- Compliance strip (Gift Aid, Zakat, email, WhatsApp - compact)
- Payment flexibility (now/later/monthly)
- Platform logos
- Dark CTA section
- Footer with persona links
/for/charities:
- Pain: pledges on napkins, awkward chasing, no visibility
- 5-step how-it-works specific to charity managers
- 6 features: Gift Aid, Zakat, WhatsApp, scheduling, GDPR, exports
- CTA: Start Free
/for/fundraisers:
- Pain: shared link 50 times, 3 donated
- Before/after comparison grid (without vs with)
- 6 external platforms with branding
- CTA: Start Free
/for/volunteers:
- Personal link, live stats, leaderboard
- Event night flow (4 steps)
- Share channels grid
- CTA: Tell your charity about this
/for/donors:
- Educational trust page, not a sign-up funnel
- 6-step pledge flow explained
- Data protection table (what/why for each field)
- FAQ (cancel, already paid, no WhatsApp consent)
- CTA: Are you a charity?
GIFT AID (HMRC compliance):
- Exact HMRC model declaration text displayed and recorded
- Home address (line 1 + postcode) collected when Gift Aid is ticked
- giftAidAt timestamp recorded separately from the boolean
- Declaration text, donor name, timestamp stored in consentMeta JSON
EMAIL + WHATSAPP (GDPR/PECR compliance):
- Separate, granular opt-in checkboxes (not bundled, not pre-ticked)
- Each consent records: exact text shown, timestamp, consent version
- Consent checkboxes only appear when relevant contact info is provided
- Cron reminders gated on consent — no sends without opt-in
- Pledge creation WhatsApp receipt gated on whatsappOptIn
AUDIT TRAIL (consentMeta JSON on every pledge):
- giftAid: {declared, declarationText, declaredAt}
- email: {granted, consentText, grantedAt}
- whatsapp: {granted, consentText, grantedAt}
- IP address captured server-side from x-forwarded-for
- User agent captured client-side
- consentVersion field for tracking wording changes
EXPORTS:
- CRM CSV now includes: donor_address, donor_postcode, gift_aid_declared_at,
is_zakat, email_opt_in, whatsapp_opt_in
- Gift Aid export has full HMRC-required fields
Schema: 6 new columns on Pledge (donorAddressLine1, donorPostcode,
giftAidAt, emailOptIn, whatsappOptIn, consentMeta)
POSITIONING FIX — PNPL is NOT just 'QR codes at events':
- Charities collecting at events (QR per table)
- High-net-worth donor outreach (personal links via WhatsApp/email)
- Org-to-org pledges (multi-charity projects)
- Personal fundraisers (LaunchGood/Enthuse redirect)
TERMINOLOGY (throughout app):
- Events → Campaigns (sidebar, pages, create dialogs, onboarding)
- QR Codes page → Pledge Links (sharing-first, QR is one option)
- Scans → Clicks (not just QR scans)
- 'New Event' → 'New Campaign'
- 'Create QR Code' → 'Create Pledge Link'
- Source label: 'Table Name' → 'Source / Channel'
SHARING (pledge links page):
- 4-button share row: Copy · WhatsApp · Email · More (native share)
- Each link shows its full URL
- Create dialog suggests: 'WhatsApp Family Group, Table 5, Instagram Bio'
- QR code is still shown but as one option, not the hero
LANDING PAGE (complete rewrite):
- Hero: 'Collect pledges. Convert them into donations.'
- 4 use case cards: Events, HNW Donors, Org-to-Org, Personal Fundraisers
- 'Share anywhere' section: WhatsApp, QR, Email, Instagram, Twitter, 1-on-1
- Platform support: Bank Transfer, LaunchGood, Enthuse, JustGiving, GoFundMe, Any URL
- Islamic fund types section: Zakat, Sadaqah, Sadaqah Jariyah, Lillah, Fitrana
ZAKAT & FUND TYPES:
- Organization.zakatEnabled toggle in Settings
- Pledge.fundType: general, zakat, sadaqah, lillah, fitrana
- Identity step: fund type picker (5 options) when org has zakatEnabled
- Zakat note: Quran 9:60 categories reference
- Settings: toggle card with fund type descriptions
FUND ALLOCATION:
- Event.fundAllocation: 'Mosque Building Fund', 'Orphan Sponsorship' etc.
- Charities can also add external URL for reference/allocation (not just fundraisers)
- Shows on campaign cards and pledge flow
AUTH0 SETUP (done via Management API):
- Created 'Pledge Now Pay Later' app (regular_web) on quikcue.us.auth0.com
- Enabled connections: Google, Apple, Username-Password
- Callback: https://pledge.quikcue.com/api/auth/callback/auth0
- Client ID: hpr7JcEAAk3Q5ADkzyyZSRDxGIZTcjRJ
CODE CHANGES:
- Auth0Provider added to NextAuth alongside existing CredentialsProvider
- findOrCreateSocialUser(): first Google login auto-creates org + user
- Login page: 'Continue with Google' button at top, email/password below
- Signup page: 'Sign up with Google' button at top, form below
- JWT callback: resolves Auth0 users to DB users on every token refresh
- Docker compose: AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_ISSUER env vars
FLOW:
- Click 'Continue with Google' → Auth0 Universal Login → Google consent
- First time: auto-creates '{Name}'s Charity' org + org_admin user
- Return time: finds existing user, loads their org
- Demo login still works via credentials provider
OLD FLOW (8+ screens):
signup (4 fields) → auto-login → setup wizard step 1 → step 2 → step 3 → step 4 → dashboard
NEW FLOW (2 screens):
signup (3 fields) → dashboard with inline checklist
- Signup page: just charity name + email + password. No 'your name' field. One button.
- Dashboard: shows getting-started checklist when org has no pledges yet
- /api/onboarding: returns setup progress (bank, event, qr, pledge)
- Checklist: progress bar, next-step highlighting, done states with strikethrough
- Each step links directly to the right page (settings, events, pledges)
- Tip shown for brand new orgs: 'Add bank details first'
- No more separate setup wizard — guidance is inline on the dashboard
- Signup loading state: pulsing emoji while account creates
- Don't auto-poll WAHA on settings page load
- Check connection status once on mount (to show 'Connected' if already paired)
- QR screenshot + polling only starts after clicking 'Connect WhatsApp'
- Polling stops once status changes to CONNECTED
AUTH:
- NextAuth with credentials provider (bcrypt password hashing)
- /api/auth/signup: creates org + user in transaction
- /login, /signup pages with clean minimal UI
- Middleware protects all /dashboard/* routes → redirects to /login
- Session-based org resolution (no more hardcoded 'demo' headers)
- SessionProvider wraps entire app
- Dashboard header shows org name + sign out button
LANDING PAGE:
- Full marketing page at / with hero, problem, how-it-works, features, CTA
- 'Get Started Free' → /signup → auto-login → /dashboard/setup
- Clean responsive design, no auth required for public pages
WAHA QR FIX:
- WAHA CORE doesn't expose QR value via API or webhook
- Now uses /api/screenshot (full browser capture) with CSS crop to QR area
- Settings panel shows cropped screenshot with overflow:hidden
- Auto-polls every 5s, refresh button
MULTI-TENANT:
- getOrgId() tries session first, then header, then first-org fallback
- All dashboard APIs use session-based org
- Signup creates isolated org per charity
- /api/whatsapp/qr: GET returns session status + QR image, POST starts/restarts session
- Settings page: WhatsApp panel shows QR code for pairing, connected status with phone info
- WAHA session started with webhook pointing to /api/whatsapp/webhook
- WAHA_API_URL updated to external https://waha.quikcue.com (cross-stack DNS doesn't work)
- Auto-polls every 5 seconds during QR scan state
- Shows connected state with phone number, push name, feature summary
CORE PRODUCT SHIFT:
A pledge is now a promise to pay on a future date, not just 'pay now'.
NEW FLOW: Amount → Schedule → Payment/Identity → Confirmation
SCHEDULE STEP (/p/[token] step 1):
- 'Pay right now' — existing card/DD/bank flow
- 'Pay on a specific date' — calendar picker with smart suggestions
(This Friday, End of month, Payday 1st, In 2 weeks, In 1 month)
- 'Split into monthly payments' — 2/3/4/6/12 month installment plans
with per-installment breakdown and date schedule
SCHEMA CHANGES:
- Pledge.dueDate — when the donor promises to pay (null = now)
- Pledge.planId — groups installment pledges together
- Pledge.installmentNumber / installmentTotal — e.g. 2 of 4
- Pledge.reminderSentForDueDate — tracking flag
- New indexes on dueDate+status and planId
INSTALLMENT PLANS:
- Creates N linked Pledge records with shared planId
- Each installment gets its own reference, due date, reminders
- Reminders: 2 days before, on due date, 3 days after, 10 days after
- WhatsApp receipt shows full plan summary
DEFERRED SINGLE PLEDGES:
- Reminders anchored to due date, not creation date
- 'Pay on date' → reminders: 2 days before, on day, +3d nudge, +10d final
- WhatsApp preferred when phone number provided
DASHBOARD:
- API returns dueDate, planId, installment info for each pledge
- Confirmation step shows schedule details for deferred pledges