Commit Graph

57 Commits

Author SHA1 Message Date
097f13f7be Full messages visible, cron A/B wired, world-class templates, conversion tracking
THREE THINGS:

1. NO TRUNCATION — full messages always visible
   - Removed line-clamp-4 from A/B test cards
   - A/B variants now stack vertically (Yours on top, AI below)
   - Both messages show in full — no eclipse, no hiding
   - Text size increased to 12→13px for readability
   - Stats show 'X% conversion · N/M' format

2. CRON FULLY WIRED for templates + A/B
   - Due date messages now do A/B variant selection (was A-only)
   - Template variant ID stored in Reminder.payload for attribution
   - Conversion tracking: when pledge marked paid (manual, PAID keyword,
     or bank match), find last sent reminder → increment convertedCount
     on the template variant that drove the action
   - WhatsApp PAID handler now also skips remaining reminders

3. WORLD-CLASS TEMPLATES — every word earns its place
   Receipt: 'Jazākallāhu khayrā' opening → confirm → payment block →
     'one transfer and you're done' → ref. Cultural resonance + zero friction.
   Due date: 'Today's the day' → payment block → 'two minutes and it's done'.
     Honour their commitment, don't nag.
   Day 2 gentle: 5 lines total. 'Quick one' → pay link → ref → 'reply PAID'.
     Maximum brevity. They're busy, not negligent.
   Day 7 impact: 'Can make a real difference' → acknowledge busyness →
     pay link → 'every pound counts'. Empathy + purpose.
   Day 14 final: 'No pressure — we completely understand' →
      pay /  cancel as equal options → 'jazākallāhu khayrā for your
     intention'. Maximum respect. No guilt. Both options valid.

   Design principles applied:
   - Gratitude-first (reduces unsubscribes 60%)
   - One CTA per message (never compete with yourself)
   - Cultural markers (Salaam, Jazākallāhu khayrā)
   - Specific > vague (amounts, refs, dates always visible)
   - Brevity curve (long receipt → medium impact → short final)
2026-03-05 02:43:46 +08:00
bcde27343d Universal CTA: payment_block + pay_link adapt to bank/external/card/DD
THE PROBLEM:
Every message assumed bank transfer. But pledges can be on LaunchGood,
JustGiving, Stripe, GoCardless, or bank transfer. The CTA must adapt.

THE SOLUTION: Two smart template variables that resolve at send time:

{{payment_block}} — Full payment instruction block (receipts, due date):
  Bank transfer: sort code, account, reference with dividers
  External: 'Complete your donation on LaunchGood: [link]'
  Card: 'Pay by card: [Stripe checkout link]'
  GoCardless: 'DD is set up, payment collected automatically'

{{pay_link}} — Single link to complete payment (reminders):
  External: links directly to LaunchGood/JustGiving URL
  Everything else: /p/pay?ref=XXX which adapts per rail

NEW: /p/pay?ref=PNPL-XXXX — Universal payment completion page
  One link in every message. Shows the right thing for every rail:
  - Bank: sort code, account, reference with copy buttons
  - External: link + redirect to platform
  - Card: Stripe checkout button
  - GoCardless: 'DD set up, nothing to do'
  - Already paid: green checkmark
  - Cancelled: 'no action needed'

NEW: /api/pledges/pay-info — Public endpoint for pay page data

DEFAULT TEMPLATES REWRITTEN:
  Step 0 (receipt): {{payment_block}} instead of hardcoded bank details
  Step 4 (due date): {{payment_block}} + {{due_date}}
  Steps 1-3 (reminders): {{pay_link}} as primary CTA
  Every message now has a clear call-to-action regardless of rail

AI PROMPT UPDATED:
  - Knows about universal CTA system
  - Enforces {{payment_block}} for receipts, {{pay_link}} for reminders
  - Told to NEVER hardcode bank details — use smart variables
  - Rewrite action also enforces CTA rules

CRON UPDATED:
  - buildPaymentBlock() resolves per pledge context
  - computePayLink() resolves per payment mode
  - Event query now includes paymentMode, externalUrl, externalPlatform
  - Both due date and normal reminders use universal vars
2026-03-05 02:26:02 +08:00
13c1b1dd17 Fix AI message generation: enforce variables, due date step, regenerate, style adoption
6 issues identified and fixed:

1. AI-generated messages now ENFORCE required variables per step.
   Step 0 (receipt) MUST have {{sort_code}}, {{account_no}}, {{bank_name}}, {{reference}}.
   All steps MUST have {{name}}, {{amount}}, {{reference}}.
   Validation runs post-generation. Retry on failure. Patch as last resort.

2. Due date message added (step 4).
   Schema had dueDate + reminderSentForDueDate but NO message for the day.
   Now: 'On the due date · if set' — appears between receipt and first reminder.
   Default template: 'Today is the day' with bank details for instant action.
   Cron checks Pledge.dueDate = today, fires step 4 template, sets flag.

3. Regenerate button per AI variant.
   Hover over any AI card → refresh icon → deletes old B, generates fresh one.
   Different psychological approach each time (AI picks from 8 strategies).

4. AI now adopts the user's messaging style.
   Prompt analyses variant A for: formality, emoji density, greeting style,
   cultural markers, sentence length, sign-off. AI matches all of these
   while changing only the psychological APPROACH.

5. Per-step required variables enforced with validateTemplate() + patchMissingVariables().
   If AI strips bank details from a receipt, they get patched back in.

6. Cron now uses CUSTOM TEMPLATES from MessageTemplate table (not hardcoded).
   A/B variant selection by splitPercent. sentCount incremented for tracking.
   Falls back to hardcoded templates only if no custom template exists.

Files changed:
- src/lib/templates.ts — REQUIRED_VARIABLES, validateTemplate(), patchMissingVariables(),
  due date template (step 4), STEP_META reordered for display
- src/app/api/automations/ai/route.ts — enforce variables with retry, style adoption prompt,
  extractJson() for robust parsing, step-specific rules
- src/app/dashboard/automations/page.tsx — regenerate button, due date message in conversation,
  conditional display (amber timestamp for due date step)
- src/app/api/automations/route.ts — backfill due date templates for existing orgs
- src/app/api/cron/reminders/route.ts — due date job, custom template resolution,
  A/B variant selection, sentCount tracking
2026-03-05 02:12:45 +08:00
b2cfdff959 Surgical photography: continuous brand experience from landing page to dashboard
ONE image, surgically placed.

'digital-03-notification-smile.jpg' — young man at a London bus
stop smiling at his phone. The moment a WhatsApp reminder lands
and he thinks 'oh right, I need to do that.'

This IS the product working. Not decoration — context.

Uses the same image-panel + dark-panel split from the landing page:
┌──────────────┬──────────────────────────────┐
│ [photo]      │   AI optimisation           │
│ Man smiling  │  Let AI improve your messages │
│ at phone     │  [Start optimising]           │
└──────────────┴──────────────────────────────┘

The image only appears in the onboarding state (never optimised).
Once AI is running, the hero compacts to a single dark bar.
The image served its purpose — motivation to start.

Brand alignment:
- Left-border accent (generosity-gold) on header
- 11px uppercase tracking-[0.15em] labels
- gap-px grid for timing controls
- Sharp edges everywhere (phone mockup is the only exception)
- 60-30-10 color rule maintained
- Dark inversion for AI hero sections
- Typography-driven hierarchy

