feat: conditional & match funding pledges — deeply integrated across entire product
- Schema: isConditional, conditionType, conditionText, conditionThreshold, conditionMet, conditionMetAt on Pledge - Pledge form: 'This is a match pledge' toggle after amount selection - Two modes: threshold (if target is reached) and match (match funding) - Goal amount passed through from event - Auto-trigger: when total raised hits threshold, conditional pledges unlock automatically - WhatsApp notification sent to donor when unlocked - Threshold check runs after every pledge creation AND every status change - Cron: skips conditional pledges until conditionMet=true (no premature reminders) - Dashboard Home: progress bar shows conditional segment (amber), stats grid adds Conditional column - Dashboard Money: conditional/unlocked badge on pledge rows - Dashboard Collect: hero shows conditional total in amber - Dashboard Reports: financial summary shows conditional breakdown - Donor 'My Pledges': conditional card with condition text + activation status - Confirmation step: specialized messaging for match pledges - CRM export: includes is_conditional, condition_type, condition_text, condition_met columns - Status guide: conditional status explained in human language
This commit is contained in:
@@ -33,6 +33,11 @@ export interface PledgeData {
|
||||
dueDate?: string
|
||||
installmentCount?: number
|
||||
installmentDates?: string[]
|
||||
// Conditional / match funding
|
||||
isConditional: boolean
|
||||
conditionType?: "threshold" | "match" | "custom"
|
||||
conditionText?: string
|
||||
conditionThreshold?: number
|
||||
}
|
||||
|
||||
interface EventInfo {
|
||||
@@ -46,6 +51,7 @@ interface EventInfo {
|
||||
externalPlatform: string | null
|
||||
zakatEligible: boolean
|
||||
hasStripe: boolean
|
||||
goalAmount: number | null
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -78,6 +84,7 @@ export default function PledgePage() {
|
||||
emailOptIn: false,
|
||||
whatsappOptIn: false,
|
||||
scheduleMode: "now",
|
||||
isConditional: false,
|
||||
})
|
||||
const [pledgeResult, setPledgeResult] = useState<{
|
||||
id: string
|
||||
@@ -106,14 +113,14 @@ export default function PledgePage() {
|
||||
const isExternal = eventInfo?.paymentMode === "external" && eventInfo?.externalUrl
|
||||
|
||||
// Step 0: Amount selected
|
||||
const handleAmountSelected = (amountPence: number) => {
|
||||
setPledgeData((d) => ({ ...d, amountPence }))
|
||||
const handleAmountSelected = (amountPence: number, conditional?: { isConditional: boolean; conditionType?: "threshold" | "match" | "custom"; conditionText?: string; conditionThreshold?: number }) => {
|
||||
const conditionalData = conditional || { isConditional: false }
|
||||
setPledgeData((d) => ({ ...d, amountPence, ...conditionalData }))
|
||||
if (isExternal) {
|
||||
// External events: amount → identity → redirect (skip schedule + payment method)
|
||||
setPledgeData((d) => ({ ...d, amountPence, rail: "bank", scheduleMode: "now" }))
|
||||
setStep(3) // → Identity
|
||||
setPledgeData((d) => ({ ...d, amountPence, rail: "bank", scheduleMode: "now", ...conditionalData }))
|
||||
setStep(3)
|
||||
} else {
|
||||
setStep(1) // → Schedule step
|
||||
setStep(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +232,7 @@ export default function PledgePage() {
|
||||
: undefined
|
||||
|
||||
const steps: Record<number, React.ReactNode> = {
|
||||
0: <AmountStep onSelect={handleAmountSelected} eventName={eventInfo?.name || ""} eventId={eventInfo?.id} />,
|
||||
0: <AmountStep onSelect={handleAmountSelected} eventName={eventInfo?.name || ""} eventId={eventInfo?.id} goalAmount={eventInfo?.goalAmount} />,
|
||||
1: <ScheduleStep amount={pledgeData.amountPence} onSelect={handleScheduleSelected} />,
|
||||
2: <PaymentStep onSelect={handleRailSelected} amount={pledgeData.amountPence} hasStripe={eventInfo?.hasStripe ?? false} />,
|
||||
3: <IdentityStep onSubmit={submitPledge} amount={pledgeData.amountPence} zakatEligible={eventInfo?.zakatEligible} orgName={eventInfo?.organizationName} />,
|
||||
@@ -242,6 +249,8 @@ export default function PledgePage() {
|
||||
dueDateLabel={dueDateLabel}
|
||||
installmentCount={pledgeData.installmentCount}
|
||||
installmentAmount={pledgeData.installmentCount ? Math.ceil(pledgeData.amountPence / pledgeData.installmentCount) : undefined}
|
||||
isConditional={pledgeData.isConditional}
|
||||
conditionText={pledgeData.conditionText}
|
||||
/>
|
||||
),
|
||||
6: <CardPaymentStep amount={pledgeData.amountPence} eventName={eventInfo?.name || ""} eventId={eventInfo?.id || ""} qrSourceId={eventInfo?.qrSourceId || null} onComplete={submitPledge} />,
|
||||
|
||||
Reference in New Issue
Block a user