"use client" import { useState, useEffect, useCallback } from "react" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Badge } from "@/components/ui/badge" import { Building2, CreditCard, Palette, Check, Loader2, AlertCircle, MessageCircle, Radio, QrCode, RefreshCw, Smartphone, Wifi, WifiOff } from "lucide-react" interface OrgSettings { name: string bankName: string bankSortCode: string bankAccountNo: string bankAccountName: string refPrefix: string primaryColor: string gcAccessToken: string gcEnvironment: string orgType: string } export default function SettingsPage() { const [settings, setSettings] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(null) const [saved, setSaved] = useState(null) const [error, setError] = useState(null) useEffect(() => { fetch("/api/settings") .then((r) => r.json()) .then((data) => { if (data.name) setSettings(data) }) .catch(() => setError("Failed to load settings")) .finally(() => setLoading(false)) }, []) const save = async (section: string, data: Record) => { setSaving(section) setError(null) try { const res = await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }) if (res.ok) { setSaved(section); setTimeout(() => setSaved(null), 2000) } else setError("Failed to save") } catch { setError("Failed to save") } setSaving(null) } if (loading) return
if (!settings) return

Failed to load settings

const update = (key: keyof OrgSettings, value: string) => setSettings((s) => s ? { ...s, [key]: value } : s) return (

Settings

Configure your organisation's payment details and integrations

{error &&
{error}
} {/* WhatsApp — MOST IMPORTANT, first */} {/* Bank Details */} Bank Account Shown to donors who choose bank transfer. Each pledge gets a unique reference.
update("bankName", e.target.value)} placeholder="e.g. Barclays" />
update("bankAccountName", e.target.value)} />
update("bankSortCode", e.target.value)} placeholder="20-30-80" />
update("bankAccountNo", e.target.value)} placeholder="12345678" />
update("refPrefix", e.target.value)} maxLength={4} className="w-24" />

e.g. {settings.refPrefix}-XXXX-50

{/* GoCardless */} GoCardless (Direct Debit) Enable Direct Debit collection protected by the DD Guarantee.
update("gcAccessToken", e.target.value)} placeholder="sandbox_xxxxx or live_xxxxx" />
{["sandbox", "live"].map((env) => ( ))}
{/* Branding */} Branding
update("name", e.target.value)} />
update("primaryColor", e.target.value)} className="w-12 h-9 p-0.5" /> update("primaryColor", e.target.value)} className="flex-1" />
) } // ─── WhatsApp Connection Panel ─────────────────────────────────── function WhatsAppPanel() { const [status, setStatus] = useState("loading") const [qrImage, setQrImage] = useState(null) const [phone, setPhone] = useState("") const [pushName, setPushName] = useState("") const [starting, setStarting] = useState(false) const [showQr, setShowQr] = useState(false) // only true after user clicks Connect const checkStatus = useCallback(async () => { try { const res = await fetch("/api/whatsapp/qr") const data = await res.json() setStatus(data.status) if (data.screenshot) setQrImage(data.screenshot) if (data.phone) setPhone(data.phone) if (data.pushName) setPushName(data.pushName) // Auto-show QR panel once connected (user paired successfully) if (data.status === "CONNECTED") setShowQr(false) } catch { setStatus("ERROR") } }, []) // On mount: just check if already connected. Don't start polling yet. useEffect(() => { checkStatus() }, [checkStatus]) // Poll only when user has clicked Connect and we're waiting for scan useEffect(() => { if (!showQr) return const interval = setInterval(checkStatus, 5000) return () => clearInterval(interval) }, [showQr, checkStatus]) const startSession = async () => { setStarting(true) setShowQr(true) try { await fetch("/api/whatsapp/qr", { method: "POST" }) await new Promise(r => setTimeout(r, 3000)) await checkStatus() } catch { /* ignore */ } setStarting(false) } if (status === "CONNECTED") { return ( WhatsApp Connected

{pushName || "WhatsApp Business"}

+{phone}

Auto-Sends

Receipts

Reminders

4-step

Chatbot

PAID / HELP

) } if (status === "SCAN_QR_CODE" && showQr) { return ( WhatsApp Scan QR Open WhatsApp on your phone → Settings → Linked Devices → Link a Device
{qrImage ? (
{/* Crop to QR area: the screenshot shows full WhatsApp web page. QR code is roughly in center. We use overflow hidden + object positioning. */}
{/* eslint-disable-next-line @next/next/no-img-element */} WhatsApp QR Code
) : (
)}

Scan with WhatsApp

Open WhatsApp → Settings → Linked Devices → Link a Device

Auto-refreshes every 5 seconds

) } // NO_SESSION or STARTING or ERROR return ( WhatsApp Offline Connect WhatsApp to auto-send pledge receipts, payment reminders, and enable a chatbot for donors.

Connect your WhatsApp number

  • 📨 Pledge receipts with bank transfer details
  • ⏰ Automatic reminders (2d before → due day → 3d after → 10d final)
  • 🤖 Donor chatbot: reply PAID, HELP, CANCEL, STATUS
  • 📊 Volunteer notifications when someone pledges at their table

Uses WAHA (WhatsApp HTTP API) · No WhatsApp Business API required · Free tier

) }