171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import { Button, TextInput } from "@/components/atoms";
|
|
import Link from "next/link";
|
|
import { useState } from "react";
|
|
|
|
interface FormState {
|
|
name: string;
|
|
phone: string;
|
|
password: string;
|
|
}
|
|
|
|
interface FormErrors {
|
|
name?: string;
|
|
phone?: string;
|
|
password?: string;
|
|
}
|
|
|
|
const PHONE_RE = /^0\d{9}$/;
|
|
|
|
function validate(data: FormState): FormErrors {
|
|
const errors: FormErrors = {};
|
|
if (!data.name.trim()) errors.name = "Name is required";
|
|
if (!data.phone.trim()) {
|
|
errors.phone = "Phone number is required";
|
|
} else if (!PHONE_RE.test(data.phone.trim())) {
|
|
errors.phone = "Must be 10 digits starting with 0 (e.g. 0912345678)";
|
|
}
|
|
if (!data.password) {
|
|
errors.password = "Password is required";
|
|
} else if (data.password.length < 6) {
|
|
errors.password = "Password must be at least 6 characters";
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
export default function CreateStaffPage() {
|
|
const [form, setForm] = useState<FormState>({ name: "", phone: "", password: "" });
|
|
const [errors, setErrors] = useState<FormErrors>({});
|
|
const [apiError, setApiError] = useState<string | null>(null);
|
|
const [success, setSuccess] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
const { name, value } = e.target;
|
|
setForm((prev) => ({ ...prev, [name]: value }));
|
|
setErrors((prev) => ({ ...prev, [name]: undefined }));
|
|
setApiError(null);
|
|
setSuccess(false);
|
|
}
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
|
|
const validation = validate(form);
|
|
if (Object.keys(validation).length > 0) {
|
|
setErrors(validation);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setApiError(null);
|
|
try {
|
|
const res = await fetch("/api/Staff/signup", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
name: form.name.trim(),
|
|
phone: form.phone.trim(),
|
|
password: form.password,
|
|
}),
|
|
});
|
|
|
|
if (res.ok) {
|
|
setSuccess(true);
|
|
setForm({ name: "", phone: "", password: "" });
|
|
setErrors({});
|
|
} else {
|
|
const text = await res.text();
|
|
setApiError(text || "Failed to create account. Please try again.");
|
|
}
|
|
} catch {
|
|
setApiError("Connection error. Please check your network and try again.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen items-start justify-center bg-(--color-background) px-4 py-10">
|
|
<div className="w-full max-w-md">
|
|
<div className="mb-6">
|
|
<Link
|
|
href="/manager"
|
|
className="mb-4 inline-flex items-center gap-1.5 text-sm text-(--color-text-muted) no-underline transition hover:text-(--color-primary)"
|
|
>
|
|
<i className="fa-solid fa-arrow-left"></i>
|
|
Back to Dashboard
|
|
</Link>
|
|
<h1 className="text-foreground text-2xl font-bold">Create Staff Account</h1>
|
|
<p className="mt-1 text-sm text-(--color-text-muted)">
|
|
Fill in the details below to register a new staff member.
|
|
</p>
|
|
</div>
|
|
|
|
{success && (
|
|
<div className="mb-4 flex items-center gap-2 rounded-xl bg-green-50 px-4 py-3 text-sm font-medium text-green-700">
|
|
<i className="fa-solid fa-circle-check"></i>
|
|
Staff account created successfully.
|
|
</div>
|
|
)}
|
|
|
|
{apiError && (
|
|
<div className="mb-4 flex items-center gap-2 rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
|
|
<i className="fa-solid fa-circle-exclamation"></i>
|
|
{apiError}
|
|
</div>
|
|
)}
|
|
|
|
<div className="rounded-2xl border border-(--color-border-light) bg-white p-8 shadow-sm">
|
|
<form onSubmit={handleSubmit} className="space-y-5" noValidate>
|
|
<TextInput
|
|
label="Full Name"
|
|
name="name"
|
|
value={form.name}
|
|
onChange={handleChange}
|
|
placeholder="Enter staff's full name"
|
|
error={errors.name}
|
|
autoComplete="name"
|
|
/>
|
|
<TextInput
|
|
label="Phone Number"
|
|
name="phone"
|
|
value={form.phone}
|
|
onChange={handleChange}
|
|
placeholder="0xxxxxxxxx"
|
|
error={errors.phone}
|
|
inputMode="tel"
|
|
autoComplete="tel"
|
|
/>
|
|
<TextInput
|
|
label="Password"
|
|
name="password"
|
|
type={showPassword ? "text" : "password"}
|
|
value={form.password}
|
|
onChange={handleChange}
|
|
placeholder="At least 6 characters"
|
|
error={errors.password}
|
|
icon={showPassword ? "fa-eye-slash" : "fa-eye"}
|
|
onIconClick={() => setShowPassword((v) => !v)}
|
|
autoComplete="new-password"
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="primary"
|
|
size="lg"
|
|
icon="fa-user-plus"
|
|
disabled={loading}
|
|
className="mt-2 w-full"
|
|
>
|
|
{loading ? "Creating..." : "Create Account"}
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|