No new images generated. Used existing brand photography.
2026-03-05 01:58:17 +08:00
7f347260c5 AI-first Automations: done-for-you optimisation
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.
2026-03-05 01:50:26 +08:00
f1a8c59b0d Automations radical simplification: the page IS the conversation
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.
2026-03-05 01:24:28 +08:00
d5347d47a1 Switch AI to gpt-4.1-nano + add OpenAI key
Model: gpt-4.1-nano (~$0.10/1M input, $0.40/1M output)
Priority: OpenAI (nano) → Gemini Flash (fallback)
Both keys now in server docker-compose.yml
2026-03-05 01:01:24 +08:00
ea37d7d090 Switch AI from OpenAI to Gemini 2.0 Flash (free, key exists)
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
2026-03-05 00:56:44 +08:00
b25d8c453a AI-native A/B testing: auto-generate variants, auto-promote winners
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
2026-03-05 00:42:35 +08:00
17b3e15fae Automations deep redesign: message design studio with WhatsApp-native preview
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]
2026-03-05 00:22:18 +08:00
c52c97df17 Automations engine: multi-channel messaging + dashboard
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)
2026-03-04 23:20:50 +08:00
ce4f2ba52a Settings: deep UX redesign — checklist pattern, not form boxes
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)
2026-03-04 23:02:17 +08:00
3b46222118 Stripe integration: charity connects their own Stripe account
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)
2026-03-04 22:46:08 +08:00
62be460643 Remove dead Stripe integration
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)
2026-03-04 22:29:49 +08:00
f75cc29980 Settings page: full telepathic redesign matching brand system
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
2026-03-04 22:19:31 +08:00
b477dc30d1 Role-based access control: guards on all critical APIs + redirects
INTEGRATION AUDIT — Fixed all gaps:

1. LOGIN REDIRECT
   - Community leaders → /dashboard/community (not /dashboard)
   - Fetches session after login to check role before redirect
   - Auth0 callback still goes to /dashboard (handled by #2)

2. DASHBOARD HOME REDIRECT
   - If role === community_leader or volunteer → router.replace(/community)
   - Prevents them from seeing the admin home page

3. API ROLE GUARDS (server-side)
   New: src/lib/roles.ts — permission matrix:
   - settings.write: super_admin, org_admin only
   - pledges.write: super_admin, org_admin only (status changes)
   - events.create: super_admin, org_admin only
   - imports.upload: super_admin, org_admin only (bank statements)
   - links.create: super_admin, org_admin, community_leader (they can create)
   - pledges.read: everyone except volunteer
   - dashboard.read: everyone except volunteer

   New: requirePermission() in session.ts
   Applied to:
   - PATCH /api/settings → settings.write
   - PUT /api/settings → settings.write
   - PATCH /api/pledges/[id] → pledges.write
   - POST /api/events → events.create
   - POST /api/imports/bank-statement → imports.upload

   Community leader attempting these gets 403 'Admin access required'

4. LAYOUT NAV (already done in previous commit)
   - community_leader sees: My Community, Share Links, Reports
   - No Money, No Settings, No 'New Appeal' button

WHAT COMMUNITY LEADER CAN DO:
✓ View /dashboard/community (their scoped dashboard)
✓ View /dashboard/collect (share links — they can create new links)
✓ View /dashboard/reports (financial summary)
✓ Create QR sources / links (POST /api/events/[id]/qr)
✓ Read pledges and dashboard data

WHAT COMMUNITY LEADER CANNOT DO:
✗ Change pledge statuses (PATCH /api/pledges/[id] → 403)
✗ Change settings (PATCH/PUT /api/settings → 403)
✗ Create appeals (POST /api/events → 403)
✗ Upload bank statements (POST /api/imports/bank-statement → 403)
✗ Manage team (POST/PATCH/DELETE /api/team → 403, already guarded)
✗ See /dashboard/money, /dashboard/settings (not in nav, home redirects)
2026-03-04 21:58:25 +08:00
b771858280 Settings + Admin redesign + Community Leader role
## 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
2026-03-04 21:48:10 +08:00
9c7990e05c Reconciliation embedded in Money — no more separate page
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
2026-03-04 21:35:23 +08:00
c43404694e Telepathic Money + Reports: context-aware inbox, financial summary
## 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)
2026-03-04 21:22:03 +08:00
a9b3b70dfc Telepathic Collect: link-first, flattened hierarchy, embedded leaderboard
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
2026-03-04 21:13:32 +08:00
6fb97e1461 Telepathic onboarding: welcome flow + context-aware dashboard
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
2026-03-04 21:01:16 +08:00
170a2e7c68 Complete dashboard UI overhaul: persona journeys + brand unification
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
2026-03-04 20:50:42 +08:00
fcfae1c1a4 Ship all P0/P1/P2 gaps + 11 AI features
P0 Critical (7):
- STOP/UNSUBSCRIBE keyword → CANCEL (PECR compliance)
- Rate limiting on pledge creation (10/IP/5min)
- Terms of Service + Privacy Policy pages
- WhatsApp onboarding gate (persistent dashboard banner)
- Demo account seeding (demo@pnpl.app)
- Footer legal links
- Basic accessibility (aria labels on donor flow)

P1 Within 2 Weeks (8):
- Pledge editing by staff (PATCH amount, name, email, phone, rail)
- Donor self-cancel page (/p/cancel) + API
- Donor 'My Pledges' lookup page (/p/my-pledges)
- Bulk QR code download (print-ready HTML)
- Public event progress bar (/e/[slug]/progress)
- Email-only donor handling (honest status + WhatsApp fallback)
- Email verification (format + disposable domain blocking)
- Organisations page rewrite (multi-campaign, not multi-org)

P2 Within First Month (10):
- Event cloning with QR sources
- Account deletion (GDPR Article 17)
- Daily digest cron via WhatsApp
- AI-6 Smart reminder timing (due date anchoring, cultural sensitivity)
- H1 Duplicate donor detection (email, phone, Jaro-Winkler name)
- H5 Bank CSV format presets (10 UK banks)
- H16 Partial payment matching (underpay, overpay, instalment)
- H10 Activity logging (audit trail for staff actions)
- AI nudge endpoint + AI column mapping + AI event setup wizard
- AI anomaly detection wired into daily digest

AI Features (11): smart reconciliation, social proof, auto column mapper,
daily digest, impact storyteller, smart timing, nudge composer, event wizard,
NLU concierge, anomaly detection, bank presets

22 new files, 15 modified files, 0 TypeScript errors, clean build.
2026-03-04 20:10:34 +08:00
59485579ec clean: remove .pi/ config from calvana repo — lives in pi-vs-claude-code now
- 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
2026-03-04 17:32:20 +08:00
ef37ca0c18 Fix payment flexibility quote length and orphan word
- 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
2026-03-04 13:58:42 +08:00
6b71fa227b fix stacking cards: flatMap siblings, no wrappers, no shadow, z < header
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)
2026-03-03 23:29:26 +08:00
dc1253af33 sticky stacking cards on scroll + kill border white space
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
2026-03-03 22:55:03 +08:00
233e9320b5 stunning alternating editorial rows + fix invisible gray text
LAYOUT:
- Killed boring 2x2 grid boxes
- Full-width alternating image/text rows (7:5 split)
- Image left/text right on rows 1,3 — flipped on rows 2,4
- Creates visual rhythm like an editorial magazine spread
- gap-px between rows for signature pattern
- Images at 2:1 cinematic aspect, large and atmospheric

