account page render fix
This commit is contained in:
parent
996ebe4757
commit
6ea6080b11
4 changed files with 161 additions and 223 deletions
|
|
@ -1,15 +1,21 @@
|
|||
// app/auth/sign-in/page.tsx
|
||||
import { Suspense } from "react";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import SignIn from "./sign-in";
|
||||
|
||||
// Ensure this page renders at runtime (avoids SSG trying to pre-render a client-only page)
|
||||
export const dynamic = "force-dynamic";
|
||||
export default async function SignInPage(
|
||||
props: { searchParams: Promise<Record<string, string | string[] | undefined>> }
|
||||
) {
|
||||
const at = (await cookies()).get("ma_at")?.value;
|
||||
if (at) redirect("/portal");
|
||||
|
||||
export default function SignInPage() {
|
||||
// Wrap the client component (which uses useSearchParams) in Suspense
|
||||
return (
|
||||
<Suspense fallback={<div className="p-6">Loading…</div>}>
|
||||
<SignIn />
|
||||
</Suspense>
|
||||
);
|
||||
const sp = await props.searchParams;
|
||||
const nextParam = Array.isArray(sp.next) ? sp.next[0] : sp.next;
|
||||
const nextPath = nextParam && nextParam.startsWith("/") ? nextParam : "/portal";
|
||||
|
||||
const reauthParam = Array.isArray(sp.reauth) ? sp.reauth[0] : sp.reauth;
|
||||
const forceParam = Array.isArray(sp.force) ? sp.force[0] : sp.force;
|
||||
const reauth = reauthParam === "1" || forceParam === "1";
|
||||
|
||||
return <SignIn nextPath={nextPath} reauth={reauth} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,130 +2,114 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
type Props = { nextPath?: string };
|
||||
type Props = { nextPath?: string; reauth?: boolean };
|
||||
|
||||
export default function SignIn({ nextPath = "/portal" }: Props) {
|
||||
export default function SignIn({ nextPath = "/portal", reauth = false }: Props) {
|
||||
const router = useRouter();
|
||||
const sp = useSearchParams();
|
||||
|
||||
// Respect reauth/force flags from query
|
||||
const reauth = sp.get("reauth") === "1" || sp.get("force") === "1";
|
||||
const next = sp.get("next") || nextPath;
|
||||
|
||||
const [identifier, setIdentifier] = useState(""); // email OR username
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShow] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setErr(null);
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
body: JSON.stringify({ identifier, password }),
|
||||
});
|
||||
|
||||
const txt = await res.text();
|
||||
let j: any = null;
|
||||
try { j = txt ? JSON.parse(txt) : null; } catch {}
|
||||
|
||||
if (!res.ok) {
|
||||
const message = j?.error || j?.message || `Sign-in failed (${res.status})`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
// Success → go to intended destination (account page in reauth flow)
|
||||
router.replace(next);
|
||||
router.refresh();
|
||||
} catch (e: any) {
|
||||
setErr(e?.message || "Unable to sign in.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const onSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setErr(null);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
body: JSON.stringify({ identifier, password }),
|
||||
});
|
||||
const txt = await res.text();
|
||||
let j: any = null; try { j = txt ? JSON.parse(txt) : null; } catch {}
|
||||
if (!res.ok) {
|
||||
throw new Error(j?.error || j?.message || `Sign-in failed (${res.status})`);
|
||||
}
|
||||
},
|
||||
[identifier, password, next, router]
|
||||
);
|
||||
// Land where caller requested
|
||||
router.replace(nextPath);
|
||||
router.refresh();
|
||||
} catch (e: any) {
|
||||
setErr(e?.message || "Unable to sign in.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [identifier, password, nextPath, router]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md rounded-lg border p-6">
|
||||
{reauth && (
|
||||
<div className="mb-4 rounded-md border p-3 bg-card">
|
||||
<div className="font-medium">Please sign in again</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
For security, we need to confirm it’s you before accessing account settings.
|
||||
<div className="mx-auto max-w-md rounded-lg border p-6 space-y-4">
|
||||
<div>
|
||||
<h1 className="mb-1 text-2xl font-semibold">
|
||||
{reauth ? "Re-authenticate" : "Sign In"}
|
||||
</h1>
|
||||
<p className="text-sm opacity-70">
|
||||
{reauth
|
||||
? "Please sign in again to continue."
|
||||
: <>Use your email <em>or</em> username with your password.</>}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form className="space-y-4" onSubmit={onSubmit}>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Email or Username</label>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="you@example.com or your-handle"
|
||||
value={identifier}
|
||||
onChange={(e) => setIdentifier(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h1 className="mb-1 text-2xl font-semibold">Sign In</h1>
|
||||
<p className="mb-6 text-sm opacity-70">
|
||||
Use your email <em>or</em> username with your password.
|
||||
</p>
|
||||
|
||||
<form className="space-y-4" onSubmit={onSubmit}>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Email or Username</label>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="you@example.com or your-handle"
|
||||
value={identifier}
|
||||
onChange={(e) => setIdentifier(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">Password</label>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs opacity-70 hover:opacity-100"
|
||||
onClick={() => setShowPassword((s) => !s)}
|
||||
>
|
||||
{showPassword ? "Hide" : "Show"}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="current-password"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{err && (
|
||||
<div className="rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||
{err}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">Password</label>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs opacity-70 hover:opacity-100"
|
||||
onClick={() => setShow((s) => !s)}
|
||||
>
|
||||
{showPassword ? "Hide" : "Show"}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="current-password"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full rounded-md bg-black px-3 py-2 text-white disabled:opacity-60"
|
||||
>
|
||||
{loading ? "Signing in…" : "Sign In"}
|
||||
</button>
|
||||
</form>
|
||||
{err && (
|
||||
<div className="rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||
{err}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 text-center text-sm">
|
||||
<span className="opacity-70">New here?</span>{" "}
|
||||
<a className="underline" href={"/auth/sign-up"}>Create an account</a>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full rounded-md bg-black px-3 py-2 text-white disabled:opacity-60"
|
||||
>
|
||||
{loading ? "Signing in…" : reauth ? "Re-authenticate" : "Sign In"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{!reauth && (
|
||||
<div className="mt-2 text-center text-sm">
|
||||
<span className="opacity-70">New here?</span>{" "}
|
||||
<a className="underline" href="/auth/sign-up">Create an account</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue