Files
examjam-frontend/app/(auth)/register/page.tsx

224 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 { register } from "@/lib/auth";
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 { setToken } = 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, setToken);
router.push("/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>
);
}