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:
@@ -76,3 +76,7 @@ ssh root@157.245.43.50
|
||||
| `GOOGLE_PLACES_API_KEY` | Google Places autocomplete |
|
||||
| `CT_STRAVA_*` | Strava challenge tracker |
|
||||
| `WORDPRESS_URL`, `WORDPRESS_KEY` | WordPress (Cloudways) |
|
||||
|
||||
## CharityRight n8n
|
||||
- **URL**: https://n8n.charityright.org.uk
|
||||
- **API Key**: stored in .env as N8N_CR_API_KEY
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Zakat / fund type tracking
|
||||
ALTER TABLE "Organization" ADD COLUMN IF NOT EXISTS "zakatEnabled" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "Pledge" ADD COLUMN IF NOT EXISTS "fundType" TEXT;
|
||||
ALTER TABLE "Event" ADD COLUMN IF NOT EXISTS "fundAllocation" TEXT;
|
||||
@@ -24,6 +24,7 @@ model Organization {
|
||||
gcAccessToken String?
|
||||
gcEnvironment String @default("sandbox")
|
||||
whatsappConnected Boolean @default(false)
|
||||
zakatEnabled Boolean @default(false) // enables Zakat / Sadaqah / Lillah fund type picker
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -62,6 +63,7 @@ model Event {
|
||||
paymentMode String @default("self") // self = we show bank details, external = redirect to URL
|
||||
externalUrl String? // e.g. https://launchgood.com/my-campaign
|
||||
externalPlatform String? // launchgood, enthuse, justgiving, gofundme, other
|
||||
fundAllocation String? // e.g. "Mosque Building Fund" — tracks which fund this event raises for
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
@@ -102,6 +104,7 @@ model Pledge {
|
||||
donorEmail String?
|
||||
donorPhone String?
|
||||
giftAid Boolean @default(false)
|
||||
fundType String? // null=general, zakat, sadaqah, lillah, fitrana
|
||||
iPaidClickedAt DateTime?
|
||||
notes String?
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 & 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 "I'll donate £5,000" — 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 & 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'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 "I'll pay £500" 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'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'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">"I'll pay on payday" — reminders sent automatically</p>
|
||||
<p className="text-xs text-muted-foreground">"I'll pay on payday" — 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 & 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'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>
|
||||
|
||||
@@ -10,6 +10,7 @@ export const createEventSchema = z.object({
|
||||
paymentMode: z.enum(['self', 'external']).default('self'),
|
||||
externalUrl: z.string().url().max(1000).optional(),
|
||||
externalPlatform: z.enum(['launchgood', 'enthuse', 'justgiving', 'gofundme', 'other']).optional(),
|
||||
fundAllocation: z.string().max(200).optional(),
|
||||
})
|
||||
|
||||
export const createQrSourceSchema = z.object({
|
||||
@@ -25,6 +26,7 @@ export const createPledgeSchema = z.object({
|
||||
donorEmail: z.string().max(200).optional().default(''),
|
||||
donorPhone: z.string().max(20).optional().default(''),
|
||||
giftAid: z.boolean().default(false),
|
||||
fundType: z.enum(['general', 'zakat', 'sadaqah', 'lillah', 'fitrana']).optional(),
|
||||
eventId: z.string(),
|
||||
qrSourceId: z.string().nullable().optional(),
|
||||
// Payment scheduling
|
||||
|
||||
Reference in New Issue
Block a user