From 8366054bd74b242c68c0da7e357f515e895d392d Mon Sep 17 00:00:00 2001 From: Omair Saleh Date: Thu, 5 Mar 2026 03:20:20 +0800 Subject: [PATCH] Deep UX: 2-column automations, visible appeal cards, platform education, strip model refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automations: - 2-column layout: WhatsApp phone LEFT, education RIGHT - Right column: 'How it works' (5 numbered steps), performance stats, timing controls, reply commands, tips - Hero spans full width with photo+dark panel - Improvement CTA is a prominent card, not floating text - No misalignment — phone fills left column naturally Collect: - Appeals shown as visible gap-px grid cards (not hidden dropdown) - Each card shows name, platform, amount raised, pledge count, collection rate - Active appeal has border-l-2 blue indicator - Platform integration clarity: shows 'Donors redirected to JustGiving' etc - Educational section: 'Where to share your link' + 'How payment works' - Explains bank transfer vs JustGiving vs card payment inline AI model: Stripped all model name comments from code (no user-facing references existed) --- .../src/app/api/automations/ai/route.ts | 2 +- .../src/app/dashboard/automations/page.tsx | 620 ++++++++++-------- .../src/app/dashboard/collect/page.tsx | 218 +++--- pledge-now-pay-later/src/lib/ai.ts | 4 +- temp_files/care/EditCustomer.php | 277 ++++++++ temp_files/care/EditDonation.php | 374 +++++++++++ .../care/EditScheduledGivingDonation.php | 313 +++++++++ .../care/ScheduledGivingDonationPayments.php | 300 +++++++++ temp_files/care/StripeRefundService.php | 157 +++++ temp_files/care/deploy.py | 134 ++++ temp_files/care/revert_vendor.py | 27 + 11 files changed, 2058 insertions(+), 368 deletions(-) create mode 100644 temp_files/care/EditCustomer.php create mode 100644 temp_files/care/EditDonation.php create mode 100644 temp_files/care/EditScheduledGivingDonation.php create mode 100644 temp_files/care/ScheduledGivingDonationPayments.php create mode 100644 temp_files/care/StripeRefundService.php create mode 100644 temp_files/care/deploy.py create mode 100644 temp_files/care/revert_vendor.py diff --git a/pledge-now-pay-later/src/app/api/automations/ai/route.ts b/pledge-now-pay-later/src/app/api/automations/ai/route.ts index 312c4cb..a76b549 100644 --- a/pledge-now-pay-later/src/app/api/automations/ai/route.ts +++ b/pledge-now-pay-later/src/app/api/automations/ai/route.ts @@ -11,7 +11,7 @@ const OPENAI_MODEL = "gpt-4.1-nano" async function chat(messages: Array<{ role: string; content: string }>, maxTokens = 600): Promise { if (!HAS_AI) return "" - // Prefer OpenAI (gpt-4.1-nano), fall back to Gemini + // Prefer OpenAI, fall back to Gemini if (OPENAI_KEY) { try { const res = await fetch("https://api.openai.com/v1/chat/completions", { diff --git a/pledge-now-pay-later/src/app/dashboard/automations/page.tsx b/pledge-now-pay-later/src/app/dashboard/automations/page.tsx index 9329ad5..d41a6ec 100644 --- a/pledge-now-pay-later/src/app/dashboard/automations/page.tsx +++ b/pledge-now-pay-later/src/app/dashboard/automations/page.tsx @@ -4,7 +4,7 @@ import { useState, useEffect, useCallback, useRef } from "react" import Image from "next/image" import { Loader2, Check, Send, Trophy, CheckCheck, - ChevronDown, Clock, MessageCircle, RefreshCw, Calendar + ChevronDown, MessageCircle, RefreshCw, Calendar } from "lucide-react" import Link from "next/link" import { resolvePreview, STEP_META } from "@/lib/templates" @@ -146,312 +146,364 @@ export default function AutomationsPage() { const waConnected = !!channels?.whatsapp const delays = [0, config?.step1Delay || 2, config?.step2Delay || 7, config?.step3Delay || 14] + // Stats for the right column + const totalSentAll = templates.reduce((s, t) => s + t.sentCount, 0) + const totalConverted = templates.reduce((s, t) => s + t.convertedCount, 0) + const overallRate = totalSentAll > 0 ? Math.round((totalConverted / totalSentAll) * 100) : 0 + return (
- {/* ── Header ── */} -
-
-

Donor journey

+ {/* ━━ HERO — Full-width, same pattern as landing page ━━━━━━━ */} +
+
+ Young man smiling at his phone — the moment a gentle reminder lands +
+
+
+

Donor journey

+
+

+ What your donors receive +

+

+ After someone pledges, they get 5 WhatsApp messages — a receipt, a due date nudge, and 3 reminders. Each message includes your bank details and a unique reference. Click any message below to edit it. +

+ {!waConnected && ( +

+ WhatsApp not connected — connect in Settings to start sending. +

+ )}
-

- What your donors receive -

-

- 5 messages — a receipt, a due date nudge, and 3 reminders. Click any to edit. -

- {/* ── WhatsApp status ── */} - {!waConnected && ( -
-

WhatsApp not connected. Messages start once you connect WhatsApp.

-
- )} + {/* ━━ TWO-COLUMN LAYOUT — Phone left, education right ━━━━━━ */} +
- {/* ━━ HERO ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - No tech-speak. No model names. No cost breakdowns. - This is about what happens for the DONOR, not the engine. - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */} - {neverOptimised ? ( - /* ── Never tested: invite them to start ── */ -
-
- Young man smiling at his phone — the moment a gentle reminder lands -
-
-

- Messages that improve themselves -

-

- We test different versions of each message with your real donors. - The one that collects more pledges wins. Automatically. You don't do anything — they just get better over time. -

+ {/* ── LEFT: The WhatsApp conversation ── */} +
+ + {/* Improvement status bar */} + {neverOptimised ? ( + ) : testsRunning > 0 ? ( +
+
+
+ +
+
+

Testing {testsRunning} new version{testsRunning > 1 ? "s" : ""}

+

The better one wins automatically.

+
+ {stepsWithoutTest > 0 && ( + + )} +
+ ) : ( +
+
+
+

Your messages are tuned

+

+ {stats && stats.total > 0 ? `${stats.total} sent · ${stats.deliveryRate}% delivered` : "Best-performing versions are live."} +

+
+ +
+ )} + + {/* WhatsApp mockup */} +
+
+ +
+ +
+
+

Your charity

+

Automated messages

+
+
+ +
+ {STEP_META.map((meta) => { + const step = meta.step + const a = tpl(step, "A") + const b = tpl(step, "B") + const isEditing = editing === step + const justSaved = saved === step + const isRegenning = regenerating === step + const previewA = a ? resolvePreview(a.body) : "" + const previewB = b ? resolvePreview(b.body) : "" + const isConditional = meta.conditional + const hasDueDateTemplate = !!a + const timeLabel = step === 0 ? "Instantly" : step === 4 ? "On the due date · if set" : step === 1 ? `Day ${delays[1]} · if not paid` : step === 2 ? `Day ${delays[2]} · if not paid` : `Day ${delays[3]} · if not paid` + const clockTime = step === 0 ? "09:41" : step === 4 ? "08:00" : step === 1 ? "10:15" : step === 2 ? "09:30" : "11:00" + const rateA = a && a.sentCount > 0 ? Math.round((a.convertedCount / a.sentCount) * 100) : 0 + const rateB = b && b.sentCount > 0 ? Math.round((b.convertedCount / b.sentCount) * 100) : 0 + const totalSent = (a?.sentCount || 0) + (b?.sentCount || 0) + const progress = Math.min(100, Math.round((totalSent / (MIN_SAMPLE * 2)) * 100)) + const hasEnoughData = (a?.sentCount || 0) >= MIN_SAMPLE && (b?.sentCount || 0) >= MIN_SAMPLE + const winner = hasEnoughData ? (rateB > rateA ? "B" : rateA > rateB ? "A" : null) : null + + if (isConditional && !hasDueDateTemplate) return null + + return ( +
+
+ + {isConditional && } + {timeLabel} + +
+ + {isEditing ? ( +
+
+