generated from muhtadeetaron/nextjs-template
222 lines
6.6 KiB
TypeScript
222 lines
6.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
|
import FormField from "@/components/FormField";
|
|
import DestructibleAlert from "@/components/DestructibleAlert";
|
|
import { RegisterForm } from "@/types/auth";
|
|
import { useAuthStore } from "@/stores/authStore";
|
|
|
|
interface ValidationErrorItem {
|
|
type: string;
|
|
loc: string[];
|
|
msg: string;
|
|
input?: unknown;
|
|
ctx?: Record<string, unknown>;
|
|
}
|
|
|
|
interface CustomError extends Error {
|
|
response?: {
|
|
detail?: string | ValidationErrorItem;
|
|
};
|
|
}
|
|
|
|
export default function RegisterPage() {
|
|
const { register } = useAuthStore();
|
|
const router = useRouter();
|
|
const [form, setForm] = useState<RegisterForm>({
|
|
full_name: "",
|
|
username: "",
|
|
email: "",
|
|
password: "",
|
|
phone_number: "",
|
|
ssc_roll: 0,
|
|
ssc_board: "",
|
|
hsc_roll: 0,
|
|
hsc_board: "",
|
|
college: "",
|
|
preparation_unit: "",
|
|
});
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
function formatError(error: unknown): string {
|
|
if (error && typeof error === "object" && "response" in (error as any)) {
|
|
const customError = error as CustomError;
|
|
const detail = customError.response?.detail;
|
|
|
|
if (typeof detail === "string") {
|
|
return detail; // plain backend error string
|
|
}
|
|
|
|
if (Array.isArray(detail)) {
|
|
// Pick the first validation error, or join them if multiple
|
|
return detail.map((d) => d.msg).join("; ");
|
|
}
|
|
}
|
|
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
|
|
return "An unexpected error occurred.";
|
|
}
|
|
|
|
const validateForm = () => {
|
|
const { ssc_roll, hsc_roll, password } = form;
|
|
if (ssc_roll === hsc_roll) {
|
|
return "SSC Roll and HSC Roll must be unique.";
|
|
}
|
|
const passwordRegex =
|
|
/^(?=.*[A-Z])(?=.*[!@#$%^&*(),.?":{}|<>])[A-Za-z\d!@#$%^&*(),.?":{}|<>]{8,16}$/;
|
|
if (!passwordRegex.test(password)) {
|
|
return "Password must be 8-16 characters long, include at least one uppercase letter and one special character.";
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const createUser = async () => {
|
|
const validationError = validateForm();
|
|
if (validationError) {
|
|
setError(validationError);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await register(form);
|
|
router.replace("/login");
|
|
} catch (err: unknown) {
|
|
setError(formatError(err));
|
|
console.error("User creation error: ", err);
|
|
}
|
|
};
|
|
return (
|
|
<BackgroundWrapper>
|
|
<div className="min-h-screen flex flex-col items-center justify-center px-4 py-10">
|
|
<div className="w-full space-y-6">
|
|
<div className="w-full aspect-[368/89] mx-auto">
|
|
<Image
|
|
src="/images/logo/logo.png"
|
|
alt="logo"
|
|
width={368}
|
|
height={89}
|
|
className="w-full h-auto"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-10">
|
|
<div className="space-y-6">
|
|
<h1 className="text-2xl font-semibold">Personal Info</h1>
|
|
<FormField
|
|
title="Full name"
|
|
value={form.full_name}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, full_name: value })
|
|
}
|
|
/>
|
|
<FormField
|
|
title="User name"
|
|
value={form.username}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, username: value })
|
|
}
|
|
/>
|
|
<FormField
|
|
title="Phone Number"
|
|
value={form.phone_number}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, phone_number: value })
|
|
}
|
|
/>
|
|
<FormField
|
|
title="Email Address"
|
|
value={form.email}
|
|
handleChangeText={(value) => setForm({ ...form, email: value })}
|
|
/>
|
|
|
|
<FormField
|
|
title="Password"
|
|
value={form.password}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, password: value })
|
|
}
|
|
placeholder={undefined}
|
|
/>
|
|
<h1 className="text-2xl font-semibold">Educational Background</h1>
|
|
|
|
<FormField
|
|
title="College"
|
|
value={form.college}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, college: value })
|
|
}
|
|
/>
|
|
<FormField
|
|
title="Preparation Unit"
|
|
value={form.preparation_unit}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, preparation_unit: value })
|
|
}
|
|
/>
|
|
<div className="w-full flex gap-4">
|
|
<FormField
|
|
title="SSC Board"
|
|
value={form.ssc_board}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, ssc_board: value })
|
|
}
|
|
/>
|
|
|
|
<FormField
|
|
title="SSC Roll No."
|
|
value={form.ssc_roll}
|
|
handleChangeText={(value: string) =>
|
|
setForm({ ...form, ssc_roll: Number(value) })
|
|
}
|
|
className="max-w-26"
|
|
/>
|
|
</div>
|
|
|
|
<div className="w-full flex gap-4">
|
|
<FormField
|
|
title="HSC Board"
|
|
value={form.hsc_board}
|
|
handleChangeText={(value) =>
|
|
setForm({ ...form, hsc_board: value })
|
|
}
|
|
/>
|
|
<FormField
|
|
title="HSC Roll No."
|
|
value={form.hsc_roll}
|
|
handleChangeText={(value: string) =>
|
|
setForm({ ...form, hsc_roll: Number(value) })
|
|
}
|
|
className="max-w-26"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{error && <DestructibleAlert text={error} />}
|
|
|
|
<button
|
|
onClick={createUser}
|
|
className="w-full h-14 rounded-full border border-blue-900 text-center text-base font-medium"
|
|
>
|
|
Get started
|
|
</button>
|
|
|
|
<p className="text-center text-sm">
|
|
Already have an account?
|
|
<Link href="/login" className="text-blue-600">
|
|
Login here
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</BackgroundWrapper>
|
|
);
|
|
}
|