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

@@ -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 },