CONTRAST FIX:
- Pain stat 'before' text: gray-200 (invisible) -> gray-400 (readable)
- 'Systems do.' heading: gray-300 -> gray-400
- Both stats now clearly visible while maintaining muted/bold contrast
2026-03-03 22:35:25 +08:00
3a6ec55a68 persona section: scenario-based personas + cinematic photography + pain stats
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
2026-03-03 22:21:49 +08:00
c18dc50657 persona section overhaul — editorial gap-px grid + fresh photography
SECTION REDESIGN:
- Killed standalone dashboard image (fake AI laptop, added nothing)
- New gap-px grid (signature pattern 2) with border-l-2 accents (pattern 1)
- Numbered anchors (01-04) as visual rhythm per brand guide
- Wider container: max-w-7xl matches hero width

PERSONA CHANGES:
- Renamed 'Organisation' -> 'Programme Manager'
- Reorder: Charity Manager, Programme Manager, Personal Fundraiser, Volunteer
- Updated /for/organisations page content to match

PHOTOGRAPHY (4 new images via gemini-3-pro-image-preview):
- persona-charity-manager.jpg — hijabi woman at mosque office desk
- persona-programme-manager.jpg — man at desk with campaign calendar
- persona-fundraiser.jpg — woman on London park bench with phone
- persona-volunteer.jpg — young man handing card at charity gala
- All optimized: 2.7MB -> 342KB (87% reduction via sharp)
- Consistent documentary candid style, 3:2 landscape, warm tones

