full terminology overhaul + zakat fund types + fund allocation

POSITIONING FIX — PNPL is NOT just 'QR codes at events':
- Charities collecting at events (QR per table)
- High-net-worth donor outreach (personal links via WhatsApp/email)
- Org-to-org pledges (multi-charity projects)
- Personal fundraisers (LaunchGood/Enthuse redirect)

TERMINOLOGY (throughout app):
- Events → Campaigns (sidebar, pages, create dialogs, onboarding)
- QR Codes page → Pledge Links (sharing-first, QR is one option)
- Scans → Clicks (not just QR scans)
- 'New Event' → 'New Campaign'
- 'Create QR Code' → 'Create Pledge Link'
- Source label: 'Table Name' → 'Source / Channel'

SHARING (pledge links page):
- 4-button share row: Copy · WhatsApp · Email · More (native share)
- Each link shows its full URL
- Create dialog suggests: 'WhatsApp Family Group, Table 5, Instagram Bio'
- QR code is still shown but as one option, not the hero

LANDING PAGE (complete rewrite):
- Hero: 'Collect pledges. Convert them into donations.'
- 4 use case cards: Events, HNW Donors, Org-to-Org, Personal Fundraisers
- 'Share anywhere' section: WhatsApp, QR, Email, Instagram, Twitter, 1-on-1
- Platform support: Bank Transfer, LaunchGood, Enthuse, JustGiving, GoFundMe, Any URL
- Islamic fund types section: Zakat, Sadaqah, Sadaqah Jariyah, Lillah, Fitrana

ZAKAT & FUND TYPES:
- Organization.zakatEnabled toggle in Settings
- Pledge.fundType: general, zakat, sadaqah, lillah, fitrana
- Identity step: fund type picker (5 options) when org has zakatEnabled
- Zakat note: Quran 9:60 categories reference
- Settings: toggle card with fund type descriptions

FUND ALLOCATION:
- Event.fundAllocation: 'Mosque Building Fund', 'Orphan Sponsorship' etc.
- Charities can also add external URL for reference/allocation (not just fundraisers)
- Shows on campaign cards and pledge flow
This commit is contained in:
2026-03-03 07:00:04 +08:00
parent 0e8df76f89
commit f87aec7beb
17 changed files with 486 additions and 202 deletions

View File

