simplify: zakat yes/no per campaign, remove 5 fund types, add I've Donated button for external pledges

- Event.zakatEligible (boolean) replaces Organization.zakatEnabled + 5 fund types
- Pledge.isZakat (boolean) replaces Pledge.fundType enum
- Removed fundAllocation from Event (campaign IS the allocation)
- Identity step: simple checkbox instead of 5-option grid
- Campaign creation: Zakat toggle + optional external URL for self-payment
- External redirect step: 'I've Donated' button calls /api/pledges/[id]/mark-initiated
- Landing page: simplified Zakat section (toggle preview, not 5 fund descriptions)
- Settings: removed org-level Zakat toggle (it's per campaign now)
- Migration: ALTER TABLE adds zakatEligible/isZakat, drops fundAllocation
This commit is contained in:
2026-03-03 07:19:52 +08:00
parent f87aec7beb
commit fc80a43a89
14 changed files with 131 additions and 179 deletions

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: "", fundAllocation: "" })
const [form, setForm] = useState({ name: "", description: "", location: "", eventDate: "", goalAmount: "", paymentMode: "self" as "self" | "external", externalUrl: "", externalPlatform: "", zakatEligible: false })
// Fetch org type to customize the form
useEffect(() => {
@@ -79,14 +79,14 @@ export default function EventsPage() {
paymentMode: form.paymentMode,
externalUrl: form.externalUrl || undefined,
externalPlatform: form.paymentMode === "external" ? (form.externalPlatform || "other") : undefined,
fundAllocation: form.fundAllocation || undefined,
zakatEligible: form.zakatEligible,
}),
})
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: "", fundAllocation: "" })
setForm({ name: "", description: "", location: "", eventDate: "", goalAmount: "", paymentMode: orgType === "fundraiser" ? "external" : "self", externalUrl: "", externalPlatform: "", zakatEligible: false })
}
} catch {
// handle error
@@ -298,29 +298,37 @@ export default function EventsPage() {
</div>
)}
{/* Fund allocation — for charities tracking which fund this goes to */}
{/* Zakat eligible toggle */}
<button
type="button"
onClick={() => setForm(f => ({ ...f, zakatEligible: !f.zakatEligible }))}
className={`w-full flex items-center justify-between rounded-xl border-2 p-3 text-left transition-all ${
form.zakatEligible ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"
}`}
>
<div>
<p className="text-sm font-bold">🌙 Zakat eligible</p>
<p className="text-xs text-muted-foreground">Let donors mark their pledge as Zakat</p>
</div>
<div className={`w-5 h-5 rounded border-2 flex items-center justify-center ${form.zakatEligible ? "bg-trust-blue border-trust-blue" : "border-gray-300"}`}>
{form.zakatEligible && <span className="text-white text-xs font-bold"></span>}
</div>
</button>
{/* External URL for self-payment campaigns (for reference/allocation) */}
{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>
<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/my-campaign"
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 page for reference. Payments still go via your bank.</p>
</div>
)}

View File

@@ -22,7 +22,6 @@ interface OrgSettings {
gcAccessToken: string
gcEnvironment: string
orgType: string
zakatEnabled: boolean
}
export default function SettingsPage() {
@@ -118,42 +117,6 @@ 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>