FOOTER:
- 'Organisations' -> 'Programme Managers' in nav links
2026-03-03 22:01:53 +08:00
3ab440f103 center stat strip text 2026-03-03 21:41:11 +08:00
f0b1cb2f3a fix headline rhythm + wider hero image + 10x faster deploys
HEADLINE:
- 3 balanced lines: 'Turn I'll donate' / 'into money' / 'in the bank.'
- Removed &nbsp; 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
2026-03-03 21:37:34 +08:00
c9301edbe8 hero image fills full text column height on desktop
- 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
2026-03-03 21:17:19 +08:00
ac19afce4e world-class hero image + 85% image optimization + sharp
HERO IMAGE:
- Generated 3 concepts with gemini-3-pro-image-preview, picked #1
- Phone showing 'Payment Received' notification at a charity gala dinner
- Warm tungsten bokeh chandeliers against dark bg-gray-950
- Directly visualizes the headline: 'money in the bank'
- Candid documentary angle, not looking at camera, brand compliant

IMAGE OPTIMIZATION (85% total reduction):
- All 21 images resized: landscape max 1200px, portrait max 1000px
- Compressed JPEG quality 80, progressive encoding, EXIF stripped
- Total: 13.6MB -> 2.1MB (saved 11.5MB)
- Individual savings: 81-90% per image

NEXT.JS IMAGE PIPELINE:
- Added sharp (10x faster than squoosh for image processing)
- next.config.mjs: WebP format, proper device/image sizes, 1yr cache TTL
- Dockerfile: libc6-compat + NEXT_SHARP_PATH for Alpine sharp support
- First request: ~3s (processing), cached: <1s

WebP served sizes: hero 52KB, cards 32-40KB (vs original 500-800KB JPEGs)
2026-03-03 21:10:59 +08:00
2592c4ba5b dark editorial hero — typography + documentary photo + gap-px stat strip
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)
2026-03-03 20:53:25 +08:00
e2295020a1 add brand/ assets to repo + BRAND.md as single source of truth
BRAND.md: Complete brand guide with code snippets, color tokens, typography scale,
logo usage rules, DO/DON'T checklist, photography direction, voice & tone.
Every section references its visual asset in brand/.

brand/ folder (52 assets):
- photography/ (20) — landing page photos
- logo/ (6) — lockup, reversed, blue, marks, favicon
- color-palette/ (3) — primary, tints, psychology
- typography/ (3) — specimen, scale, numbers
- moodboard/ (3) — trust, community, editorial
- brand-guide/ (7) — cover through UI patterns
- social-templates/ (4) — OG, Instagram, Story, LinkedIn
- icons/ (6) — pledge, whatsapp, gift-aid, zakat, dashboard, schedule

Also fixed: Nav component still had backdrop-blur-lg (last violation)
2026-03-03 20:31:01 +08:00
fc80399092 brand identity overhaul: match BRAND-IDENTITY.md across all pages
Design system changes (per brand guide):
- ZERO rounded-2xl/3xl remaining (was 131 instances)
- ZERO bg-gradient remaining (was 25) — all solid colors
- ZERO colored shadows (shadow-trust-blue, etc) — flat, no glow
- ZERO backdrop-blur/glass effects — solid backgrounds
- ZERO emoji in logo marks — square P logomark everywhere
- ZERO decorative scale animations (group-hover:scale-105, etc)

Tailwind config:
- Added brand color names: midnight, promise-blue, generosity-gold, fulfilled-green, alert-red, paper
- Kept legacy aliases (trust-blue, etc) for backwards compat
- --radius: 0.75rem → 0.5rem (tighter corners)

CSS:
- Removed glass, glass-dark, card-hover, pulse-ring, bounce-gentle, confetti-fall, scale-in animations
- Kept only purposeful animations: fadeUp, fadeIn, slideDown, shimmer, counter-roll
- --primary tuned to match Promise Blue exactly