@@ -104,6 +104,7 @@ export async function POST(request: NextRequest) {
paymentMode: parsed.data.paymentMode || "self",
externalUrl: parsed.data.externalUrl,
externalPlatform: parsed.data.externalPlatform,
fundAllocation: parsed.data.fundAllocation,
slug: slug + "-" + Date.now().toString(36),
eventDate: parsed.data.eventDate ? new Date(parsed.data.eventDate) : null,
organizationId: orgId,

View File

@@ -45,16 +45,16 @@ export async function GET() {
steps.push(
{ id: "bank", label: "Add bank details", desc: "So donors know where to send money", done: hasBank, href: "/dashboard/settings" },
{ id: "whatsapp", label: "Connect WhatsApp", desc: "Auto-send receipts & reminders to donors", done: hasWhatsApp, href: "/dashboard/settings", action: "whatsapp" },
{ id: "event", label: "Create a fundraiser", desc: "Give your campaign a name & goal", done: hasEvent, href: "/dashboard/events" },
{ id: "share", label: "Share your first link", desc: "Generate a QR code or copy the link", done: hasQr, href: "/dashboard/events" },
{ id: "event", label: "Create a campaign", desc: "Name your fundraiser and set a goal", done: hasEvent, href: "/dashboard/events" },
{ id: "share", label: "Share your pledge link", desc: "Send via WhatsApp, social media, email, or print a QR", done: hasQr, href: "/dashboard/events" },
)
} else {
// FUNDRAISER flow — needs external URL, no bank
steps.push(
{ id: "event", label: "Add your fundraising page", desc: "Paste your LaunchGood, Enthuse or JustGiving link", done: hasExternalEvent > 0, href: "/dashboard/events" },
{ id: "whatsapp", label: "Connect WhatsApp", desc: "Auto-remind donors to complete their pledge", done: hasWhatsApp, href: "/dashboard/settings", action: "whatsapp" },
{ id: "share", label: "Share your pledge link", desc: "Generate a QR or copy the link to share", done: hasQr, href: "/dashboard/events" },
{ id: "pledge", label: "Get your first pledge", desc: "Share with friends & family to start", done: hasPledge, href: "/dashboard/pledges" },
{ id: "share", label: "Share your pledge link", desc: "WhatsApp, social media, email, or QR code", done: hasQr, href: "/dashboard/events" },
{ id: "pledge", label: "Get your first pledge", desc: "Share with your network to start collecting", done: hasPledge, href: "/dashboard/pledges" },
)
}

View File

@@ -105,7 +105,7 @@ export async function POST(request: NextRequest) {
)
}
const { amountPence, rail, donorName, donorEmail, donorPhone, giftAid, eventId, qrSourceId, scheduleMode, dueDate, installmentCount, installmentDates } = parsed.data
const { amountPence, rail, donorName, donorEmail, donorPhone, giftAid, fundType, eventId, qrSourceId, scheduleMode, dueDate, installmentCount, installmentDates } = parsed.data
// Get event + org
const event = await prisma.event.findUnique({
@@ -161,6 +161,7 @@ export async function POST(request: NextRequest) {
donorEmail: donorEmail || null,
donorPhone: donorPhone || null,
giftAid,
fundType: fundType || null,
eventId,
qrSourceId: qrSourceId || null,
organizationId: org.id,
@@ -230,6 +231,7 @@ export async function POST(request: NextRequest) {
donorEmail: donorEmail || null,
donorPhone: donorPhone || null,
giftAid,
fundType: fundType || null,
eventId,
qrSourceId: qrSourceId || null,
organizationId: org.id,

View File

@@ -16,7 +16,7 @@ export async function GET(
if (token === "demo") {
const event = await prisma.event.findFirst({
where: { status: "active" },
include: { organization: { select: { name: true } } },
include: { organization: { select: { name: true, zakatEnabled: true } } },
orderBy: { createdAt: "asc" },
})
if (!event) {
@@ -31,6 +31,8 @@ export async function GET(
paymentMode: event.paymentMode || "self",
externalUrl: event.externalUrl || null,
externalPlatform: event.externalPlatform || null,
zakatEnabled: event.organization.zakatEnabled || false,
fundAllocation: event.fundAllocation || null,
})
}
@@ -39,7 +41,7 @@ export async function GET(
include: {
event: {
include: {
organization: { select: { name: true } },
organization: { select: { name: true, zakatEnabled: true } },
},
},
},
@@ -64,6 +66,8 @@ export async function GET(
paymentMode: qrSource.event.paymentMode || "self",
externalUrl: qrSource.event.externalUrl || null,
externalPlatform: qrSource.event.externalPlatform || null,
zakatEnabled: qrSource.event.organization.zakatEnabled || false,
fundAllocation: qrSource.event.fundAllocation || null,
})
} catch (error) {
console.error("QR resolve error:", error)

View File

@@ -25,6 +25,8 @@ export async function GET(request: NextRequest) {
primaryColor: org.primaryColor,
gcAccessToken: org.gcAccessToken ? "••••••••" : "",
gcEnvironment: org.gcEnvironment,
orgType: org.orgType || "charity",
zakatEnabled: org.zakatEnabled || false,
})
} catch (error) {
console.error("Settings GET error:", error)
@@ -80,13 +82,16 @@ export async function PATCH(request: NextRequest) {
if (!orgId) return NextResponse.json({ error: "Org not found" }, { status: 404 })
const body = await request.json()
const allowed = ["name", "bankName", "bankSortCode", "bankAccountNo", "bankAccountName", "refPrefix", "primaryColor", "logo", "gcAccessToken", "gcEnvironment"]
const data: Record<string, string> = {}
for (const key of allowed) {
const stringKeys = ["name", "bankName", "bankSortCode", "bankAccountNo", "bankAccountName", "refPrefix", "primaryColor", "logo", "gcAccessToken", "gcEnvironment"]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: Record<string, any> = {}
for (const key of stringKeys) {
if (key in body && body[key] !== undefined && body[key] !== "••••••••") {
data[key] = body[key]
}
}
// Boolean fields
if ("zakatEnabled" in body) data.zakatEnabled = !!body.zakatEnabled
const org = await prisma.organization.update({
where: { id: orgId },

View File

@@ -8,11 +8,11 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Dialog, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { formatPence } from "@/lib/utils"
import { Plus, Download, QrCode, ExternalLink, Copy, Check, Loader2, ArrowLeft, Trophy, Users, MessageCircle } from "lucide-react"
import { Plus, Download, ExternalLink, Copy, Check, Loader2, ArrowLeft, Trophy, Users, MessageCircle, Mail, Share2, Link2 } from "lucide-react"
import Link from "next/link"
import { QRCodeCanvas } from "@/components/qr-code"
interface QrSourceInfo {
interface SourceInfo {
id: string
label: string
code: string
@@ -23,10 +23,10 @@ interface QrSourceInfo {
totalPledged: number
}
export default function EventQRPage() {
export default function CampaignLinksPage() {
const params = useParams()
const eventId = params.id as string
const [qrSources, setQrSources] = useState<QrSourceInfo[]>([])
const [sources, setSources] = useState<SourceInfo[]>([])
const [loading, setLoading] = useState(true)
const [showCreate, setShowCreate] = useState(false)
const [copiedCode, setCopiedCode] = useState<string | null>(null)
@@ -38,9 +38,7 @@ export default function EventQRPage() {
useEffect(() => {
fetch(`/api/events/${eventId}/qr`)
.then((r) => r.json())
.then((data) => {
if (Array.isArray(data)) setQrSources(data)
})
.then((data) => { if (Array.isArray(data)) setSources(data) })
.catch(() => {})
.finally(() => setLoading(false))
}, [eventId])
@@ -48,9 +46,29 @@ export default function EventQRPage() {
const copyLink = async (code: string) => {
await navigator.clipboard.writeText(`${baseUrl}/p/${code}`)
setCopiedCode(code)
if (navigator.vibrate) navigator.vibrate(10)
setTimeout(() => setCopiedCode(null), 2000)
}
const shareLink = (code: string, label: string) => {
const url = `${baseUrl}/p/${code}`
if (navigator.share) {
navigator.share({ title: label, text: `Pledge here: ${url}`, url })
} else {
copyLink(code)
}
}
const shareWhatsApp = (code: string, label: string) => {
const url = `${baseUrl}/p/${code}`
window.open(`https://wa.me/?text=${encodeURIComponent(`Assalamu Alaikum! Please pledge here 🤲\n\n${label}\n${url}`)}`, "_blank")
}
const shareEmail = (code: string, label: string) => {
const url = `${baseUrl}/p/${code}`
window.open(`mailto:?subject=${encodeURIComponent(`Pledge: ${label}`)}&body=${encodeURIComponent(`Please pledge here:\n\n${url}`)}`)
}
const handleCreate = async () => {
setCreating(true)
try {
@@ -60,8 +78,8 @@ export default function EventQRPage() {
body: JSON.stringify(form),
})
if (res.ok) {
const qr = await res.json()
setQrSources((prev) => [{ ...qr, scanCount: 0, pledgeCount: 0, totalPledged: 0 }, ...prev])
const src = await res.json()
setSources((prev) => [{ ...src, scanCount: 0, pledgeCount: 0, totalPledged: 0 }, ...prev])
setShowCreate(false)
setForm({ label: "", volunteerName: "", tableName: "" })
}
@@ -69,7 +87,6 @@ export default function EventQRPage() {
setCreating(false)
}
// Auto-generate label
useEffect(() => {
if (form.volunteerName || form.tableName) {
const parts = [form.tableName, form.volunteerName].filter(Boolean)
@@ -77,142 +94,118 @@ export default function EventQRPage() {
}
}, [form.volunteerName, form.tableName])
if (loading) {
return (
<div className="flex items-center justify-center py-20">
<Loader2 className="h-8 w-8 text-trust-blue animate-spin" />
</div>
)
}
if (loading) return <div className="flex items-center justify-center py-20"><Loader2 className="h-8 w-8 text-trust-blue animate-spin" /></div>
const totalScans = qrSources.reduce((s, q) => s + q.scanCount, 0)
const totalPledges = qrSources.reduce((s, q) => s + q.pledgeCount, 0)
const totalAmount = qrSources.reduce((s, q) => s + q.totalPledged, 0)
const totalClicks = sources.reduce((s, q) => s + q.scanCount, 0)
const totalPledges = sources.reduce((s, q) => s + q.pledgeCount, 0)
const totalAmount = sources.reduce((s, q) => s + q.totalPledged, 0)
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<Link
href="/dashboard/events"
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1 mb-2"
>
<ArrowLeft className="h-3 w-3" /> Back to Events
<Link href="/dashboard/events" className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1 mb-2">
<ArrowLeft className="h-3 w-3" /> Back to Campaigns
</Link>
<h1 className="text-3xl font-extrabold text-gray-900">QR Codes</h1>
<h1 className="text-3xl font-extrabold text-gray-900">Pledge Links</h1>
<p className="text-muted-foreground mt-1">
{qrSources.length} QR code{qrSources.length !== 1 ? "s" : ""} · {totalScans} scans · {totalPledges} pledges · {formatPence(totalAmount)}
{sources.length} link{sources.length !== 1 ? "s" : ""} · {totalClicks} clicks · {totalPledges} pledges · {formatPence(totalAmount)}
</p>
</div>
<div className="flex gap-2">
<Link href={`/dashboard/events/${eventId}/leaderboard`}>
<Button variant="outline">
<Trophy className="h-4 w-4 mr-2" /> Leaderboard
</Button>
<Button variant="outline"><Trophy className="h-4 w-4 mr-2" /> Leaderboard</Button>
</Link>
<Button onClick={() => setShowCreate(true)}>
<Plus className="h-4 w-4 mr-2" /> New QR Code
<Plus className="h-4 w-4 mr-2" /> New Link
</Button>
</div>
</div>
{/* QR Grid */}
{qrSources.length === 0 ? (
{sources.length === 0 ? (
<Card>
<CardContent className="py-12 text-center space-y-4">
<QrCode className="h-12 w-12 text-muted-foreground mx-auto" />
<p className="text-muted-foreground">No QR codes yet. Create one to start collecting pledges!</p>
<Link2 className="h-12 w-12 text-muted-foreground mx-auto" />
<h3 className="font-bold text-lg">Create your first pledge link</h3>
<p className="text-muted-foreground text-sm max-w-md mx-auto">
Each link is unique and trackable. Create one per volunteer, table, WhatsApp group, social post, or email campaign so you know where pledges come from.
</p>
<Button onClick={() => setShowCreate(true)}>
<Plus className="h-4 w-4 mr-2" /> Create First QR Code
<Plus className="h-4 w-4 mr-2" /> Create Pledge Link
</Button>
</CardContent>
</Card>
) : (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{qrSources.map((qr) => (
<Card key={qr.id} className="hover:shadow-md transition-shadow">
{sources.map((src) => (
<Card key={src.id} className="hover:shadow-md transition-shadow">
<CardContent className="pt-6 space-y-4">
{/* QR Code */}
<div className="max-w-[180px] mx-auto bg-white rounded-2xl flex items-center justify-center p-2">
<QRCodeCanvas url={`${baseUrl}/p/${qr.code}`} size={164} />
{/* QR Code — compact */}
<div className="max-w-[140px] mx-auto bg-white rounded-xl flex items-center justify-center p-1.5">
<QRCodeCanvas url={`${baseUrl}/p/${src.code}`} size={128} />
</div>
<div className="text-center">
<h3 className="font-bold">{qr.label}</h3>
{qr.volunteerName && (
<p className="text-xs text-muted-foreground">Volunteer: {qr.volunteerName}</p>
)}
<h3 className="font-bold">{src.label}</h3>
{src.volunteerName && <p className="text-xs text-muted-foreground">By: {src.volunteerName}</p>}
<p className="text-[10px] text-muted-foreground font-mono mt-1">{baseUrl}/p/{src.code}</p>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-2 text-center text-xs">
<div className="rounded-lg bg-gray-50 p-2">
<p className="font-bold text-sm">{qr.scanCount}</p>
<p className="text-muted-foreground">Scans</p>
<p className="font-bold text-sm">{src.scanCount}</p>
<p className="text-muted-foreground">Clicks</p>
</div>
<div className="rounded-lg bg-gray-50 p-2">
<p className="font-bold text-sm">{qr.pledgeCount}</p>
<p className="font-bold text-sm">{src.pledgeCount}</p>
<p className="text-muted-foreground">Pledges</p>
</div>
<div className="rounded-lg bg-gray-50 p-2">
<p className="font-bold text-sm">{formatPence(qr.totalPledged)}</p>
<p className="text-muted-foreground">Total</p>
<p className="font-bold text-sm">{formatPence(src.totalPledged)}</p>
<p className="text-muted-foreground">Raised</p>
</div>
</div>
{/* Conversion rate */}
{qr.scanCount > 0 && (
{src.scanCount > 0 && (
<div className="text-center">
<span className="text-xs text-muted-foreground">
Conversion: <span className="font-semibold text-foreground">{Math.round((qr.pledgeCount / qr.scanCount) * 100)}%</span>
Conversion: <span className="font-semibold text-foreground">{Math.round((src.pledgeCount / src.scanCount) * 100)}%</span>
</span>
</div>
)}
{/* Actions */}
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => copyLink(qr.code)}
>
{copiedCode === qr.code ? (
<><Check className="h-3 w-3 mr-1" /> Copied</>
) : (
<><Copy className="h-3 w-3 mr-1" /> Link</>
)}
{/* Share options — the main CTA, not an afterthought */}
<div className="grid grid-cols-4 gap-1.5">
<Button variant="outline" size="sm" onClick={() => copyLink(src.code)} className="text-[10px] px-1">
{copiedCode === src.code ? <Check className="h-3.5 w-3.5" /> : <><Copy className="h-3 w-3" /> Copy</>}
</Button>
<Button size="sm" onClick={() => shareWhatsApp(src.code, src.label)} className="text-[10px] px-1 bg-[#25D366] hover:bg-[#20BD5A] text-white">
<MessageCircle className="h-3 w-3" /> WA
</Button>
<Button variant="outline" size="sm" onClick={() => shareEmail(src.code, src.label)} className="text-[10px] px-1">
<Mail className="h-3 w-3" /> Email
</Button>
<Button variant="outline" size="sm" onClick={() => shareLink(src.code, src.label)} className="text-[10px] px-1">
<Share2 className="h-3 w-3" /> More
</Button>
<a href={`/api/events/${eventId}/qr/${qr.id}/download?code=${qr.code}`} download>
<Button variant="outline" size="sm">
<Download className="h-3 w-3 mr-1" /> PNG
</Button>
</a>
<a href={`/p/${qr.code}`} target="_blank">
<Button variant="outline" size="sm">
<ExternalLink className="h-3 w-3" />
</Button>
</a>
</div>
{/* Volunteer & share links */}
{/* Secondary actions */}
<div className="flex gap-2">
<a href={`/v/${qr.code}`} target="_blank" className="flex-1">
<a href={`/api/events/${eventId}/qr/${src.id}/download?code=${src.code}`} download className="flex-1">
<Button variant="outline" size="sm" className="w-full text-xs">
<Users className="h-3 w-3 mr-1" /> Volunteer View
<Download className="h-3 w-3 mr-1" /> QR Image
</Button>
</a>
<Button
variant="outline"
size="sm"
className="flex-1 text-xs bg-[#25D366]/5 border-[#25D366]/30 text-[#25D366] hover:bg-[#25D366]/10"
onClick={() => {
const url = `${baseUrl}/p/${qr.code}`
const text = `Hi! Scan this to pledge: ${url}`
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, "_blank")
}}
>
<MessageCircle className="h-3 w-3 mr-1" /> Share
</Button>
<a href={`/v/${src.code}`} target="_blank" className="flex-1">
<Button variant="outline" size="sm" className="w-full text-xs">
<Users className="h-3 w-3 mr-1" /> Volunteer
</Button>
</a>
<a href={`/p/${src.code}`} target="_blank">
<Button variant="outline" size="sm"><ExternalLink className="h-3 w-3" /></Button>
</a>
</div>
</CardContent>
</Card>
@@ -223,42 +216,42 @@ export default function EventQRPage() {
{/* Create dialog */}
<Dialog open={showCreate} onOpenChange={setShowCreate}>
<DialogHeader>
<DialogTitle>Create QR Code</DialogTitle>
<DialogTitle>Create Pledge Link</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Each link is trackable. Create one per source to see where pledges come from.
</p>
<div className="space-y-2">
<Label>Label *</Label>
<Input
placeholder="e.g. WhatsApp Family Group, Table 5, Instagram Bio, Ahmed's Link"
value={form.label}
onChange={(e) => setForm((f) => ({ ...f, label: e.target.value }))}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Table Name</Label>
<Label>Source / Channel <span className="text-muted-foreground font-normal">(optional)</span></Label>
<Input
placeholder="e.g. Table 5"
placeholder="e.g. Table 5, WhatsApp, Twitter"
value={form.tableName}
onChange={(e) => setForm((f) => ({ ...f, tableName: e.target.value }))}
/>
</div>
<div className="space-y-2">
<Label>Volunteer Name</Label>
<Label>Person <span className="text-muted-foreground font-normal">(optional)</span></Label>
<Input
placeholder="e.g. Ahmed"
placeholder="e.g. Ahmed, Sarah"
value={form.volunteerName}
onChange={(e) => setForm((f) => ({ ...f, volunteerName: e.target.value }))}
/>
</div>
</div>
<div className="space-y-2">
<Label>Label *</Label>
<Input
placeholder="e.g. Table 5 - Ahmed"
value={form.label}
onChange={(e) => setForm((f) => ({ ...f, label: e.target.value }))}
/>
<p className="text-xs text-muted-foreground">Auto-generated from table + volunteer, or enter custom</p>
</div>
<div className="flex gap-3 pt-2">
<Button variant="outline" onClick={() => setShowCreate(false)} className="flex-1">
Cancel
</Button>
<Button variant="outline" onClick={() => setShowCreate(false)} className="flex-1">Cancel</Button>
<Button onClick={handleCreate} disabled={!form.label || creating} className="flex-1">
{creating ? "Creating..." : "Create QR Code"}
{creating ? "Creating..." : "Create Link"}
</Button>
</div>
</div>

View File

@@ -53,7 +53,7 @@ export default function EventsPage() {
}, [])
const [creating, setCreating] = useState(false)
const [orgType, setOrgType] = useState<string | null>(null)
const [form, setForm] = useState({ name: "", description: "", location: "", eventDate: "", goalAmount: "", paymentMode: "self" as "self" | "external", externalUrl: "", externalPlatform: "" })
const [form, setForm] = useState({ name: "", description: "", location: "", eventDate: "", goalAmount: "", paymentMode: "self" as "self" | "external", externalUrl: "", externalPlatform: "", fundAllocation: "" })
// Fetch org type to customize the form
useEffect(() => {
@@ -77,15 +77,16 @@ export default function EventsPage() {
goalAmount: form.goalAmount ? Math.round(parseFloat(form.goalAmount) * 100) : undefined,
eventDate: form.eventDate ? new Date(form.eventDate).toISOString() : undefined,
paymentMode: form.paymentMode,
externalUrl: form.paymentMode === "external" ? form.externalUrl : undefined,
externalUrl: form.externalUrl || undefined,
externalPlatform: form.paymentMode === "external" ? (form.externalPlatform || "other") : undefined,
fundAllocation: form.fundAllocation || undefined,
}),
})
if (res.ok) {
const event = await res.json()
setEvents((prev) => [{ ...event, pledgeCount: 0, qrSourceCount: 0, totalPledged: 0, totalCollected: 0 }, ...prev])
setShowCreate(false)
setForm({ name: "", description: "", location: "", eventDate: "", goalAmount: "", paymentMode: orgType === "fundraiser" ? "external" : "self", externalUrl: "", externalPlatform: "" })
setForm({ name: "", description: "", location: "", eventDate: "", goalAmount: "", paymentMode: orgType === "fundraiser" ? "external" : "self", externalUrl: "", externalPlatform: "", fundAllocation: "" })
}
} catch {
// handle error
@@ -97,11 +98,11 @@ export default function EventsPage() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-extrabold text-gray-900">Events</h1>
<p className="text-muted-foreground mt-1">Manage your fundraising events and QR codes</p>
<h1 className="text-3xl font-extrabold text-gray-900">Campaigns</h1>
<p className="text-muted-foreground mt-1">Create campaigns, share pledge links, and track donations</p>
</div>
<Button onClick={() => setShowCreate(true)}>
<Plus className="h-4 w-4 mr-2" /> New Event
<Plus className="h-4 w-4 mr-2" /> New Campaign
</Button>
</div>
@@ -177,7 +178,7 @@ export default function EventsPage() {
<div className="flex gap-2">
<Link href={`/dashboard/events/${event.id}`} className="flex-1">
<Button variant="outline" size="sm" className="w-full">
<QrCode className="h-4 w-4 mr-1" /> QR Codes ({event.qrSourceCount})
<QrCode className="h-4 w-4 mr-1" /> Pledge Links ({event.qrSourceCount})
</Button>
</Link>
<Link href={`/dashboard/pledges?event=${event.id}`} className="flex-1">
@@ -195,13 +196,13 @@ export default function EventsPage() {
{/* Create dialog */}
<Dialog open={showCreate} onOpenChange={setShowCreate}>
<DialogHeader>
<DialogTitle>Create Event</DialogTitle>
<DialogTitle>New Campaign</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>Event Name *</Label>
<Label>Campaign Name *</Label>
<Input
placeholder="e.g. Ramadan Gala 2025"
placeholder="e.g. Ramadan Appeal 2026, Mosque Building Fund"
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
/>
@@ -297,12 +298,38 @@ export default function EventsPage() {
</div>
)}
{/* Fund allocation — for charities tracking which fund this goes to */}
{form.paymentMode === "self" && (
<div className="space-y-2">
<Label>Fund allocation <span className="text-muted-foreground font-normal">(optional)</span></Label>
<Input
placeholder="e.g. Mosque Building Fund, Orphan Sponsorship"
value={form.fundAllocation}
onChange={(e) => setForm(f => ({ ...f, fundAllocation: e.target.value }))}
/>
<p className="text-[10px] text-muted-foreground">Track which fund this event raises for. Shows on pledge receipts & reports.</p>
<div className="space-y-2">
<Label>External fundraising page <span className="text-muted-foreground font-normal">(optional)</span></Label>
<div className="relative">
<ExternalLink className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="https://launchgood.com/mosque-building"
value={form.externalUrl}
onChange={(e) => setForm(f => ({ ...f, externalUrl: e.target.value }))}
className="pl-9"
/>
</div>
<p className="text-[10px] text-muted-foreground">Link an external campaign page for reference & allocation tracking.</p>
</div>
</div>
)}
<div className="flex gap-3 pt-2">
<Button variant="outline" onClick={() => setShowCreate(false)} className="flex-1">
Cancel
</Button>
<Button onClick={handleCreate} disabled={!form.name || creating || (form.paymentMode === "external" && !form.externalUrl)} className="flex-1">
{creating ? "Creating..." : "Create Event"}
{creating ? "Creating..." : "Create Campaign"}
</Button>
</div>
</div>

View File

@@ -3,12 +3,12 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useSession, signOut } from "next-auth/react"
import { LayoutDashboard, Calendar, FileBarChart, Upload, Download, Settings, Plus, ExternalLink, LogOut, Shield } from "lucide-react"
import { LayoutDashboard, Megaphone, FileBarChart, Upload, Download, Settings, Plus, ExternalLink, LogOut, Shield } from "lucide-react"
import { cn } from "@/lib/utils"
const navItems = [
{ href: "/dashboard", label: "Overview", icon: LayoutDashboard },
{ href: "/dashboard/events", label: "Events", icon: Calendar },
{ href: "/dashboard/events", label: "Campaigns", icon: Megaphone },
{ href: "/dashboard/pledges", label: "Pledges", icon: FileBarChart },
{ href: "/dashboard/reconcile", label: "Reconcile", icon: Upload },
{ href: "/dashboard/exports", label: "Exports", icon: Download },
@@ -39,7 +39,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
<div className="flex-1" />
<Link href="/dashboard/events" className="hidden md:block">
<button className="inline-flex items-center gap-1.5 rounded-lg bg-trust-blue px-3 py-1.5 text-xs font-semibold text-white hover:bg-trust-blue/90 transition-colors">
<Plus className="h-3 w-3" /> New Event
<Plus className="h-3 w-3" /> New Campaign
</button>
</Link>
<Link href="/" className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1">

View File

@@ -302,7 +302,7 @@ export default function DashboardPage() {
</p>
<div className="flex gap-2 justify-center pt-2">
<Link href="/dashboard/events">
<Button size="sm">Create a Fundraiser </Button>
<Button size="sm">Create a Campaign </Button>
</Link>
</div>
</CardContent>

View File

@@ -21,6 +21,8 @@ interface OrgSettings {
primaryColor: string
gcAccessToken: string
gcEnvironment: string
orgType: string
zakatEnabled: boolean
}
export default function SettingsPage() {
@@ -116,6 +118,42 @@ export default function SettingsPage() {
</CardContent>
</Card>
{/* Zakat & Fund Types */}
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2"> Zakat & Fund Types</CardTitle>
<CardDescription className="text-xs">Let donors specify their donation type (Zakat, Sadaqah, Lillah, Fitrana)</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<button
onClick={() => {
setSettings(s => s ? { ...s, zakatEnabled: !s.zakatEnabled } : s)
fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ zakatEnabled: !settings.zakatEnabled }) }).then(() => { setSaved("zakat"); setTimeout(() => setSaved(null), 2000) }).catch(() => {})
}}
className={`w-full flex items-center justify-between rounded-xl border-2 p-4 transition-all ${
settings.zakatEnabled ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"
}`}
>
<div className="text-left">
<p className="text-sm font-bold">{settings.zakatEnabled ? "Fund types enabled" : "Enable fund types"}</p>
<p className="text-xs text-muted-foreground mt-0.5">Donors can choose: Zakat · Sadaqah · Lillah · Fitrana · General</p>
</div>
<div className={`w-11 h-6 rounded-full transition-colors ${settings.zakatEnabled ? "bg-trust-blue" : "bg-gray-200"}`}>
<div className={`w-5 h-5 bg-white rounded-full shadow-sm mt-0.5 transition-transform ${settings.zakatEnabled ? "translate-x-5.5 ml-[22px]" : "translate-x-0.5 ml-[2px]"}`} />
</div>
</button>
{settings.zakatEnabled && (
<div className="rounded-xl bg-trust-blue/5 border border-trust-blue/10 p-3 space-y-1.5 text-xs text-muted-foreground animate-fade-in">
<p>🌙 <strong>Zakat</strong> Obligatory 2.5% annual charity</p>
<p>🤲 <strong>Sadaqah / General</strong> Voluntary donations</p>
<p>🌱 <strong>Sadaqah Jariyah</strong> Ongoing charity (buildings, wells)</p>
<p>🕌 <strong>Lillah</strong> For the mosque / institution</p>
<p>🍽 <strong>Fitrana</strong> Zakat al-Fitr (before Eid)</p>
</div>
)}
</CardContent>
</Card>
{/* Branding */}
<Card>
<CardHeader>

View File

@@ -21,6 +21,7 @@ export interface PledgeData {
donorEmail: string
donorPhone: string
giftAid: boolean
fundType?: string
// Scheduling
scheduleMode: "now" | "date" | "installments"
dueDate?: string
@@ -37,6 +38,8 @@ interface EventInfo {
paymentMode: "self" | "external"
externalUrl: string | null
externalPlatform: string | null
zakatEnabled: boolean
fundAllocation: string | null
}
/*
@@ -134,7 +137,7 @@ export default function PledgePage() {
}
// Submit pledge (from identity step, or card/DD steps)
const submitPledge = async (identity: { donorName: string; donorEmail: string; donorPhone: string; giftAid: boolean }) => {
const submitPledge = async (identity: { donorName: string; donorEmail: string; donorPhone: string; giftAid: boolean; fundType?: string }) => {
const finalData = { ...pledgeData, ...identity }
setPledgeData(finalData)
@@ -146,6 +149,7 @@ export default function PledgePage() {
...finalData,
eventId: eventInfo?.id,
qrSourceId: eventInfo?.qrSourceId,
fundType: finalData.fundType || undefined,
}),
})
const result = await res.json()
@@ -205,7 +209,7 @@ export default function PledgePage() {
0: <AmountStep onSelect={handleAmountSelected} eventName={eventInfo?.name || ""} eventId={eventInfo?.id} />,
1: <ScheduleStep amount={pledgeData.amountPence} onSelect={handleScheduleSelected} />,
2: <PaymentStep onSelect={handleRailSelected} amount={pledgeData.amountPence} />,
3: <IdentityStep onSubmit={submitPledge} amount={pledgeData.amountPence} />,
3: <IdentityStep onSubmit={submitPledge} amount={pledgeData.amountPence} zakatEnabled={eventInfo?.zakatEnabled} fundAllocation={eventInfo?.fundAllocation} />,
4: pledgeResult && <BankInstructionsStep pledge={pledgeResult} amount={pledgeData.amountPence} eventName={eventInfo?.name || ""} donorPhone={pledgeData.donorPhone} />,
5: pledgeResult && (
<ConfirmationStep

View File

@@ -4,21 +4,33 @@ import { useState, useRef, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Gift, Shield, Sparkles, Phone, Mail } from "lucide-react"
const FUND_TYPES = [
{ id: "general", label: "General Donation", icon: "🤲", desc: "Sadaqah — used where most needed" },
{ id: "zakat", label: "Zakat", icon: "🌙", desc: "Obligatory annual charity (2.5%)" },
{ id: "sadaqah", label: "Sadaqah Jariyah", icon: "🌱", desc: "Ongoing charity — builds, wells, education" },
{ id: "lillah", label: "Lillah", icon: "🕌", desc: "For the mosque / institution itself" },
{ id: "fitrana", label: "Fitrana", icon: "🍽️", desc: "Zakat al-Fitr — given before Eid" },
]
interface Props {
onSubmit: (data: {
donorName: string
donorEmail: string
donorPhone: string
giftAid: boolean
fundType?: string
}) => void
amount: number
zakatEnabled?: boolean
fundAllocation?: string | null
}
export function IdentityStep({ onSubmit, amount }: Props) {
export function IdentityStep({ onSubmit, amount, zakatEnabled, fundAllocation }: Props) {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [phone, setPhone] = useState("")
const [giftAid, setGiftAid] = useState(false)
const [fundType, setFundType] = useState<string>(fundAllocation ? "general" : "general")
const [submitting, setSubmitting] = useState(false)
const [contactMode, setContactMode] = useState<"email" | "phone">("email")
const nameRef = useRef<HTMLInputElement>(null)
@@ -34,7 +46,7 @@ export function IdentityStep({ onSubmit, amount }: Props) {
if (!isValid) return
setSubmitting(true)
try {
await onSubmit({ donorName: name, donorEmail: email, donorPhone: phone, giftAid })
await onSubmit({ donorName: name, donorEmail: email, donorPhone: phone, giftAid, fundType: zakatEnabled ? fundType : undefined })
} catch {
setSubmitting(false)
}
@@ -122,6 +134,37 @@ export function IdentityStep({ onSubmit, amount }: Props) {
)}
</div>
{/* Fund Type — only when org has Zakat enabled */}
{zakatEnabled && (
<div className="space-y-2 animate-fade-in">
<p className="text-sm font-bold text-gray-900">
{fundAllocation ? `Fund: ${fundAllocation}` : "What is this donation for?"}
</p>
<div className="grid grid-cols-2 gap-2">
{FUND_TYPES.map((ft) => (
<button
key={ft.id}
onClick={() => setFundType(ft.id)}
className={`text-left rounded-xl border-2 p-3 transition-all ${
fundType === ft.id
? "border-trust-blue bg-trust-blue/5 shadow-sm"
: "border-gray-100 hover:border-gray-200"
}`}
>
<span className="text-lg">{ft.icon}</span>
<p className={`text-xs font-bold mt-1 ${fundType === ft.id ? "text-trust-blue" : "text-gray-900"}`}>{ft.label}</p>
<p className="text-[10px] text-muted-foreground leading-tight">{ft.desc}</p>
</button>
))}
</div>
{fundType === "zakat" && (
<div className="rounded-xl bg-warm-amber/5 border border-warm-amber/20 p-3 text-xs text-muted-foreground animate-fade-in">
Zakat is distributed according to the eight categories specified in the Quran (9:60). This charity is Zakat-eligible.
</div>
)}
</div>
)}
{/* Gift Aid — the hero */}
<button
onClick={() => setGiftAid(!giftAid)}

View File

@@ -13,12 +13,8 @@ export default function HomePage() {
<span className="font-black text-sm">Pledge Now, Pay Later</span>
</div>
<div className="flex items-center gap-3">
<Link href="/login" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">
Sign In
</Link>
<Link href="/signup" className="rounded-lg bg-trust-blue px-4 py-2 text-sm font-semibold text-white hover:bg-trust-blue/90 transition-colors">
Get Started Free
</Link>
<Link href="/login" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">Sign In</Link>
<Link href="/signup" className="rounded-lg bg-trust-blue px-4 py-2 text-sm font-semibold text-white hover:bg-trust-blue/90 transition-colors">Get Started Free</Link>
</div>
</div>
</header>
@@ -27,15 +23,14 @@ export default function HomePage() {
<section className="py-16 md:py-24 px-4">
<div className="max-w-3xl mx-auto text-center space-y-6">
<div className="inline-flex items-center gap-2 bg-trust-blue/5 border border-trust-blue/20 rounded-full px-4 py-1.5 text-xs font-medium text-trust-blue">
🇬🇧 Built for UK charities · Free to start
🇬🇧 Built for UK charities &amp; fundraisers · Free forever
</div>
<h1 className="text-4xl md:text-5xl font-black text-gray-900 leading-tight">
Turn promises into
<span className="text-trust-blue"> payments</span>
<h1 className="text-4xl md:text-6xl font-black text-gray-900 leading-[1.1]">
Collect pledges.<br />
<span className="text-trust-blue">Convert them into donations.</span>
</h1>
<p className="text-lg text-muted-foreground max-w-xl mx-auto">
At your next event, donors pledge what they want to give then pay on their own terms.
You get QR codes, WhatsApp reminders, and a dashboard to track every penny.
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Someone says &quot;I&apos;ll donate £5,000&quot; then what? PNPL captures that promise, sends WhatsApp reminders, and tracks every penny until it lands. At events, on social media, in WhatsApp groups, or one-on-one with high-net-worth donors.
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<Link href="/signup" className="rounded-xl bg-trust-blue px-6 py-3.5 text-base font-semibold text-white hover:bg-trust-blue/90 transition-all shadow-lg shadow-trust-blue/20">
@@ -49,50 +44,130 @@ export default function HomePage() {
</div>
</section>
{/* Problem */}
<section className="py-12 bg-gray-50 px-4">
<div className="max-w-4xl mx-auto">
{/* Use Cases — the 4 audiences */}
<section className="py-14 bg-gray-50 px-4">
<div className="max-w-5xl mx-auto space-y-8">
<div className="text-center">
<h2 className="text-3xl font-black text-gray-900">Who uses PNPL?</h2>
<p className="text-muted-foreground mt-2">Anyone who collects promises of money and needs them fulfilled.</p>
</div>
<div className="grid md:grid-cols-2 gap-5">
{/* Charity at events */}
<div className="bg-white rounded-2xl p-6 border space-y-3">
<div className="flex items-center gap-3">
<div className="w-11 h-11 rounded-xl bg-trust-blue/10 flex items-center justify-center text-xl">🕌</div>
<div>
<h3 className="font-bold">Charities &amp; Mosques at Events</h3>
<p className="text-xs text-muted-foreground">Gala dinners, Ramadan nights, Jumuah appeals</p>
</div>
</div>
<p className="text-sm text-muted-foreground">Print QR codes for each table. Donors scan, pledge an amount, and choose to pay now or later. You get a dashboard with every pledge, automatic WhatsApp reminders, and bank reconciliation.</p>
<div className="flex flex-wrap gap-1.5">
<span className="text-[10px] font-medium bg-trust-blue/5 text-trust-blue px-2 py-0.5 rounded-full">QR per table</span>
<span className="text-[10px] font-medium bg-trust-blue/5 text-trust-blue px-2 py-0.5 rounded-full">Bank transfer</span>
<span className="text-[10px] font-medium bg-trust-blue/5 text-trust-blue px-2 py-0.5 rounded-full">Gift Aid</span>
<span className="text-[10px] font-medium bg-trust-blue/5 text-trust-blue px-2 py-0.5 rounded-full">Zakat tracking</span>
</div>
</div>
{/* HNW donors */}
<div className="bg-white rounded-2xl p-6 border space-y-3">
<div className="flex items-center gap-3">
<div className="w-11 h-11 rounded-xl bg-warm-amber/10 flex items-center justify-center text-xl">💎</div>
<div>
<h3 className="font-bold">High-Net-Worth Donor Outreach</h3>
<p className="text-xs text-muted-foreground">Major gifts, personal pledges, board commitments</p>
</div>
</div>
<p className="text-sm text-muted-foreground">Send a personal pledge link to a major donor via WhatsApp or email. They commit to £50k over 6 months. PNPL tracks each instalment, sends reminders before due dates, and shows you the full pipeline.</p>
<div className="flex flex-wrap gap-1.5">
<span className="text-[10px] font-medium bg-warm-amber/5 text-warm-amber px-2 py-0.5 rounded-full">Personal links</span>
<span className="text-[10px] font-medium bg-warm-amber/5 text-warm-amber px-2 py-0.5 rounded-full">Instalments</span>
<span className="text-[10px] font-medium bg-warm-amber/5 text-warm-amber px-2 py-0.5 rounded-full">WhatsApp follow-up</span>
</div>
</div>
{/* Org-to-org */}
<div className="bg-white rounded-2xl p-6 border space-y-3">
<div className="flex items-center gap-3">
<div className="w-11 h-11 rounded-xl bg-success-green/10 flex items-center justify-center text-xl">🏗</div>
<div>
<h3 className="font-bold">Org-to-Org Pledges</h3>
<p className="text-xs text-muted-foreground">Multi-charity projects, umbrella fundraising</p>
</div>
</div>
<p className="text-sm text-muted-foreground">Coordinating a large project across multiple charities? Each org pledges their share. Track commitments from organisations, not just individuals. Allocate funds to specific projects and see who&apos;s delivered.</p>
<div className="flex flex-wrap gap-1.5">
<span className="text-[10px] font-medium bg-success-green/5 text-success-green px-2 py-0.5 rounded-full">Fund allocation</span>
<span className="text-[10px] font-medium bg-success-green/5 text-success-green px-2 py-0.5 rounded-full">Multi-party</span>
<span className="text-[10px] font-medium bg-success-green/5 text-success-green px-2 py-0.5 rounded-full">Project tracking</span>
</div>
</div>
{/* Personal fundraiser */}
<div className="bg-white rounded-2xl p-6 border space-y-3">
<div className="flex items-center gap-3">
<div className="w-11 h-11 rounded-xl bg-purple-100 flex items-center justify-center text-xl"></div>
<div>
<h3 className="font-bold">Personal Fundraisers</h3>
<p className="text-xs text-muted-foreground">LaunchGood, Enthuse, JustGiving, GoFundMe</p>
</div>
</div>
<p className="text-sm text-muted-foreground">Already have a fundraising page? Share your PNPL link on WhatsApp and social media. People pledge an amount, then get redirected to your LaunchGood/Enthuse/JustGiving page to pay. You see who actually followed through.</p>
<div className="flex flex-wrap gap-1.5">
<span className="text-[10px] font-medium bg-purple-50 text-purple-600 px-2 py-0.5 rounded-full">External redirect</span>
<span className="text-[10px] font-medium bg-purple-50 text-purple-600 px-2 py-0.5 rounded-full">Social sharing</span>
<span className="text-[10px] font-medium bg-purple-50 text-purple-600 px-2 py-0.5 rounded-full">Conversion tracking</span>
</div>
</div>
</div>
</div>
</section>
{/* The Problem */}
<section className="py-14 px-4">
<div className="max-w-4xl mx-auto space-y-8">
<div className="text-center">
<p className="text-sm font-bold text-danger-red uppercase tracking-wider">The Problem</p>
<h2 className="text-3xl font-black text-gray-900 mt-2">30-50% of pledges never convert</h2>
</div>
<div className="grid md:grid-cols-3 gap-6">
<div className="bg-white rounded-2xl p-6 border">
<div className="text-3xl mb-3">😤</div>
<h3 className="font-bold mb-1">Pledges go cold</h3>
<p className="text-sm text-muted-foreground">Donors say &quot;I&apos;ll pay £500&quot; at the gala, then forget. You have no way to follow up.</p>
</div>
<div className="bg-white rounded-2xl p-6 border">
<div className="text-3xl mb-3">📝</div>
<h3 className="font-bold mb-1">Paper tracking</h3>
<p className="text-sm text-muted-foreground">Spreadsheets, napkin notes, WhatsApp groups. No system, no references, no proof.</p>
<h3 className="font-bold mb-1">No follow-up system</h3>
<p className="text-sm text-muted-foreground">Someone pledges £5,000 at your dinner. You write it on a napkin. A week later who pledged what? No idea.</p>
</div>
<div className="bg-white rounded-2xl p-6 border">
<div className="text-3xl mb-3">💸</div>
<h3 className="font-bold mb-1">Money left on the table</h3>
<p className="text-sm text-muted-foreground">UK charities lose 30-50% of pledged amounts because there&apos;s no follow-up system.</p>
<p className="text-sm text-muted-foreground">Donors meant it when they said it. But without a reminder and easy payment path, life gets in the way.</p>
</div>
<div className="bg-white rounded-2xl p-6 border">
<div className="text-3xl mb-3">🕌</div>
<h3 className="font-bold mb-1">Funds mixed up</h3>
<p className="text-sm text-muted-foreground">Zakat mixed with Sadaqah. Building fund mixed with general. No audit trail for fund allocation.</p>
</div>
</div>
</div>
</section>
{/* How it works */}
<section id="how" className="py-16 px-4">
<section className="py-14 bg-gray-50 px-4">
<div className="max-w-4xl mx-auto space-y-10">
<div className="text-center">
<h2 className="text-3xl font-black text-gray-900">How it works</h2>
<p className="text-muted-foreground mt-2">From pledge to payment in 4 steps</p>
<p className="text-muted-foreground mt-2">Whether it&apos;s a QR code at a gala or a link in a WhatsApp group</p>
</div>
<div className="grid md:grid-cols-4 gap-6">
{[
{ step: "1", icon: "📱", title: "Donor scans QR", desc: "At your event, each table/volunteer has a unique QR code." },
{ step: "2", icon: "🤲", title: "Pledges amount", desc: "Pick an amount. Choose to pay now, on a date, or monthly instalments." },
{ step: "3", icon: "💬", title: "Gets reminders", desc: "WhatsApp messages with bank details before each due date. They reply PAID when done." },
{ step: "4", icon: "", title: "You reconcile", desc: "Dashboard shows who pledged, who paid, who needs a nudge. Upload bank statements to auto-match." },
{ step: "1", icon: "🔗", title: "Share a pledge link", desc: "Create a trackable link. Share via QR code, WhatsApp, social media, email, or send directly to a major donor." },
{ step: "2", icon: "🤲", title: "Donor pledges", desc: "They pick an amount, choose Zakat/Sadaqah, and decide: pay now, on a date, or in monthly instalments." },
{ step: "3", icon: "💬", title: "WhatsApp follows up", desc: "Automated reminders with bank details or a link to your fundraising page. They reply PAID when done." },
{ step: "4", icon: "📊", title: "You see everything", desc: "Live dashboard: who pledged, who paid, what fund, which source. Export for HMRC Gift Aid." },
].map((s) => (
<div key={s.step} className="text-center space-y-2">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-trust-blue/5 text-2xl">
{s.icon}
</div>
<div className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-trust-blue text-white text-xs font-bold">
{s.step}
</div>
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-trust-blue/5 text-2xl">{s.icon}</div>
<div className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-trust-blue text-white text-xs font-bold">{s.step}</div>
<h3 className="font-bold text-sm">{s.title}</h3>
<p className="text-xs text-muted-foreground">{s.desc}</p>
</div>
@@ -101,25 +176,101 @@ export default function HomePage() {
</div>
</section>
{/* Sharing channels */}
<section className="py-14 px-4">
<div className="max-w-3xl mx-auto text-center space-y-8">
<h2 className="text-3xl font-black text-gray-900">Share anywhere. Track everything.</h2>
<p className="text-muted-foreground">Every link is unique and trackable. See exactly where each pledge came from.</p>
<div className="grid grid-cols-3 md:grid-cols-6 gap-3">
{[
{ icon: "💬", label: "WhatsApp" },
{ icon: "📱", label: "QR Code" },
{ icon: "📧", label: "Email" },
{ icon: "📸", label: "Instagram" },
{ icon: "🐦", label: "Twitter/X" },
{ icon: "👤", label: "1-on-1" },
].map((c) => (
<div key={c.label} className="rounded-xl border bg-white p-3 text-center space-y-1">
<span className="text-xl">{c.icon}</span>
<p className="text-[11px] font-medium">{c.label}</p>
</div>
))}
</div>
</div>
</section>
{/* Platforms */}
<section className="py-14 bg-gray-50 px-4">
<div className="max-w-3xl mx-auto text-center space-y-8">
<h2 className="text-3xl font-black text-gray-900">Works with your payment platform</h2>
<p className="text-muted-foreground">Process donations directly, or redirect donors to your existing page.</p>
<div className="flex flex-wrap justify-center gap-3">
{[
{ name: "Bank Transfer (UK)", icon: "🏦", color: "#1e40af" },
{ name: "LaunchGood", icon: "🌙", color: "#00C389" },
{ name: "Enthuse", icon: "💜", color: "#6B4FBB" },
{ name: "JustGiving", icon: "💛", color: "#AD29B6" },
{ name: "GoFundMe", icon: "💚", color: "#00B964" },
{ name: "Any URL", icon: "🔗", color: "#6b7280" },
].map((p) => (
<div key={p.name} className="flex items-center gap-2 rounded-xl border px-4 py-3 bg-white" style={{ borderColor: p.color + "30" }}>
<span className="text-lg">{p.icon}</span>
<span className="text-sm font-medium">{p.name}</span>
</div>
))}
</div>
</div>
</section>
{/* Fund Types */}
<section className="py-14 px-4">
<div className="max-w-4xl mx-auto space-y-8">
<div className="text-center">
<h2 className="text-3xl font-black text-gray-900">Islamic fund types built-in</h2>
<p className="text-muted-foreground mt-2">Donors choose their fund type. You get clean reporting. Zakat never mixes with Sadaqah.</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
{[
{ name: "Zakat", icon: "🌙", desc: "Obligatory 2.5%" },
{ name: "Sadaqah", icon: "🤲", desc: "Voluntary" },
{ name: "Sadaqah Jariyah", icon: "🌱", desc: "Ongoing" },
{ name: "Lillah", icon: "🕌", desc: "For institution" },
{ name: "Fitrana", icon: "🍽️", desc: "Before Eid" },
].map((f) => (
<div key={f.name} className="rounded-2xl border bg-white p-4 text-center space-y-1">
<span className="text-2xl">{f.icon}</span>
<p className="text-sm font-bold">{f.name}</p>
<p className="text-[11px] text-muted-foreground">{f.desc}</p>
</div>
))}
</div>
<p className="text-center text-xs text-muted-foreground">
Enable in Settings Fund Types. Donors see the picker during pledge. Reports break down by type.
</p>
</div>
</section>
{/* Features */}
<section className="py-16 bg-gray-50 px-4">
<section className="py-14 bg-gray-50 px-4">
<div className="max-w-4xl mx-auto space-y-10">
<div className="text-center">
<h2 className="text-3xl font-black text-gray-900">Everything you need</h2>
</div>
<div className="grid md:grid-cols-2 gap-4">
{[
{ icon: "📱", title: "QR Code Generator", desc: "Unique codes per volunteer/table. Track who brings in the most." },
{ icon: "📅", title: "Flexible Scheduling", desc: "Pay now, pick a date, or split into 2-12 monthly instalments." },
{ icon: "💬", title: "WhatsApp Reminders", desc: "Auto-send bank details and reminders. Donors reply PAID, HELP, or CANCEL." },
{ icon: "🎁", title: "Gift Aid", desc: "Collect declarations inline. Export HMRC-ready CSV with one click." },
{ icon: "🏦", title: "UK Bank Transfers", desc: "Unique reference per pledge for easy reconciliation. Tap-to-copy details." },
{ icon: "📊", title: "Live Dashboard", desc: "See pledges come in real-time. Pipeline view: pending → initiated → paid." },
{ icon: "🏆", title: "Volunteer Leaderboard", desc: "Real-time scoreboard. Motivate your team with friendly competition." },
{ icon: "📤", title: "CRM Export", desc: "Download all pledge data as CSV. Gift Aid pack for HMRC." },
{ icon: "🔗", title: "Trackable Pledge Links", desc: "Create unique links per source — WhatsApp group, social post, email, volunteer, table. See where pledges come from." },
{ icon: "📅", title: "Flexible Scheduling", desc: "Pay now, pick a date, or split into 2-12 monthly instalments. Each instalment tracked separately." },
{ icon: "💬", title: "WhatsApp Reminders", desc: "Automated multi-step: 2 days before → due day → gentle nudge → final. Donors reply PAID, HELP, STATUS." },
{ icon: "🎁", title: "Gift Aid + HMRC Export", desc: "Collect declarations inline with live math. One-click HMRC-ready CSV export." },
{ icon: "☪️", title: "Fund Type Tracking", desc: "Zakat, Sadaqah, Lillah, Fitrana. Clean fund-level reporting. Optional — enable when you need it." },
{ icon: "📊", title: "Live Dashboard", desc: "Real-time pipeline: new → initiated → paid → overdue. Needs-attention alerts. Auto-refreshes." },
{ icon: "🏦", title: "Fund Allocation", desc: "Link campaigns to specific funds. Charities can also link external fundraising pages for allocation tracking." },
{ icon: "🏆", title: "Leaderboard", desc: "See which volunteer, table, or link source brings in the most pledges. Friendly competition." },
{ icon: "📱", title: "QR Codes for Events", desc: "Print a QR code per table. Works alongside WhatsApp sharing, social posts, and direct links." },
{ icon: "📤", title: "CRM Export", desc: "Download all pledge data as CSV. Filter by fund type, campaign, status, or source." },
].map((f) => (
<div key={f.title} className="bg-white rounded-xl p-4 border flex gap-3 items-start">
<span className="text-xl">{f.icon}</span>
<div key={f.title} className="bg-white rounded-xl p-4 border flex gap-3 items-start hover:shadow-sm transition-shadow">
<span className="text-xl flex-shrink-0">{f.icon}</span>
<div>
<h3 className="font-bold text-sm">{f.title}</h3>
<p className="text-xs text-muted-foreground mt-0.5">{f.desc}</p>
@@ -131,24 +282,24 @@ export default function HomePage() {
</section>
{/* Donor schedule */}
<section className="py-16 px-4">
<section className="py-14 px-4">
<div className="max-w-3xl mx-auto text-center space-y-8">
<h2 className="text-3xl font-black text-gray-900">Donors choose when to pay</h2>
<div className="grid grid-cols-3 gap-4">
<div className="rounded-2xl border-2 border-trust-blue/20 p-5 space-y-2">
<div className="rounded-2xl border-2 border-trust-blue/20 p-5 space-y-2 bg-white">
<div className="text-3xl"></div>
<h3 className="font-bold">Pay Now</h3>
<p className="text-xs text-muted-foreground">Card, bank transfer, or Direct Debit right away</p>
<p className="text-xs text-muted-foreground">Bank transfer or redirect to your fundraising page</p>
</div>
<div className="rounded-2xl border-2 border-warm-amber/20 p-5 space-y-2">
<div className="rounded-2xl border-2 border-warm-amber/20 p-5 space-y-2 bg-white">
<div className="text-3xl">📅</div>
<h3 className="font-bold">Pick a Date</h3>
<p className="text-xs text-muted-foreground">&quot;I&apos;ll pay on payday&quot; reminders sent automatically</p>
<p className="text-xs text-muted-foreground">&quot;I&apos;ll pay on payday&quot; WhatsApp reminders sent automatically</p>
</div>
<div className="rounded-2xl border-2 border-success-green/20 p-5 space-y-2">
<div className="rounded-2xl border-2 border-success-green/20 p-5 space-y-2 bg-white">
<div className="text-3xl">📆</div>
<h3 className="font-bold">Monthly</h3>
<p className="text-xs text-muted-foreground">Split into 2-12 instalments. Each one tracked separately</p>
<p className="text-xs text-muted-foreground">Split into 2-12 instalments. Each one tracked &amp; reminded</p>
</div>
</div>
</div>
@@ -157,14 +308,18 @@ export default function HomePage() {
{/* CTA */}
<section className="py-16 bg-gradient-to-br from-trust-blue to-blue-600 px-4">
<div className="max-w-2xl mx-auto text-center space-y-6">
<h2 className="text-3xl font-black text-white">Start collecting pledges today</h2>
<p className="text-blue-100">
Free to use. Set up in 2 minutes. No technical knowledge needed.
<h2 className="text-3xl font-black text-white">Stop losing pledges.</h2>
<p className="text-blue-100 max-w-lg mx-auto">
Free to use. Set up in 2 minutes. Whether you&apos;re collecting pledges at a gala dinner, from your WhatsApp contacts, or from other organisations for a joint project.
</p>
<Link href="/signup" className="inline-block rounded-xl bg-white px-8 py-4 text-base font-bold text-trust-blue hover:bg-blue-50 transition-all shadow-xl">
Create Your Free Account
</Link>
<p className="text-xs text-blue-200">Used by mosques, churches, schools and charities across the UK</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<Link href="/signup" className="inline-block rounded-xl bg-white px-8 py-4 text-base font-bold text-trust-blue hover:bg-blue-50 transition-all shadow-xl">
Create Your Free Account
</Link>
<Link href="/login?demo=1" className="inline-block rounded-xl border-2 border-white/30 px-8 py-4 text-base font-bold text-white hover:bg-white/10 transition-all">
🎮 Try the Demo
</Link>
</div>
</div>
</section>
@@ -172,14 +327,13 @@ export default function HomePage() {
<footer className="py-8 px-4 border-t">
<div className="max-w-4xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4 text-xs text-muted-foreground">
<div className="flex items-center gap-2">
<div className="h-6 w-6 rounded-lg bg-trust-blue flex items-center justify-center">
<span className="text-white text-[10px]">🤲</span>
</div>
<div className="h-6 w-6 rounded-lg bg-trust-blue flex items-center justify-center"><span className="text-white text-[10px]">🤲</span></div>
<span>Pledge Now, Pay Later</span>
</div>
<div className="flex gap-4">
<Link href="/login" className="hover:text-foreground">Sign In</Link>
<Link href="/signup" className="hover:text-foreground">Get Started</Link>
<Link href="/login?demo=1" className="hover:text-foreground">Demo</Link>
</div>
<span>© {new Date().getFullYear()} QuikCue Ltd</span>
</div>