demo login, super admin view, password reset

- Landing page: 'Try the Demo' button links to /login?demo=1
- Login page: 'Try the Demo — no signup needed' button auto-logs in as demo@pnpl.app
- /login?demo=1: auto-triggers demo login on page load
- Super Admin page (/dashboard/admin): platform stats, org list, user list, recent pledges
- /api/admin: returns cross-org data, gated by super_admin role check
- Sidebar shows 'Super Admin' link only for super_admin users
- Password reset: omair@quikcue.com = Omair2026!, demo@pnpl.app = demo1234
- omair@quikcue.com confirmed as super_admin role
This commit is contained in:
2026-03-03 05:55:27 +08:00
parent 5f111d1808
commit 12ea9691c4
5 changed files with 378 additions and 11 deletions

View File

@@ -1,25 +1,27 @@
"use client"
import { useState } from "react"
import { useState, useEffect, Suspense } from "react"
import { signIn } from "next-auth/react"
import { useRouter } from "next/navigation"
import { useRouter, useSearchParams } from "next/navigation"
import Link from "next/link"
export default function LoginPage() {
function LoginForm() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const router = useRouter()
const searchParams = useSearchParams()
const isDemo = searchParams.get("demo") === "1"
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const doLogin = async (e?: React.FormEvent, demoEmail?: string, demoPass?: string) => {
if (e) e.preventDefault()
setError("")
setLoading(true)
const result = await signIn("credentials", {
email,
password,
email: demoEmail || email,
password: demoPass || password,
redirect: false,
})
@@ -31,6 +33,12 @@ export default function LoginPage() {
}
}
const handleSubmit = (e: React.FormEvent) => doLogin(e)
// Auto-login as demo if ?demo=1
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => { if (isDemo) doLogin(undefined, "demo@pnpl.app", "demo1234") }, [])
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4">
<div className="w-full max-w-sm space-y-6">
@@ -82,6 +90,20 @@ export default function LoginPage() {
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div>
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-2 text-muted-foreground">or</span></div>
</div>
<button
type="button"
onClick={() => doLogin(undefined, "demo@pnpl.app", "demo1234")}
disabled={loading}
className="w-full rounded-xl border-2 border-dashed border-gray-200 px-4 py-3 text-sm font-medium text-muted-foreground hover:border-trust-blue hover:text-trust-blue disabled:opacity-50 transition-all"
>
🎮 Try the Demo no signup needed
</button>
<p className="text-center text-sm text-muted-foreground">
Don&apos;t have an account?{" "}
<Link href="/signup" className="text-trust-blue font-semibold hover:underline">
@@ -92,3 +114,11 @@ export default function LoginPage() {
</div>
)
}
export default function LoginPage() {
return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center"><div className="animate-spin h-8 w-8 border-2 border-trust-blue border-t-transparent rounded-full" /></div>}>
<LoginForm />
</Suspense>
)
}