production auth: signup, login, protected dashboard, landing page, WAHA QR fix

AUTH:
- NextAuth with credentials provider (bcrypt password hashing)
- /api/auth/signup: creates org + user in transaction
- /login, /signup pages with clean minimal UI
- Middleware protects all /dashboard/* routes → redirects to /login
- Session-based org resolution (no more hardcoded 'demo' headers)
- SessionProvider wraps entire app
- Dashboard header shows org name + sign out button

LANDING PAGE:
- Full marketing page at / with hero, problem, how-it-works, features, CTA
- 'Get Started Free' → /signup → auto-login → /dashboard/setup
- Clean responsive design, no auth required for public pages

WAHA QR FIX:
- WAHA CORE doesn't expose QR value via API or webhook
- Now uses /api/screenshot (full browser capture) with CSS crop to QR area
- Settings panel shows cropped screenshot with overflow:hidden
- Auto-polls every 5s, refresh button

MULTI-TENANT:
- getOrgId() tries session first, then header, then first-org fallback
- All dashboard APIs use session-based org
- Signup creates isolated org per charity
This commit is contained in:
2026-03-03 05:37:04 +08:00
parent 6894f091fd
commit 4f23f28873
22 changed files with 708 additions and 221 deletions

View File

@@ -1,15 +1,29 @@
import { NextRequest, NextResponse } from "next/server"
import prisma from "@/lib/prisma"
import { sendWhatsAppMessage } from "@/lib/whatsapp"
import { setQrValue } from "@/lib/qr-store"
/**
* WAHA webhook — receives incoming WhatsApp messages
* WAHA webhook — receives incoming WhatsApp messages + session status events
* Handles: PAID, HELP, CANCEL commands from donors
* Also captures QR code value for the pairing flow
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { payload } = body
const { event, payload } = body
// Handle session.status events (for QR code capture)
if (event === "session.status") {
console.log("[WAHA] Session status:", JSON.stringify({ status: payload?.status, hasQr: !!payload?.qr, keys: Object.keys(payload || {}) }))
// WAHA sends QR in different formats depending on version
const qrVal = payload?.qr?.value || payload?.qr || payload?.qrCode
if (payload?.status === "SCAN_QR_CODE" && qrVal && typeof qrVal === "string") {
setQrValue(qrVal)
console.log("[WAHA] QR value stored, length:", qrVal.length)
}
return NextResponse.json({ ok: true })
}
// WAHA sends message events
if (!payload?.body || !payload?.from) {