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
This commit is contained in:
2026-03-04 22:19:31 +08:00
parent b477dc30d1
commit f75cc29980

View File

@@ -2,25 +2,27 @@
import { useState, useEffect, useCallback } from "react" import { useState, useEffect, useCallback } from "react"
import { useSession } from "next-auth/react" import { useSession } from "next-auth/react"
import { Input } from "@/components/ui/input"
import { import {
Check, Loader2, AlertCircle, MessageCircle, Radio, RefreshCw, Check, Loader2, AlertCircle, MessageCircle, Radio, RefreshCw,
Smartphone, Wifi, WifiOff, QrCode, UserPlus, Trash2, Copy, Smartphone, Wifi, WifiOff, QrCode, UserPlus, Trash2, Copy,
Users, Crown, Eye Users, Crown, Eye, Building2, CreditCard, Palette, ChevronRight
} from "lucide-react" } from "lucide-react"
/** /**
* /dashboard/settings — Aaisha's control panel * /dashboard/settings — Aaisha's control panel
* *
* Organised by what she's thinking, not by system concept: * Telepathic approach: What is she thinking each time she visits?
* 1. WhatsApp — "I need to connect" (or see it's connected)
* 2. Team — "Who has access? I need to invite Imam Yusuf"
* 3. Bank — "Where donors send money"
* 4. Your charity — name, brand colour
* 5. Direct Debit — advanced, for later
* *
* Team management is NEW — the missing feature. * First visit: "They told me to connect WhatsApp. Where?"
* This is how community leaders get invited. * Second visit: "Is everything working? What else do I need to set up?"
* Monthly: "I need to invite Imam Yusuf" / "Let me check bank details"
* Rarely: "What name shows to donors?" / "Direct Debit?"
*
* The page opens with a READINESS BAR — dark section showing
* what's configured vs what's missing. Aaisha sees instantly:
* "WhatsApp ✓ · Bank details ✗ · Team: 1 member"
*
* Then sections in order of how often she needs them.
*/ */
interface OrgSettings { interface OrgSettings {
@@ -33,11 +35,11 @@ interface TeamMember {
id: string; email: string; name: string | null; role: string; createdAt: string id: string; email: string; name: string | null; role: string; createdAt: string
} }
const ROLE_LABELS: Record<string, { label: string; desc: string; icon: typeof Crown }> = { const ROLE_META: Record<string, { label: string; desc: string; icon: typeof Crown; color: string; bg: string }> = {
org_admin: { label: "Admin", desc: "Full access to everything", icon: Crown }, org_admin: { label: "Admin", desc: "Full access — settings, money, everything", icon: Crown, color: "text-[#1E40AF]", bg: "bg-[#1E40AF]/10" },
community_leader: { label: "Community Leader", desc: "Can see their links, pledges, and share. Can't change settings.", icon: Users }, community_leader: { label: "Community Leader", desc: "Their own links and pledges. Can't change settings.", icon: Users, color: "text-[#F59E0B]", bg: "bg-[#F59E0B]/10" },
staff: { label: "Staff", desc: "Can view pledges and reports", icon: Eye }, staff: { label: "Staff", desc: "Can view pledges and reports, read-only", icon: Eye, color: "text-gray-600", bg: "bg-gray-100" },
volunteer: { label: "Volunteer", desc: "Read-only access", icon: Eye }, volunteer: { label: "Volunteer", desc: "Minimal access — they mostly use the live feed link", icon: Eye, color: "text-gray-400", bg: "bg-gray-50" },
} }
export default function SettingsPage() { export default function SettingsPage() {
@@ -61,6 +63,9 @@ export default function SettingsPage() {
const [inviteResult, setInviteResult] = useState<{ email: string; tempPassword: string } | null>(null) const [inviteResult, setInviteResult] = useState<{ email: string; tempPassword: string } | null>(null)
const [copiedCred, setCopiedCred] = useState(false) const [copiedCred, setCopiedCred] = useState(false)
// WhatsApp status (pulled from child, exposed for readiness bar)
const [waStatus, setWaStatus] = useState<string>("loading")
useEffect(() => { useEffect(() => {
Promise.all([ Promise.all([
fetch("/api/settings").then(r => r.json()), fetch("/api/settings").then(r => r.json()),
@@ -139,34 +144,59 @@ export default function SettingsPage() {
const update = (key: keyof OrgSettings, value: string) => setSettings(s => s ? { ...s, [key]: value } : s) const update = (key: keyof OrgSettings, value: string) => setSettings(s => s ? { ...s, [key]: value } : s)
const isAdmin = currentUser?.role === "org_admin" || currentUser?.role === "super_admin" const isAdmin = currentUser?.role === "org_admin" || currentUser?.role === "super_admin"
const SaveButton = ({ section, data }: { section: string; data: Record<string, string> }) => ( // Readiness checks
<button const bankReady = !!(settings.bankSortCode && settings.bankAccountNo && settings.bankAccountName)
onClick={() => save(section, data)} const whatsappReady = waStatus === "CONNECTED"
disabled={saving === section} const charityReady = !!settings.name
className="bg-[#111827] px-4 py-2 text-xs font-bold text-white hover:bg-gray-800 disabled:opacity-50 transition-colors"
>
{saving === section ? <><Loader2 className="h-3 w-3 mr-1.5 animate-spin inline" /> Saving</> : saved === section ? <><Check className="h-3 w-3 mr-1.5 inline" /> Saved!</> : "Save"}
</button>
)
return ( return (
<div className="space-y-8 max-w-2xl"> <div className="space-y-8">
{/* ── Header ── */}
<div> <div>
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">{settings.name}</p>
<h1 className="text-3xl font-black text-[#111827] tracking-tight">Settings</h1> <h1 className="text-3xl font-black text-[#111827] tracking-tight">Settings</h1>
<p className="text-sm text-gray-500 mt-0.5">WhatsApp, team, bank account, and charity details</p> </div>
{/* ── Readiness bar — "Am I set up?" ── */}
<div className="bg-[#111827] p-5">
<p className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-3">Setup progress</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-px bg-gray-700">
{[
{ label: "WhatsApp", ready: whatsappReady, detail: whatsappReady ? "Connected" : "Not connected" },
{ label: "Bank details", ready: bankReady, detail: bankReady ? `${settings.bankSortCode}` : "Not set" },
{ label: "Charity name", ready: charityReady, detail: charityReady ? settings.name : "Not set" },
{ label: "Team", ready: team.length > 0, detail: `${team.length} member${team.length !== 1 ? "s" : ""}` },
].map(item => (
<div key={item.label} className="bg-[#111827] p-3">
<div className="flex items-center gap-1.5 mb-1">
<div className={`w-2 h-2 ${item.ready ? "bg-[#4ADE80]" : "bg-gray-600"}`} />
<p className="text-[10px] font-bold text-gray-400">{item.label}</p>
</div>
<p className={`text-sm font-bold ${item.ready ? "text-white" : "text-gray-600"} truncate`}>{item.detail}</p>
</div>
))}
</div>
</div> </div>
{error && <div className="border-l-2 border-[#DC2626] bg-[#DC2626]/5 p-3 text-sm text-[#DC2626]">{error}</div>} {error && <div className="border-l-2 border-[#DC2626] bg-[#DC2626]/5 p-3 text-sm text-[#DC2626]">{error}</div>}
{/* ── 1. WhatsApp ── */} {/* ── 1. WhatsApp ── */}
<WhatsAppPanel /> <WhatsAppPanel onStatusChange={setWaStatus} />
{/* ── 2. Team management ── */} {/* ── 2. Team management ── */}
{isAdmin && ( {isAdmin && (
<div className="bg-white border border-gray-200"> <section className="bg-white border border-gray-200">
<div className="p-6 pb-4"> <div className="border-b border-gray-100 px-5 py-4 flex items-center justify-between">
<div className="flex items-center justify-between mb-1"> <div className="flex items-center gap-3">
<h3 className="text-base font-bold text-[#111827]">Team</h3> <div className="w-8 h-8 bg-[#1E40AF]/10 flex items-center justify-center shrink-0">
<Users className="h-4 w-4 text-[#1E40AF]" />
</div>
<div>
<h2 className="text-sm font-bold text-[#111827]">Team</h2>
<p className="text-[10px] text-gray-500">People who can access your dashboard</p>
</div>
</div>
<button <button
onClick={() => { setShowInvite(!showInvite); setInviteResult(null) }} onClick={() => { setShowInvite(!showInvite); setInviteResult(null) }}
className="bg-[#111827] px-3 py-1.5 text-xs font-bold text-white hover:bg-gray-800 transition-colors flex items-center gap-1.5" className="bg-[#111827] px-3 py-1.5 text-xs font-bold text-white hover:bg-gray-800 transition-colors flex items-center gap-1.5"
@@ -174,115 +204,143 @@ export default function SettingsPage() {
<UserPlus className="h-3.5 w-3.5" /> Invite <UserPlus className="h-3.5 w-3.5" /> Invite
</button> </button>
</div> </div>
<p className="text-xs text-gray-500">People who can access your dashboard. Invite community leaders to track their pledges.</p>
</div>
{/* Invite form */} {/* Invite form */}
{showInvite && !inviteResult && ( {showInvite && !inviteResult && (
<div className="mx-6 mb-4 border-2 border-[#1E40AF] p-4 space-y-3"> <div className="border-b border-gray-100 p-5 bg-[#F9FAFB] space-y-4">
<p className="text-sm font-bold text-[#111827]">Invite a team member</p> <p className="text-xs font-bold text-[#111827]">Invite a team member</p>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<label className="text-[10px] font-bold text-gray-500 block mb-1">Email</label> <label className="text-[10px] font-bold text-gray-500 uppercase tracking-wide block mb-1.5">Email</label>
<input value={inviteEmail} onChange={e => setInviteEmail(e.target.value)} placeholder="imam@mosque.org" className="w-full h-9 px-3 border-2 border-gray-200 text-sm focus:border-[#1E40AF] outline-none" /> <input
value={inviteEmail}
onChange={e => setInviteEmail(e.target.value)}
placeholder="imam@mosque.org"
className="w-full h-10 px-3 border-2 border-gray-200 bg-white text-sm placeholder:text-gray-300 focus:border-[#1E40AF] outline-none"
/>
</div> </div>
<div> <div>
<label className="text-[10px] font-bold text-gray-500 block mb-1">Name <span className="font-normal text-gray-400">(optional)</span></label> <label className="text-[10px] font-bold text-gray-500 uppercase tracking-wide block mb-1.5">
<input value={inviteName} onChange={e => setInviteName(e.target.value)} placeholder="Imam Yusuf" className="w-full h-9 px-3 border-2 border-gray-200 text-sm focus:border-[#1E40AF] outline-none" /> Name <span className="font-normal text-gray-400 normal-case">(optional)</span>
</label>
<input
value={inviteName}
onChange={e => setInviteName(e.target.value)}
placeholder="Imam Yusuf"
className="w-full h-10 px-3 border-2 border-gray-200 bg-white text-sm placeholder:text-gray-300 focus:border-[#1E40AF] outline-none"
/>
</div> </div>
</div> </div>
<div> <div>
<label className="text-[10px] font-bold text-gray-500 block mb-2">Role</label> <label className="text-[10px] font-bold text-gray-500 uppercase tracking-wide block mb-2">What can they do?</label>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
{Object.entries(ROLE_LABELS).filter(([k]) => k !== "org_admin" || currentUser?.role === "super_admin").map(([key, r]) => ( {Object.entries(ROLE_META).filter(([k]) => k !== "org_admin" || currentUser?.role === "super_admin").map(([key, r]) => {
const active = inviteRole === key
return (
<button <button
key={key} key={key}
onClick={() => setInviteRole(key)} onClick={() => setInviteRole(key)}
className={`border-2 p-3 text-left transition-all ${inviteRole === key ? "border-[#1E40AF] bg-[#1E40AF]/5" : "border-gray-200"}`} className={`border-2 p-3 text-left transition-all ${active ? "border-[#1E40AF] bg-[#1E40AF]/5" : "border-gray-200 bg-white hover:border-gray-300"}`}
> >
<div className="flex items-center gap-2 mb-1">
<div className={`w-5 h-5 flex items-center justify-center ${r.bg}`}>
<r.icon className={`h-3 w-3 ${r.color}`} />
</div>
<p className="text-xs font-bold text-[#111827]">{r.label}</p> <p className="text-xs font-bold text-[#111827]">{r.label}</p>
<p className="text-[10px] text-gray-500 mt-0.5">{r.desc}</p> </div>
<p className="text-[10px] text-gray-500 leading-snug">{r.desc}</p>
</button> </button>
))} )
})}
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2 pt-1">
<button onClick={() => setShowInvite(false)} className="flex-1 border border-gray-200 py-2 text-xs font-bold text-[#111827] hover:bg-gray-50">Cancel</button> <button onClick={() => setShowInvite(false)} className="flex-1 border-2 border-gray-200 py-2.5 text-xs font-bold text-[#111827] hover:bg-gray-50 transition-colors">Cancel</button>
<button onClick={inviteMember} disabled={!inviteEmail.trim() || inviting} className="flex-1 bg-[#111827] py-2 text-xs font-bold text-white hover:bg-gray-800 disabled:opacity-40 flex items-center justify-center gap-1.5"> <button onClick={inviteMember} disabled={!inviteEmail.trim() || inviting} className="flex-1 bg-[#111827] py-2.5 text-xs font-bold text-white hover:bg-gray-800 disabled:opacity-40 transition-colors flex items-center justify-center gap-1.5">
{inviting ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : "Send invite"} {inviting ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : "Create account"}
</button> </button>
</div> </div>
</div> </div>
)} )}
{/* Invite result — show credentials once */} {/* Invite result — credentials shown once */}
{inviteResult && ( {inviteResult && (
<div className="mx-6 mb-4 bg-[#16A34A]/5 border border-[#16A34A]/20 p-4 space-y-3"> <div className="border-b border-gray-100 p-5 bg-[#16A34A]/5 space-y-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Check className="h-4 w-4 text-[#16A34A]" /> <div className="w-5 h-5 bg-[#16A34A] flex items-center justify-center">
<p className="text-sm font-bold text-[#111827]">Invited!</p> <Check className="h-3 w-3 text-white" />
</div> </div>
<p className="text-xs text-gray-600">Share these login details with them. The password is shown only once.</p> <p className="text-sm font-bold text-[#111827]">Account created</p>
<div className="bg-white border border-gray-200 p-3 font-mono text-xs space-y-1">
<p>Email: <strong>{inviteResult.email}</strong></p>
<p>Password: <strong>{inviteResult.tempPassword}</strong></p>
<p>Login: <strong>{typeof window !== "undefined" ? window.location.origin : ""}/login</strong></p>
</div> </div>
<div className="flex gap-2"> <div className="border-l-2 border-[#16A34A] pl-3">
<p className="text-xs text-gray-600">Share these login details. The password is shown <strong>only once</strong>.</p>
</div>
<div className="bg-white border border-gray-200 p-4 space-y-2">
<div className="grid grid-cols-[80px_1fr] gap-1 text-xs">
<span className="text-gray-500">Email</span>
<span className="font-bold text-[#111827] font-mono">{inviteResult.email}</span>
<span className="text-gray-500">Password</span>
<span className="font-bold text-[#111827] font-mono">{inviteResult.tempPassword}</span>
<span className="text-gray-500">Login URL</span>
<span className="font-bold text-[#111827] font-mono text-[11px]">{typeof window !== "undefined" ? window.location.origin : ""}/login</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<button <button
onClick={() => copyCredentials(inviteResult.email, inviteResult.tempPassword)} onClick={() => copyCredentials(inviteResult.email, inviteResult.tempPassword)}
className="flex-1 bg-[#111827] py-2 text-xs font-bold text-white hover:bg-gray-800 flex items-center justify-center gap-1.5" className={`py-2.5 text-xs font-bold transition-colors flex items-center justify-center gap-1.5 ${copiedCred ? "bg-[#16A34A] text-white" : "bg-[#111827] text-white hover:bg-gray-800"}`}
> >
{copiedCred ? <><Check className="h-3 w-3" /> Copied</> : <><Copy className="h-3 w-3" /> Copy credentials</>} {copiedCred ? <><Check className="h-3 w-3" /> Copied</> : <><Copy className="h-3 w-3" /> Copy all</>}
</button> </button>
<button <button
onClick={() => { onClick={() => {
const text = `Your login for ${settings?.name || "Pledge Now Pay Later"}:\n\nEmail: ${inviteResult.email}\nPassword: ${inviteResult.tempPassword}\nLogin: ${window.location.origin}/login` const text = `Your login for ${settings?.name || "Pledge Now Pay Later"}:\n\nEmail: ${inviteResult.email}\nPassword: ${inviteResult.tempPassword}\nLogin: ${window.location.origin}/login`
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, "_blank") window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, "_blank")
}} }}
className="bg-[#25D366] py-2 px-4 text-xs font-bold text-white hover:bg-[#25D366]/90 flex items-center gap-1.5" className="bg-[#25D366] py-2.5 text-xs font-bold text-white hover:bg-[#25D366]/90 transition-colors flex items-center justify-center gap-1.5"
> >
<MessageCircle className="h-3 w-3" /> WhatsApp <MessageCircle className="h-3 w-3" /> Send via WhatsApp
</button> </button>
</div> </div>
<button onClick={() => { setInviteResult(null); setShowInvite(false) }} className="text-xs text-gray-400 hover:text-gray-600">Done</button> <button onClick={() => { setInviteResult(null); setShowInvite(false) }} className="text-xs text-gray-400 hover:text-gray-600">Done</button>
</div> </div>
)} )}
{/* Team list */} {/* Team list — gap-px grid */}
<div className="divide-y divide-gray-50"> {team.length > 0 ? (
<div className="divide-y divide-gray-100">
{team.map(m => { {team.map(m => {
const r = ROLE_LABELS[m.role] || ROLE_LABELS.staff const r = ROLE_META[m.role] || ROLE_META.staff
const isCurrentUser = m.id === currentUser?.id const isMe = m.id === currentUser?.id
const RoleIcon = r.icon const RoleIcon = r.icon
return ( return (
<div key={m.id} className="px-6 py-3 flex items-center gap-3"> <div key={m.id} className="px-5 py-3 flex items-center gap-3 hover:bg-[#F9FAFB] transition-colors">
<div className="w-8 h-8 bg-[#1E40AF]/10 flex items-center justify-center shrink-0"> <div className={`w-8 h-8 flex items-center justify-center shrink-0 ${r.bg}`}>
<RoleIcon className="h-4 w-4 text-[#1E40AF]" /> <RoleIcon className={`h-3.5 w-3.5 ${r.color}`} />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<p className="text-sm font-medium text-[#111827] truncate">{m.name || m.email}</p> <p className="text-sm font-medium text-[#111827] truncate">{m.name || m.email.split("@")[0]}</p>
{isCurrentUser && <span className="text-[9px] font-bold text-gray-400">You</span>} {isMe && <span className="text-[8px] font-bold text-gray-400 uppercase tracking-wider">You</span>}
</div> </div>
<p className="text-[10px] text-gray-500">{m.email}</p> <p className="text-[10px] text-gray-400 truncate">{m.email}</p>
</div> </div>
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0">
{isAdmin && !isCurrentUser ? ( {isAdmin && !isMe ? (
<select <select
value={m.role} value={m.role}
onChange={e => changeRole(m.id, e.target.value)} onChange={e => changeRole(m.id, e.target.value)}
className="text-[10px] font-bold border border-gray-200 px-2 py-1 bg-white" className="text-[10px] font-bold border-2 border-gray-200 px-2 py-1.5 bg-white focus:border-[#1E40AF] outline-none"
> >
{Object.entries(ROLE_LABELS).map(([key, v]) => ( {Object.entries(ROLE_META).map(([key, v]) => (
<option key={key} value={key}>{v.label}</option> <option key={key} value={key}>{v.label}</option>
))} ))}
</select> </select>
) : ( ) : (
<span className="text-[10px] font-bold px-2 py-0.5 bg-gray-100 text-gray-600">{r.label}</span> <span className={`text-[10px] font-bold px-2 py-1 ${r.bg} ${r.color}`}>{r.label}</span>
)} )}
{isAdmin && !isCurrentUser && ( {isAdmin && !isMe && (
<button onClick={() => removeMember(m.id)} className="text-gray-300 hover:text-[#DC2626] p-1 transition-colors"> <button onClick={() => removeMember(m.id)} className="text-gray-300 hover:text-[#DC2626] p-1.5 transition-colors" title="Remove">
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" />
</button> </button>
)} )}
@@ -291,171 +349,399 @@ export default function SettingsPage() {
) )
})} })}
</div> </div>
) : (
<div className="p-8 text-center">
<Users className="h-6 w-6 text-gray-200 mx-auto mb-2" />
<p className="text-xs text-gray-400">Just you for now. Invite community leaders to track their pledges.</p>
</div> </div>
)} )}
</section>
)}
{/* ── 3. Bank account ── */} {/* ── 3. Bank account ── */}
<div className="bg-white border border-gray-200 p-6 space-y-4"> <section className="bg-white border border-gray-200">
<div> <div className="border-b border-gray-100 px-5 py-4 flex items-center gap-3">
<h3 className="text-base font-bold text-[#111827]">Bank account</h3> <div className={`w-8 h-8 flex items-center justify-center shrink-0 ${bankReady ? "bg-[#16A34A]/10" : "bg-gray-100"}`}>
<p className="text-xs text-gray-500 mt-0.5">Shown to donors so they know where to transfer. Each pledge gets a unique reference.</p> <Building2 className={`h-4 w-4 ${bankReady ? "text-[#16A34A]" : "text-gray-400"}`} />
</div>
<div className="grid grid-cols-2 gap-3">
<div><label className="text-[10px] font-bold text-gray-500 block mb-1">Bank name</label><Input value={settings.bankName} onChange={e => update("bankName", e.target.value)} placeholder="e.g. Barclays" /></div>
<div><label className="text-[10px] font-bold text-gray-500 block mb-1">Account name</label><Input value={settings.bankAccountName} onChange={e => update("bankAccountName", e.target.value)} /></div>
</div>
<div className="grid grid-cols-2 gap-3">
<div><label className="text-[10px] font-bold text-gray-500 block mb-1">Sort code</label><Input value={settings.bankSortCode} onChange={e => update("bankSortCode", e.target.value)} placeholder="20-30-80" /></div>
<div><label className="text-[10px] font-bold text-gray-500 block mb-1">Account number</label><Input value={settings.bankAccountNo} onChange={e => update("bankAccountNo", e.target.value)} placeholder="12345678" /></div>
</div> </div>
<div> <div>
<label className="text-[10px] font-bold text-gray-500 block mb-1">Reference prefix</label> <div className="flex items-center gap-2">
<Input value={settings.refPrefix} onChange={e => update("refPrefix", e.target.value)} maxLength={4} className="w-24" /> <h2 className="text-sm font-bold text-[#111827]">Bank account</h2>
<p className="text-[10px] text-gray-400 mt-1">Donors see references like <strong>{settings.refPrefix}-XXXX-50</strong></p> {bankReady && <div className="w-2 h-2 bg-[#16A34A]" />}
</div> </div>
<SaveButton section="bank" data={{ bankName: settings.bankName, bankSortCode: settings.bankSortCode, bankAccountNo: settings.bankAccountNo, bankAccountName: settings.bankAccountName, refPrefix: settings.refPrefix }} /> <p className="text-[10px] text-gray-500">Where donors send their payment</p>
</div> </div>
</div>
<div className="p-5 space-y-4">
<div className="border-l-2 border-[#1E40AF] pl-3 text-xs text-gray-500">
<p>When someone pledges, they see these details with instructions to transfer. Each pledge gets a unique reference so you can match payments.</p>
</div>
<div className="grid grid-cols-2 gap-3">
<Field label="Bank name" value={settings.bankName} onChange={v => update("bankName", v)} placeholder="e.g. Barclays" />
<Field label="Account name" value={settings.bankAccountName} onChange={v => update("bankAccountName", v)} placeholder="e.g. Al Furqan Mosque" />
</div>
<div className="grid grid-cols-2 gap-3">
<Field label="Sort code" value={settings.bankSortCode} onChange={v => update("bankSortCode", v)} placeholder="20-30-80" />
<Field label="Account number" value={settings.bankAccountNo} onChange={v => update("bankAccountNo", v)} placeholder="12345678" />
</div>
<div className="grid grid-cols-[96px_1fr] gap-3 items-end">
<Field label="Reference prefix" value={settings.refPrefix} onChange={v => update("refPrefix", v)} maxLength={4} />
<div className="pb-[2px]">
<p className="text-[10px] text-gray-400">
Donors see references like <span className="font-bold text-[#111827]">{settings.refPrefix || "PNPL"}-A2F4-50</span>
</p>
</div>
</div>
{/* Donor preview — what they actually see */}
{bankReady && (
<div className="bg-[#F9FAFB] border border-gray-100 p-4 space-y-2">
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-wide">What donors see after pledging</p>
<div className="bg-white border border-gray-200 p-4 space-y-3">
<p className="text-xs font-bold text-[#111827]">Transfer to:</p>
<div className="grid grid-cols-[80px_1fr] gap-y-1.5 text-xs">
<span className="text-gray-500">Bank</span>
<span className="font-bold text-[#111827]">{settings.bankName}</span>
<span className="text-gray-500">Name</span>
<span className="font-bold text-[#111827]">{settings.bankAccountName}</span>
<span className="text-gray-500">Sort code</span>
<span className="font-mono font-bold text-[#111827]">{settings.bankSortCode}</span>
<span className="text-gray-500">Account</span>
<span className="font-mono font-bold text-[#111827]">{settings.bankAccountNo}</span>
<span className="text-gray-500">Reference</span>
<span className="font-mono font-bold text-[#1E40AF]">{settings.refPrefix || "PNPL"}-A2F4-50</span>
</div>
</div>
</div>
)}
<div className="flex items-center justify-between pt-1">
<p className="text-[10px] text-gray-400">Changes apply to new pledges immediately</p>
<SaveBtn
section="bank"
saving={saving}
saved={saved}
onSave={() => save("bank", { bankName: settings.bankName, bankSortCode: settings.bankSortCode, bankAccountNo: settings.bankAccountNo, bankAccountName: settings.bankAccountName, refPrefix: settings.refPrefix })}
/>
</div>
</div>
</section>
{/* ── 4. Charity details ── */} {/* ── 4. Charity details ── */}
<div className="bg-white border border-gray-200 p-6 space-y-4"> <section className="bg-white border border-gray-200">
<div className="border-b border-gray-100 px-5 py-4 flex items-center gap-3">
<div className="w-8 h-8 bg-gray-100 flex items-center justify-center shrink-0">
<Palette className="h-4 w-4 text-gray-600" />
</div>
<div> <div>
<h3 className="text-base font-bold text-[#111827]">Your charity</h3> <h2 className="text-sm font-bold text-[#111827]">Your charity</h2>
<p className="text-xs text-gray-500 mt-0.5">Name and colour shown on pledge pages and WhatsApp messages.</p> <p className="text-[10px] text-gray-500">Name and brand shown on pledge pages and WhatsApp messages</p>
</div> </div>
<div><label className="text-[10px] font-bold text-gray-500 block mb-1">Charity name</label><Input value={settings.name} onChange={e => update("name", e.target.value)} /></div>
<div>
<label className="text-[10px] font-bold text-gray-500 block mb-1">Brand colour</label>
<div className="flex gap-2 mt-1">
<Input type="color" value={settings.primaryColor} onChange={e => update("primaryColor", e.target.value)} className="w-12 h-9 p-0.5" />
<Input value={settings.primaryColor} onChange={e => update("primaryColor", e.target.value)} className="flex-1" />
</div>
</div>
<SaveButton section="brand" data={{ name: settings.name, primaryColor: settings.primaryColor }} />
</div> </div>
{/* ── 5. Direct Debit (collapsed) ── */} <div className="p-5 space-y-4">
<details className="bg-white border border-gray-200"> <Field label="Charity name" value={settings.name} onChange={v => update("name", v)} placeholder="e.g. Al Furqan Mosque" />
<summary className="p-6 text-base font-bold text-[#111827] cursor-pointer hover:bg-gray-50 transition-colors">
Direct Debit <span className="text-xs font-normal text-gray-400 ml-2">GoCardless integration</span>
</summary>
<div className="px-6 pb-6 space-y-4 border-t border-gray-100 pt-4">
<p className="text-xs text-gray-500">Accept Direct Debit payments via GoCardless. Donors set up a mandate and payments are collected automatically.</p>
<div> <div>
<label className="text-[10px] font-bold text-gray-500 block mb-1">GoCardless access token</label> <label className="text-[10px] font-bold text-gray-500 uppercase tracking-wide block mb-1.5">Brand colour</label>
<Input type="password" value={settings.gcAccessToken} onChange={e => update("gcAccessToken", e.target.value)} placeholder="sandbox_xxxxx or live_xxxxx" /> <div className="flex gap-2">
<div
className="w-10 h-10 border-2 border-gray-200 shrink-0 cursor-pointer relative overflow-hidden"
style={{ backgroundColor: settings.primaryColor || "#1E40AF" }}
>
<input
type="color"
value={settings.primaryColor || "#1E40AF"}
onChange={e => update("primaryColor", e.target.value)}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
</div> </div>
<input
value={settings.primaryColor || "#1E40AF"}
onChange={e => update("primaryColor", e.target.value)}
className="flex-1 h-10 px-3 border-2 border-gray-200 text-sm font-mono placeholder:text-gray-300 focus:border-[#1E40AF] outline-none"
/>
</div>
</div>
{/* Brand preview */}
<div className="bg-[#F9FAFB] border border-gray-100 p-4 space-y-2">
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-wide">Preview pledge page header</p>
<div className="bg-white border border-gray-200 p-4">
<div className="flex items-center gap-2.5">
<div className="h-8 w-8 flex items-center justify-center" style={{ backgroundColor: settings.primaryColor || "#1E40AF" }}>
<span className="text-white text-xs font-black">{(settings.name || "P")[0].toUpperCase()}</span>
</div>
<span className="font-black text-sm tracking-tight text-[#111827]">{settings.name || "Your Charity"}</span>
</div>
</div>
</div>
<div className="flex justify-end pt-1">
<SaveBtn
section="brand"
saving={saving}
saved={saved}
onSave={() => save("brand", { name: settings.name, primaryColor: settings.primaryColor })}
/>
</div>
</div>
</section>
{/* ── 5. Direct Debit (collapsed) ── */}
<details className="bg-white border border-gray-200 group">
<summary className="px-5 py-4 flex items-center gap-3 cursor-pointer hover:bg-[#F9FAFB] transition-colors list-none [&::-webkit-details-marker]:hidden">
<div className="w-8 h-8 bg-gray-100 flex items-center justify-center shrink-0">
<CreditCard className="h-4 w-4 text-gray-400" />
</div>
<div className="flex-1">
<h2 className="text-sm font-bold text-[#111827]">Direct Debit</h2>
<p className="text-[10px] text-gray-500">GoCardless integration advanced</p>
</div>
<ChevronRight className="h-4 w-4 text-gray-300 transition-transform group-open:rotate-90" />
</summary>
<div className="border-t border-gray-100 p-5 space-y-4">
<div className="border-l-2 border-gray-300 pl-3 text-xs text-gray-500">
<p>Accept Direct Debit via GoCardless. Donors set up a mandate and payments are collected automatically. Most charities don&apos;t need this bank transfer works fine.</p>
</div>
<Field
label="GoCardless access token"
value={settings.gcAccessToken}
onChange={v => update("gcAccessToken", v)}
placeholder="sandbox_xxxxx or live_xxxxx"
type="password"
/>
<div> <div>
<label className="text-[10px] font-bold text-gray-500 block mb-1">Mode</label> <label className="text-[10px] font-bold text-gray-500 uppercase tracking-wide block mb-2">Mode</label>
<div className="flex gap-2 mt-1"> <div className="flex gap-2">
{["sandbox", "live"].map(env => ( {(["sandbox", "live"] as const).map(env => (
<button key={env} onClick={() => update("gcEnvironment", env)} className={`px-3 py-1.5 text-xs font-bold border-2 transition-colors ${settings.gcEnvironment === env ? env === "live" ? "border-[#DC2626] bg-[#DC2626]/5 text-[#DC2626]" : "border-[#1E40AF] bg-[#1E40AF]/5 text-[#1E40AF]" : "border-gray-200 text-gray-400"}`}> <button
key={env}
onClick={() => update("gcEnvironment", env)}
className={`px-4 py-2 text-xs font-bold border-2 transition-colors ${settings.gcEnvironment === env
? env === "live"
? "border-[#DC2626] bg-[#DC2626]/5 text-[#DC2626]"
: "border-[#1E40AF] bg-[#1E40AF]/5 text-[#1E40AF]"
: "border-gray-200 text-gray-400 hover:border-gray-300"}`}
>
{env === "sandbox" ? "Test mode" : "Live mode"} {env === "sandbox" ? "Test mode" : "Live mode"}
</button> </button>
))} ))}
</div> </div>
</div> </div>
<SaveButton section="gc" data={{ gcAccessToken: settings.gcAccessToken, gcEnvironment: settings.gcEnvironment }} /> <div className="flex justify-end pt-1">
<SaveBtn
section="gc"
saving={saving}
saved={saved}
onSave={() => save("gc", { gcAccessToken: settings.gcAccessToken, gcEnvironment: settings.gcEnvironment })}
/>
</div>
</div> </div>
</details> </details>
</div> </div>
) )
} }
// ─── WhatsApp Connection Panel (unchanged) ───────────────────
function WhatsAppPanel() { // ─── Reusable Field component ────────────────────────────────
function Field({ label, value, onChange, placeholder, type = "text", maxLength }: {
label: string; value: string; onChange: (v: string) => void
placeholder?: string; type?: string; maxLength?: number
}) {
return (
<div>
<label className="text-[10px] font-bold text-gray-500 uppercase tracking-wide block mb-1.5">{label}</label>
<input
type={type}
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
maxLength={maxLength}
className="w-full h-10 px-3 border-2 border-gray-200 text-sm placeholder:text-gray-300 focus:border-[#1E40AF] outline-none transition-colors"
/>
</div>
)
}
// ─── Save button component ───────────────────────────────────
function SaveBtn({ section, saving, saved, onSave }: {
section: string; saving: string | null; saved: string | null; onSave: () => void
}) {
const isSaving = saving === section
const isSaved = saved === section
return (
<button
onClick={onSave}
disabled={isSaving}
className={`px-5 py-2.5 text-xs font-bold transition-all flex items-center gap-1.5 ${
isSaved
? "bg-[#16A34A] text-white"
: "bg-[#111827] text-white hover:bg-gray-800 disabled:opacity-50"
}`}
>
{isSaving ? <><Loader2 className="h-3 w-3 animate-spin" /> Saving</>
: isSaved ? <><Check className="h-3 w-3" /> Saved</>
: "Save changes"}
</button>
)
}
// ─── WhatsApp Connection Panel ───────────────────────────────
function WhatsAppPanel({ onStatusChange }: { onStatusChange?: (status: string) => void }) {
const [status, setStatus] = useState<string>("loading") const [status, setStatus] = useState<string>("loading")
const [qrImage, setQrImage] = useState<string | null>(null) const [qrImage, setQrImage] = useState<string | null>(null)
const [phone, setPhone] = useState(""); const [pushName, setPushName] = useState("") const [phone, setPhone] = useState("")
const [starting, setStarting] = useState(false); const [showQr, setShowQr] = useState(false) const [pushName, setPushName] = useState("")
const [starting, setStarting] = useState(false)
const [showQr, setShowQr] = useState(false)
const checkStatus = useCallback(async () => { const checkStatus = useCallback(async () => {
try { try {
const res = await fetch("/api/whatsapp/qr"); const data = await res.json() const res = await fetch("/api/whatsapp/qr")
const data = await res.json()
setStatus(data.status) setStatus(data.status)
onStatusChange?.(data.status)
if (data.screenshot) setQrImage(data.screenshot) if (data.screenshot) setQrImage(data.screenshot)
if (data.phone) setPhone(data.phone) if (data.phone) setPhone(data.phone)
if (data.pushName) setPushName(data.pushName) if (data.pushName) setPushName(data.pushName)
if (data.status === "CONNECTED") setShowQr(false) if (data.status === "CONNECTED") setShowQr(false)
} catch { setStatus("ERROR") } } catch {
}, []) setStatus("ERROR")
onStatusChange?.("ERROR")
}
}, [onStatusChange])
useEffect(() => { checkStatus() }, [checkStatus]) useEffect(() => { checkStatus() }, [checkStatus])
useEffect(() => { if (!showQr) return; const i = setInterval(checkStatus, 5000); return () => clearInterval(i) }, [showQr, checkStatus]) useEffect(() => { if (!showQr) return; const i = setInterval(checkStatus, 5000); return () => clearInterval(i) }, [showQr, checkStatus])
const startSession = async () => { const startSession = async () => {
setStarting(true); setShowQr(true) setStarting(true); setShowQr(true)
try { await fetch("/api/whatsapp/qr", { method: "POST" }); await new Promise(r => setTimeout(r, 3000)); await checkStatus() } catch { /* */ } try {
await fetch("/api/whatsapp/qr", { method: "POST" })
await new Promise(r => setTimeout(r, 3000))
await checkStatus()
} catch { /* */ }
setStarting(false) setStarting(false)
} }
// ── Connected state ──
if (status === "CONNECTED") { if (status === "CONNECTED") {
return ( return (
<div className="bg-white border border-[#25D366]/30 p-6"> <section className="bg-white border border-[#25D366]/30">
<div className="flex items-center gap-2 mb-4"> <div className="border-b border-[#25D366]/10 px-5 py-4 flex items-center gap-3">
<h3 className="text-base font-bold text-[#111827]">WhatsApp</h3> <div className="w-8 h-8 bg-[#25D366]/10 flex items-center justify-center shrink-0">
<span className="text-[10px] font-bold px-1.5 py-0.5 bg-[#25D366]/10 text-[#25D366] flex items-center gap-1"><Radio className="h-2.5 w-2.5" /> Connected</span> <Smartphone className="h-4 w-4 text-[#25D366]" />
</div> </div>
<div className="flex items-center gap-4"> <div className="flex-1">
<div className="w-10 h-10 bg-[#25D366]/10 flex items-center justify-center"><Smartphone className="h-5 w-5 text-[#25D366]" /></div> <div className="flex items-center gap-2">
<div><p className="text-sm font-medium text-[#111827]">{pushName || "WhatsApp"}</p><p className="text-xs text-gray-500">+{phone}</p></div> <h2 className="text-sm font-bold text-[#111827]">WhatsApp</h2>
<Wifi className="h-5 w-5 text-[#25D366] ml-auto" /> <span className="text-[9px] font-bold px-1.5 py-0.5 bg-[#25D366]/10 text-[#25D366] flex items-center gap-1">
<Radio className="h-2 w-2" /> Connected
</span>
</div> </div>
<div className="mt-4 pt-3 border-t border-[#25D366]/10 grid grid-cols-3 gap-3"> <p className="text-[10px] text-gray-500">{pushName || "WhatsApp"} · +{phone}</p>
</div>
<Wifi className="h-5 w-5 text-[#25D366]" />
</div>
{/* What's active */}
<div className="grid grid-cols-3 gap-px bg-[#25D366]/10">
{[ {[
{ label: "Receipts", desc: "Auto-sends when someone pledges" }, { label: "Receipts", desc: "Auto-sent when someone pledges" },
{ label: "Reminders", desc: "4-step reminder sequence" }, { label: "Reminders", desc: "4-step nudge sequence" },
{ label: "Chatbot", desc: "Donors reply PAID, HELP, etc." }, { label: "Chatbot", desc: "PAID, HELP, CANCEL replies" },
].map(f => (<div key={f.label} className="text-center"><p className="text-xs font-bold text-[#111827]">{f.label}</p><p className="text-[9px] text-gray-500 mt-0.5">{f.desc}</p></div>))} ].map(f => (
<div key={f.label} className="bg-white p-4 text-center">
<p className="text-xs font-bold text-[#111827]">{f.label}</p>
<p className="text-[9px] text-gray-500 mt-0.5">{f.desc}</p>
</div> </div>
))}
</div> </div>
</section>
) )
} }
// ── QR scanning state ──
if (status === "SCAN_QR_CODE" && showQr) { if (status === "SCAN_QR_CODE" && showQr) {
return ( return (
<div className="bg-white border border-[#F59E0B]/30 p-6"> <section className="bg-white border border-[#F59E0B]/30">
<div className="flex items-center gap-2 mb-4"> <div className="border-b border-[#F59E0B]/10 px-5 py-4 flex items-center gap-3">
<h3 className="text-base font-bold text-[#111827]">WhatsApp</h3> <div className="w-8 h-8 bg-[#F59E0B]/10 flex items-center justify-center shrink-0">
<span className="text-[10px] font-bold px-1.5 py-0.5 bg-[#F59E0B]/10 text-[#F59E0B] flex items-center gap-1"><QrCode className="h-2.5 w-2.5" /> Scan QR</span> <QrCode className="h-4 w-4 text-[#F59E0B]" />
</div> </div>
<div className="flex flex-col items-center gap-4"> <div>
<h2 className="text-sm font-bold text-[#111827]">WhatsApp</h2>
<p className="text-[10px] text-[#F59E0B] font-bold">Waiting for QR scan</p>
</div>
</div>
<div className="p-8 flex flex-col items-center gap-4">
{qrImage ? ( {qrImage ? (
<div className="w-64 h-64 border-2 border-[#25D366]/20 overflow-hidden bg-white"> <div className="w-64 h-64 border-2 border-[#25D366]/20 overflow-hidden bg-white">
{/* eslint-disable-next-line @next/next/no-img-element */} {/* eslint-disable-next-line @next/next/no-img-element */}
<img src={qrImage} alt="WhatsApp QR Code" className="w-[200%] h-auto max-w-none" style={{ marginLeft: "-30%", marginTop: "-35%" }} /> <img src={qrImage} alt="WhatsApp QR Code" className="w-[200%] h-auto max-w-none" style={{ marginLeft: "-30%", marginTop: "-35%" }} />
</div> </div>
) : ( ) : (
<div className="w-64 h-64 border-2 border-dashed border-gray-200 flex items-center justify-center"><Loader2 className="h-6 w-6 text-gray-400 animate-spin" /></div> <div className="w-64 h-64 border-2 border-dashed border-gray-200 flex items-center justify-center">
<Loader2 className="h-6 w-6 text-gray-300 animate-spin" />
</div>
)} )}
<div className="text-center space-y-1"> <div className="text-center space-y-1">
<p className="text-sm font-bold text-[#111827]">Scan with your phone</p> <p className="text-sm font-bold text-[#111827]">Scan with your phone</p>
<div className="border-l-2 border-[#25D366] pl-3 text-left inline-block">
<p className="text-xs text-gray-500">WhatsApp Settings Linked Devices Link a Device</p> <p className="text-xs text-gray-500">WhatsApp Settings Linked Devices Link a Device</p>
</div> </div>
<button onClick={checkStatus} className="border border-gray-200 px-3 py-1.5 text-xs font-semibold text-gray-600 hover:bg-gray-50 flex items-center gap-1.5"><RefreshCw className="h-3 w-3" /> Refresh</button>
</div> </div>
<button onClick={checkStatus} className="border-2 border-gray-200 px-4 py-2 text-xs font-bold text-gray-600 hover:bg-gray-50 flex items-center gap-1.5 transition-colors">
<RefreshCw className="h-3 w-3" /> Refresh
</button>
</div> </div>
</section>
) )
} }
// ── Not connected state ──
return ( return (
<div className="bg-white border border-gray-200 p-6"> <section className="bg-white border border-gray-200">
<div className="flex items-center gap-2 mb-4"> <div className="border-b border-gray-100 px-5 py-4 flex items-center gap-3">
<h3 className="text-base font-bold text-[#111827]">WhatsApp</h3> <div className="w-8 h-8 bg-gray-100 flex items-center justify-center shrink-0">
<span className="text-[10px] font-bold px-1.5 py-0.5 bg-gray-100 text-gray-500 flex items-center gap-1"><WifiOff className="h-2.5 w-2.5" /> Not connected</span> <WifiOff className="h-4 w-4 text-gray-400" />
</div> </div>
<div className="border-l-2 border-[#25D366] pl-4 space-y-2"> <div>
<p className="text-sm font-medium text-[#111827]">Connect your WhatsApp number</p> <div className="flex items-center gap-2">
<div className="text-xs text-gray-500 space-y-0.5"> <h2 className="text-sm font-bold text-[#111827]">WhatsApp</h2>
<p>When you connect, donors automatically receive:</p> <span className="text-[9px] font-bold px-1.5 py-0.5 bg-gray-100 text-gray-500">Not connected</span>
<p className="font-medium text-gray-600"> Pledge receipts with bank details</p> </div>
<p className="font-medium text-gray-600"> Payment reminders on a 4-step schedule</p> <p className="text-[10px] text-gray-500">Connect to send automatic receipts and reminders</p>
<p className="font-medium text-gray-600"> A chatbot (they reply PAID, HELP, or CANCEL)</p>
</div> </div>
</div> </div>
<button onClick={startSession} disabled={starting} className="mt-4 w-full bg-[#25D366] px-4 py-2.5 text-sm font-bold text-white hover:bg-[#25D366]/90 disabled:opacity-50 transition-colors flex items-center justify-center gap-2">
{starting ? <><Loader2 className="h-4 w-4 animate-spin" /> Starting...</> : <><MessageCircle className="h-4 w-4" /> Connect WhatsApp</>} <div className="p-5 space-y-4">
<div className="border-l-2 border-[#25D366] pl-3 space-y-1.5 text-xs text-gray-600">
<p className="font-bold text-[#111827]">When you connect, donors automatically receive:</p>
<div className="grid grid-cols-1 gap-1">
<p>Pledge receipt with your bank details within seconds</p>
<p>Payment reminders 4-step sequence over 14 days</p>
<p>Chatbot replies they text <span className="font-mono font-bold text-[#111827]">PAID</span>, <span className="font-mono font-bold text-[#111827]">HELP</span>, or <span className="font-mono font-bold text-[#111827]">CANCEL</span></p>
</div>
</div>
<button
onClick={startSession}
disabled={starting}
className="w-full bg-[#25D366] px-4 py-3 text-sm font-bold text-white hover:bg-[#25D366]/90 disabled:opacity-50 transition-colors flex items-center justify-center gap-2"
>
{starting ? <><Loader2 className="h-4 w-4 animate-spin" /> Starting</> : <><MessageCircle className="h-4 w-4" /> Connect WhatsApp</>}
</button> </button>
</div> </div>
</section>
) )
} }