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:
@@ -31,7 +31,7 @@ export default function SettingsPage() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/settings", { headers: { "x-org-id": "demo" } })
|
||||
fetch("/api/settings")
|
||||
.then((r) => r.json())
|
||||
.then((data) => { if (data.name) setSettings(data) })
|
||||
.catch(() => setError("Failed to load settings"))
|
||||
@@ -44,7 +44,7 @@ export default function SettingsPage() {
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json", "x-org-id": "demo" },
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
if (res.ok) { setSaved(section); setTimeout(() => setSaved(null), 2000) }
|
||||
@@ -150,7 +150,7 @@ function WhatsAppPanel() {
|
||||
const res = await fetch("/api/whatsapp/qr")
|
||||
const data = await res.json()
|
||||
setStatus(data.status)
|
||||
if (data.qrImage) setQrImage(data.qrImage)
|
||||
if (data.screenshot) setQrImage(data.screenshot)
|
||||
if (data.phone) setPhone(data.phone)
|
||||
if (data.pushName) setPushName(data.pushName)
|
||||
} catch {
|
||||
@@ -231,23 +231,33 @@ function WhatsAppPanel() {
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{qrImage ? (
|
||||
<div className="relative">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={qrImage} alt="WhatsApp QR Code" className="w-64 h-64 rounded-xl border-2 border-[#25D366]/20" />
|
||||
{/* Crop to QR area: the screenshot shows full WhatsApp web page.
|
||||
QR code is roughly in center. We use overflow hidden + object positioning. */}
|
||||
<div className="w-72 h-72 rounded-xl border-2 border-[#25D366]/20 overflow-hidden bg-white">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={qrImage}
|
||||
alt="WhatsApp QR Code"
|
||||
className="w-[200%] h-auto max-w-none"
|
||||
style={{ marginLeft: "-30%", marginTop: "-35%" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute -bottom-2 -right-2 w-8 h-8 rounded-full bg-[#25D366] flex items-center justify-center shadow-lg">
|
||||
<MessageCircle className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-64 h-64 rounded-xl border-2 border-dashed border-muted flex items-center justify-center">
|
||||
<div className="w-72 h-72 rounded-xl border-2 border-dashed border-muted flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 text-muted-foreground animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center space-y-1">
|
||||
<p className="text-sm font-medium">Scan with WhatsApp</p>
|
||||
<p className="text-xs text-muted-foreground">QR refreshes automatically every 5 seconds</p>
|
||||
<p className="text-xs text-muted-foreground">Open WhatsApp → Settings → Linked Devices → Link a Device</p>
|
||||
<p className="text-xs text-muted-foreground">Auto-refreshes every 5 seconds</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={checkStatus} className="gap-1.5">
|
||||
<RefreshCw className="h-3 w-3" /> Refresh QR
|
||||
<RefreshCw className="h-3 w-3" /> Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user