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
This commit is contained in:
2026-03-03 06:42:11 +08:00
parent 05acda0adb
commit 0e8df76f89
11 changed files with 767 additions and 269 deletions

View File

@@ -0,0 +1,187 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Check, MessageCircle, Share2, Sparkles, ExternalLink, ArrowRight } from "lucide-react"
const platformBranding: Record<string, { name: string; color: string; icon: string }> = {
launchgood: { name: "LaunchGood", color: "#00C389", icon: "🌙" },
enthuse: { name: "Enthuse", color: "#6B4FBB", icon: "💜" },
justgiving: { name: "JustGiving", color: "#AD29B6", icon: "💛" },
gofundme: { name: "GoFundMe", color: "#00B964", icon: "💚" },
other: { name: "Fundraising Page", color: "#3B82F6", icon: "🔗" },
}
interface Props {
pledge: { id: string; reference: string }
amount: number
eventName: string
externalUrl: string
externalPlatform?: string | null
donorPhone?: string
}
export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, externalPlatform, donorPhone }: Props) {
const [clicked, setClicked] = useState(false)
const [whatsappSent, setWhatsappSent] = useState(false)
const platform = platformBranding[externalPlatform || "other"] || platformBranding.other
// Send WhatsApp with link
useEffect(() => {
if (!donorPhone || whatsappSent) return
fetch("/api/whatsapp/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "receipt",
phone: donorPhone,
data: {
amountPounds: (amount / 100).toFixed(0),
eventName,
reference: pledge.reference,
rail: "external",
externalUrl,
},
}),
}).then(() => setWhatsappSent(true)).catch(() => {})
}, [donorPhone, whatsappSent, amount, eventName, pledge.reference, externalUrl])
const handleDonate = () => {
setClicked(true)
if (navigator.vibrate) navigator.vibrate([10, 50, 10])
// Mark as initiated
fetch(`/api/pledges/${pledge.id}/mark-initiated`, { method: "POST" }).catch(() => {})
// Open external URL
window.open(externalUrl, "_blank")
}
if (clicked) {
return (
<div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up">
<div className="relative inline-flex items-center justify-center">
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" />
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30">
<Check className="h-10 w-10 text-white" strokeWidth={3} />
</div>
</div>
<h1 className="text-2xl font-black text-gray-900">Jazak&apos;Allah Khair!</h1>
<p className="text-muted-foreground">
Complete your <span className="font-bold text-foreground">£{(amount / 100).toFixed(0)}</span> donation on {platform.name}.
</p>
{whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-4 w-4" /> Link sent to your WhatsApp
</div>
)}
<Card className="text-left">
<CardContent className="pt-4 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Pledge ref</span>
<span className="font-mono font-bold text-trust-blue">{pledge.reference}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Donate via</span>
<span>{platform.icon} {platform.name}</span>
</div>
</CardContent>
</Card>
{/* Re-open link */}
<Button onClick={() => window.open(externalUrl, "_blank")} className="w-full" variant="outline">
<ExternalLink className="h-4 w-4 mr-2" /> Open {platform.name} again
</Button>
{/* Share */}
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3">
<div className="flex items-center justify-center gap-2">
<Sparkles className="h-4 w-4 text-warm-amber" />
<p className="text-sm font-bold text-gray-900">Know someone who&apos;d donate too?</p>
</div>
<div className="flex gap-2">
<Button
onClick={() => {
const text = `I just pledged £${(amount / 100).toFixed(0)} to ${eventName}! 🤲\nDonate here: ${externalUrl}`
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, "_blank")
}}
className="flex-1 bg-[#25D366] hover:bg-[#20BD5A] text-white shadow-lg shadow-[#25D366]/25"
size="sm"
>
<MessageCircle className="h-4 w-4 mr-1" /> WhatsApp
</Button>
<Button
onClick={() => navigator.share?.({ title: eventName, text: `Donate to ${eventName}`, url: externalUrl })}
variant="outline"
size="sm"
className="flex-1"
>
<Share2 className="h-4 w-4 mr-1" /> Share
</Button>
</div>
</div>
<p className="text-xs text-muted-foreground">
We&apos;ll send you a gentle reminder if we don&apos;t see a donation come through.
</p>
</div>
)
}
return (
<div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up">
<div className="text-center space-y-2">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl shadow-lg" style={{ background: platform.color + "20" }}>
<span className="text-2xl">{platform.icon}</span>
</div>
<h1 className="text-2xl font-black text-gray-900 tracking-tight">
Donate £{(amount / 100).toFixed(0)}
</h1>
<p className="text-sm text-muted-foreground">
You&apos;ll be taken to {platform.name} to complete your donation
</p>
</div>
{whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-3.5 w-3.5" /> Donation link also sent to your WhatsApp
</div>
)}
{/* Preview card */}
<Card className="overflow-hidden">
<div className="h-1" style={{ background: platform.color }} />
<CardContent className="pt-4 space-y-3">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Campaign</span>
<span className="font-medium">{eventName}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Amount</span>
<span className="font-bold text-xl">£{(amount / 100).toFixed(0)}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Platform</span>
<span className="flex items-center gap-1">{platform.icon} {platform.name}</span>
</div>
</CardContent>
</Card>
{/* Big CTA */}
<Button
size="xl"
className="w-full text-white shadow-lg"
style={{ background: platform.color }}
onClick={handleDonate}
>
Donate on {platform.name} <ArrowRight className="h-5 w-5 ml-2" />
</Button>
<p className="text-center text-xs text-muted-foreground">
Your pledge is recorded. We&apos;ll follow up to make sure it goes through 🤝
</p>
</div>
)
}