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:
@@ -84,7 +84,12 @@ export async function GET(request: NextRequest) {
|
||||
}),
|
||||
]) as [PledgeRow[], AnalyticsRow[]]
|
||||
|
||||
const totalPledged = pledges.reduce((s: number, p: PledgeRow) => s + p.amountPence, 0)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const confirmedPledges = pledges.filter((p: any) => !p.isConditional || p.conditionMet)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const conditionalPledges = pledges.filter((p: any) => p.isConditional && !p.conditionMet)
|
||||
const totalPledged = confirmedPledges.reduce((s: number, p: PledgeRow) => s + p.amountPence, 0)
|
||||
const totalConditional = conditionalPledges.reduce((s: number, p: PledgeRow) => s + p.amountPence, 0)
|
||||
const totalCollected = pledges
|
||||
.filter((p: PledgeRow) => p.status === "paid")
|
||||
.reduce((s: number, p: PledgeRow) => s + p.amountPence, 0)
|
||||
@@ -123,6 +128,8 @@ export async function GET(request: NextRequest) {
|
||||
totalPledges: pledges.length,
|
||||
totalPledgedPence: totalPledged,
|
||||
totalCollectedPence: totalCollected,
|
||||
totalConditionalPence: totalConditional,
|
||||
conditionalCount: conditionalPledges.length,
|
||||
collectionRate: Math.round(collectionRate * 100),
|
||||
overdueRate: Math.round(overdueRate * 100),
|
||||
},
|
||||
@@ -148,6 +155,12 @@ export async function GET(request: NextRequest) {
|
||||
installmentNumber: p.installmentNumber,
|
||||
installmentTotal: p.installmentTotal,
|
||||
isDeferred: !!p.dueDate,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
isConditional: (p as any).isConditional || false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
conditionText: (p as any).conditionText || null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
conditionMet: (p as any).conditionMet || false,
|
||||
createdAt: p.createdAt,
|
||||
paidAt: p.paidAt,
|
||||
nextReminder: p.reminders
|
||||
|
||||
Reference in New Issue
Block a user