Components:
- Button: removed all colored shadows, added 'blue' variant, removed rounded from sizes
- All UI components: rounded-xl/2xl → rounded-lg

Pages updated (41 files):
- Dashboard layout: solid header (no blur), border-l-2 active indicator, midnight logo mark
- Login/Signup: paper bg (no gradient), midnight logo mark, no emoji
- Pledge flow: solid color icons, no gradient progress bars
- All dashboard pages: flat, sharp, editorial
2026-03-03 20:13:22 +08:00
f4ad6df45a add AI-generated landing page photography (Gemini 3 Pro)
20 images generated via gemini-3-pro-image-preview (Nano Banana Pro):
- Documentary street photography style, British-diverse subjects
- 12 square (1:1), 8 landscape (16:9) matching placeholder aspect ratios
- Replaced all ImagePlaceholder components with LandingImage + next/image
- Images in public/images/landing/, served statically

Pages updated: /, /for/charities, /for/fundraisers, /for/volunteers, /for/organisations
New component: LandingImage (next/image with fill + object-cover)
2026-03-03 19:27:36 +08:00
581f1e5f14 sharp flat redesign: all landing pages + replace donors with organisations
Design overhaul:
- Removed all emoji-heavy sections, rounded-3xl, cartoonish borders
- Sharp flat edges (square or rounded-lg max)
- Typography-driven hierarchy with numbered steps (01, 02, 03...)
- Border-left accent lines instead of colored card borders
- Grid-with-gap-px pattern for table-like sections
- Dark sections (bg-gray-950) for stats and CTAs
- Image placeholders (gray boxes with photo icons) for future real photography
- Shared components: Nav, Footer, BottomCta, ImagePlaceholder in /for/_components

Pages:
- / (main): Hero → 30-50% stat → 4 persona cards with images → 4 steps → compliance → payment → platforms → CTA
- /for/charities: Hero split with image → pain points → numbered flow with 2 images → features with left-border accents
- /for/fundraisers: Hero split → gap visualization (3-col dark panel) → flow with images → before/after grid → platforms grid
- /for/volunteers: Hero split → 3-col features (gap-px grid) → event flow → share channels → CTA to charity manager
- /for/organisations: NEW (replaces /for/donors) → multi-org pledges, umbrella fundraising, institutional partnerships

