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:
@@ -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'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'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'll send you a gentle reminder if we don'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'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'll follow up to make sure it goes through 🤝
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user