production: reminder cron, dashboard overhaul, shadcn components, setup wizard

- /api/cron/reminders: processes pending reminders every 15min, sends WhatsApp with email fallback
- /api/cron/overdue: marks overdue pledges daily (7d deferred, 14d immediate)
- /api/pledges: GET handler with filtering, search, pagination, sort by dueDate
- Dashboard overview: stats, collection progress bar, needs attention, upcoming payments
- Dashboard pledges: proper table with status tabs, search, actions, pagination
- New shadcn components: Table, Tabs, DropdownMenu, Progress
- Setup wizard: 4-step onboarding (org → bank → event → QR code)
- Settings API: PUT handler for org create/update
- Org resolver: single-tenant fallback to first org
- Cron jobs installed: reminders every 15min, overdue check at 6am
- Auto-generates installment dates when not provided
- HOSTNAME=0.0.0.0 in compose for multi-network binding
This commit is contained in:
2026-03-03 05:11:17 +08:00
parent 250221b530
commit c79b9bcabc
61 changed files with 3547 additions and 534 deletions

View File

@@ -32,6 +32,47 @@ export async function GET(request: NextRequest) {
}
}
export async function PUT(request: NextRequest) {
try {
if (!prisma) return NextResponse.json({ error: "DB not configured" }, { status: 503 })
const body = await request.json()
// Try to find existing org first
const orgId = await resolveOrgId(request.headers.get("x-org-id") || "default")
if (orgId) {
// Update existing
const allowed = ["name", "charityNumber", "bankName", "bankSortCode", "bankAccountNo", "bankAccountName", "refPrefix", "primaryColor", "logo"]
const data: Record<string, string> = {}
for (const key of allowed) {
if (key in body && body[key] !== undefined) data[key] = body[key]
}
const org = await prisma.organization.update({ where: { id: orgId }, data })
return NextResponse.json({ id: org.id, name: org.name, created: false })
} else {
// Create new org
const slug = (body.name || "org").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")
const org = await prisma.organization.create({
data: {
name: body.name || "My Charity",
slug: slug || "my-charity",
country: "GB",
bankName: body.bankName || "",
bankSortCode: body.bankSortCode || "",
bankAccountNo: body.bankAccountNo || "",
bankAccountName: body.bankAccountName || body.name || "",
refPrefix: slug.substring(0, 4).toUpperCase() || "PNPL",
},
})
return NextResponse.json({ id: org.id, name: org.name, created: true })
}
} catch (error) {
console.error("Settings PUT error:", error)
return NextResponse.json({ error: "Internal error" }, { status: 500 })
}
}
export async function PATCH(request: NextRequest) {
try {
if (!prisma) return NextResponse.json({ error: "DB not configured" }, { status: 503 })