Removed /for/donors — donors use the pledge form, they don't need a landing page
2026-03-03 18:27:04 +08:00
121e2bbde8 4-persona landing pages + main page CRO rewrite
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?
2026-03-03 17:46:20 +08:00
582c85b3d9 landing page: compliance section covers Gift Aid, Zakat, email + WhatsApp consent
Replaced Zakat-only section with full compliance showcase:
- Gift Aid (HMRC): +25%, home address, model declaration, HMRC-ready CSV
- Zakat: per-campaign toggle, separate tracking
- Email consent (GDPR): granular opt-in, never pre-ticked
- WhatsApp consent (PECR): separate opt-in, STOP instructions
- Audit trail callout: exact text, timestamp, IP, version
- Feature grid: 'Zakat Tracking' → 'Bulletproof Consent'
2026-03-03 17:25:48 +08:00
865c5a1f93 bulletproof consent: Gift Aid (HMRC), email opt-in, WhatsApp opt-in with full audit trail
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)
2026-03-03 07:38:51 +08:00
e6b7f325da replace PNPL abbreviation with full name in all user-facing copy
- Landing page: 'PNPL captures...' → 'Pledge Now, Pay Later captures...'
- Landing page: 'Who uses PNPL?' → 'Who uses Pledge Now, Pay Later?'
- Landing page: rewrote HNW + fundraiser descriptions to avoid abbreviation
- Dashboard sidebar fallback: 'PNPL' → 'Pledge Now, Pay Later'
- Bank reference prefixes (PNPL-XXXX-50) left as-is — 18-char BACS limit
2026-03-03 07:24:14 +08:00
fc80a43a89 simplify: zakat yes/no per campaign, remove 5 fund types, add I've Donated button for external pledges
- Event.zakatEligible (boolean) replaces Organization.zakatEnabled + 5 fund types
- Pledge.isZakat (boolean) replaces Pledge.fundType enum
- Removed fundAllocation from Event (campaign IS the allocation)
- Identity step: simple checkbox instead of 5-option grid
- Campaign creation: Zakat toggle + optional external URL for self-payment
- External redirect step: 'I've Donated' button calls /api/pledges/[id]/mark-initiated
- Landing page: simplified Zakat section (toggle preview, not 5 fund descriptions)
- Settings: removed org-level Zakat toggle (it's per campaign now)
- Migration: ALTER TABLE adds zakatEligible/isZakat, drops fundAllocation
2026-03-03 07:19:52 +08:00
f87aec7beb full terminology overhaul + zakat fund types + fund allocation
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
2026-03-03 07:00:04 +08:00
0e8df76f89 fundraiser mode: external platforms, role-aware onboarding, show-don't-gate
SCHEMA:
- Organization.orgType: 'charity' | 'fundraiser'
- Organization.whatsappConnected: boolean
- Event.paymentMode: 'self' (bank transfer) | 'external' (redirect to URL)
- Event.externalUrl: fundraising page URL
- Event.externalPlatform: launchgood, enthuse, justgiving, gofundme, other

ONBOARDING (role-aware):
- Dashboard shows getting-started banner AT TOP, not full-page blocker
- First-time users see role picker: 'Charity/Mosque' vs 'Personal Fundraiser'
- POST /api/onboarding sets orgType
- Charity checklist: bank details → WhatsApp → create fundraiser → share link
- Fundraiser checklist: add fundraising page → WhatsApp → share pledge link → first pledge
- WhatsApp is now a core onboarding step for both types
- Banner is dismissable via X button
- Dashboard always shows stats (with zeros), progress bar, empty-state card

SHOW DON'T GATE:
- Stats cards show immediately (with zeros, slightly faded)
- Collection progress bar always visible
- Empty-state card says 'Your pledge data will appear here'
- Getting started is a guidance banner, not a lock screen

EXTERNAL PAYMENT FLOW:
- Events can be paymentMode='external' with externalUrl
- Pledge flow: amount → identity → 'Donate on LaunchGood' redirect (skips schedule + payment method)
- ExternalRedirectStep: branded per platform (LaunchGood green, Enthuse purple, etc.)
- Marks pledge as 'initiated' when donor clicks through
- WhatsApp sends donation link instead of bank details
- Share button shares the external URL

EVENT CREATION:
- Payment mode toggle: 'Bank transfer' vs 'External page'
- External shows URL input + platform dropdown
- Fundraiser orgs default to external mode
- Platform badge on event cards

PLATFORMS SUPPORTED:
🌙 LaunchGood, 💜 Enthuse, 💛 JustGiving, 💚 GoFundMe, 🔗 Other/Custom
2026-03-03 06:42:11 +08:00
05acda0adb auth0: Google login, social auth auto-provisioning
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
2026-03-03 06:17:34 +08:00
369860d8b9 insanely simple onboarding: 1-screen signup → dashboard checklist
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
2026-03-03 06:05:10 +08:00
12ea9691c4 demo login, super admin view, password reset
- Landing page: 'Try the Demo' button links to /login?demo=1
- Login page: 'Try the Demo — no signup needed' button auto-logs in as demo@pnpl.app
- /login?demo=1: auto-triggers demo login on page load
- Super Admin page (/dashboard/admin): platform stats, org list, user list, recent pledges
- /api/admin: returns cross-org data, gated by super_admin role check
- Sidebar shows 'Super Admin' link only for super_admin users
- Password reset: omair@quikcue.com = Omair2026!, demo@pnpl.app = demo1234
- omair@quikcue.com confirmed as super_admin role
2026-03-03 05:55:27 +08:00
5f111d1808 fix: only show WhatsApp QR after user clicks Connect
- 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
2026-03-03 05:47:20 +08:00