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

@@ -2,7 +2,8 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LayoutDashboard, Calendar, FileBarChart, Upload, Download, Settings, Plus, ExternalLink } from "lucide-react"
import { useSession, signOut } from "next-auth/react"
import { LayoutDashboard, Calendar, FileBarChart, Upload, Download, Settings, Plus, ExternalLink, LogOut } from "lucide-react"
import { cn } from "@/lib/utils"
const navItems = [
@@ -16,6 +17,9 @@ const navItems = [
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const { data: session } = useSession()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user = session?.user as any
return (
<div className="min-h-screen bg-gray-50/50">
@@ -27,8 +31,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
<span className="text-white font-bold text-sm">P</span>
</div>
<div className="hidden sm:block">
<span className="font-black text-sm text-gray-900">PNPL</span>
<span className="text-[10px] text-muted-foreground ml-1">Dashboard</span>
<span className="font-black text-sm text-gray-900">{user?.orgName || "PNPL"}</span>
</div>
</Link>
<div className="flex-1" />
@@ -38,8 +41,16 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</button>
</Link>
<Link href="/" className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1">
<ExternalLink className="h-3 w-3" /> <span className="hidden sm:inline">Public Site</span>
<ExternalLink className="h-3 w-3" />
</Link>
{session && (
<button
onClick={() => signOut({ callbackUrl: "/login" })}
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
>
<LogOut className="h-3 w-3" />
</button>
)}
</div>
</header>