parent
30ac27815f
commit
74036bc2ce
3 changed files with 173 additions and 184 deletions
|
|
@ -6,11 +6,14 @@ import { useRouter } from "next/navigation";
|
|||
|
||||
type Props = { nextPath?: string };
|
||||
|
||||
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
export default function SignUp({ nextPath = "/portal" }: Props) {
|
||||
const router = useRouter();
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
|
@ -20,33 +23,33 @@ export default function SignUp({ nextPath = "/portal" }: Props) {
|
|||
e.preventDefault();
|
||||
setErr(null);
|
||||
|
||||
const u = username.trim();
|
||||
const em = email.trim().toLowerCase();
|
||||
const pw = password;
|
||||
const un = username.trim();
|
||||
|
||||
if (!u) {
|
||||
setErr("Username is required.");
|
||||
if (!em || !un || !password || !confirmPassword) {
|
||||
setErr("All fields are required.");
|
||||
return;
|
||||
}
|
||||
if (!pw || pw.length < 8) {
|
||||
if (!EMAIL_RE.test(em)) {
|
||||
setErr("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
setErr("Password must be at least 8 characters.");
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
setErr("Passwords do not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch("/api/auth/register", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: u,
|
||||
email: em || undefined,
|
||||
password: pw,
|
||||
}),
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
body: JSON.stringify({ email: em, username: un, password, confirmPassword }),
|
||||
});
|
||||
|
||||
const txt = await res.text();
|
||||
|
|
@ -59,13 +62,12 @@ export default function SignUp({ nextPath = "/portal" }: Props) {
|
|||
|
||||
if (!res.ok) {
|
||||
const message =
|
||||
(j?.error && j?.debug ? `${j.error} (${j.debug})` : j?.error) ||
|
||||
j?.error ||
|
||||
j?.message ||
|
||||
`Sign-up failed (${res.status})`;
|
||||
(res.status === 409 ? "Email or username already in use." : `Sign-up failed (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
// If AUTO_LOGIN is enabled on the server, the user is already signed in.
|
||||
router.replace(nextPath);
|
||||
router.refresh();
|
||||
} catch (e: any) {
|
||||
|
|
@ -74,17 +76,28 @@ export default function SignUp({ nextPath = "/portal" }: Props) {
|
|||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[username, email, password, nextPath, router]
|
||||
); // <-- make sure this closing ); is present
|
||||
[email, username, password, confirmPassword, nextPath, router]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md rounded-lg border p-6">
|
||||
<h1 className="mb-1 text-2xl font-semibold">Create Account</h1>
|
||||
<p className="mb-6 text-sm opacity-70">
|
||||
Join MakerDash to manage rigs, settings, and projects.
|
||||
</p>
|
||||
<p className="mb-6 text-sm opacity-70">Join MakeArmy to manage rigs, settings, and projects.</p>
|
||||
|
||||
<form className="space-y-4" onSubmit={onSubmit}>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Username</label>
|
||||
<input
|
||||
|
|
@ -95,20 +108,7 @@ export default function SignUp({ nextPath = "/portal" }: Props) {
|
|||
value={username}
|
||||
onChange={(e) => setUsername(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">
|
||||
Email <span className="opacity-60">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||
minLength={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ export default function SignUp({ nextPath = "/portal" }: Props) {
|
|||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="new-password"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="Choose a strong password"
|
||||
placeholder="At least 8 characters"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
required
|
||||
|
|
@ -135,6 +135,20 @@ export default function SignUp({ nextPath = "/portal" }: Props) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Confirm Password</label>
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="new-password"
|
||||
className="w-full rounded-md border px-3 py-2"
|
||||
placeholder="Re-enter your password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.currentTarget.value)}
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{err && (
|
||||
<div className="rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||
{err}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue