forked from UKSOURCE/ipv6
201 lines
8.2 KiB
TypeScript
201 lines
8.2 KiB
TypeScript
"use client";
|
|
|
|
import SubmitToast from "@/app/components/ui/SubmitToast";
|
|
import { useMemo, useState } from "react";
|
|
|
|
type IndustryOption = { value: string; label: string };
|
|
|
|
const emptyRegistration = {
|
|
fullName: "",
|
|
phone: "",
|
|
email: "",
|
|
company: "",
|
|
jobTitle: "",
|
|
industry: "",
|
|
notes: "",
|
|
};
|
|
|
|
export default function RegistrationForm({
|
|
data,
|
|
}: {
|
|
data: {
|
|
sections: {
|
|
personal: { title: string; fullName: string; phone: string; email: string };
|
|
professional: { title: string; company: string; jobTitle: string };
|
|
industry: { title: string; label: string; placeholder: string; options: IndustryOption[] };
|
|
additional: { title: string; label: string; placeholder: string };
|
|
};
|
|
submit: { label: string; disclaimer: string };
|
|
};
|
|
}) {
|
|
const industries = useMemo(() => data.sections.industry.options, [data.sections.industry.options]);
|
|
const [form, setForm] = useState(emptyRegistration);
|
|
const [submitState, setSubmitState] = useState<"idle" | "sending" | "ok" | "error">("idle");
|
|
|
|
return (
|
|
<form
|
|
className="space-y-[32px]"
|
|
onSubmit={async (e) => {
|
|
e.preventDefault();
|
|
setSubmitState("sending");
|
|
const industryLabel = industries.find((o) => o.value === form.industry)?.label ?? "";
|
|
try {
|
|
const res = await fetch("/api/submissions", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
type: "registration",
|
|
...form,
|
|
industryLabel,
|
|
}),
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({}));
|
|
throw new Error(typeof err?.error === "string" ? err.error : res.statusText);
|
|
}
|
|
setSubmitState("ok");
|
|
} catch {
|
|
setSubmitState("error");
|
|
}
|
|
}}
|
|
>
|
|
<div className="space-y-[16px]">
|
|
<h2 className="font-[var(--font-display-lg)] text-[20px] text-primary border-l-2 border-primary pl-3">
|
|
{data.sections.personal.title}
|
|
</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-[16px]">
|
|
<div className="space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.personal.fullName}</label>
|
|
<input
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface placeholder:text-outline/40 transition-all"
|
|
placeholder="John Doe"
|
|
value={form.fullName}
|
|
onChange={(e) => setForm((s) => ({ ...s, fullName: e.target.value }))}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.personal.phone}</label>
|
|
<input
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface placeholder:text-outline/40 transition-all"
|
|
placeholder="+84 ..."
|
|
value={form.phone}
|
|
onChange={(e) => setForm((s) => ({ ...s, phone: e.target.value }))}
|
|
/>
|
|
</div>
|
|
<div className="md:col-span-2 space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.personal.email}</label>
|
|
<input
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface placeholder:text-outline/40 transition-all"
|
|
placeholder="john.doe@company.com"
|
|
type="email"
|
|
value={form.email}
|
|
onChange={(e) => setForm((s) => ({ ...s, email: e.target.value }))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="border-white/5" />
|
|
|
|
<div className="space-y-[16px]">
|
|
<h2 className="font-[var(--font-display-lg)] text-[20px] text-primary border-l-2 border-primary pl-3">
|
|
{data.sections.professional.title}
|
|
</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-[16px]">
|
|
<div className="space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.professional.company}</label>
|
|
<input
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface placeholder:text-outline/40 transition-all"
|
|
placeholder="Company"
|
|
value={form.company}
|
|
onChange={(e) => setForm((s) => ({ ...s, company: e.target.value }))}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.professional.jobTitle}</label>
|
|
<input
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface placeholder:text-outline/40 transition-all"
|
|
placeholder="Network Architect"
|
|
value={form.jobTitle}
|
|
onChange={(e) => setForm((s) => ({ ...s, jobTitle: e.target.value }))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="border-white/5" />
|
|
|
|
<div className="space-y-[16px]">
|
|
<h2 className="font-[var(--font-display-lg)] text-[20px] text-primary border-l-2 border-primary pl-3">
|
|
{data.sections.industry.title}
|
|
</h2>
|
|
<div className="space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.industry.label}</label>
|
|
<div className="relative">
|
|
<select
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface appearance-none transition-all"
|
|
value={form.industry}
|
|
onChange={(e) => setForm((s) => ({ ...s, industry: e.target.value }))}
|
|
>
|
|
<option value="" disabled>
|
|
{data.sections.industry.placeholder}
|
|
</option>
|
|
{industries.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<span className="material-symbols-outlined absolute right-4 top-1/2 -translate-y-1/2 text-outline pointer-events-none">
|
|
expand_more
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="border-white/5" />
|
|
|
|
<div className="space-y-[16px]">
|
|
<h2 className="font-[var(--font-display-lg)] text-[20px] text-primary border-l-2 border-primary pl-3">
|
|
{data.sections.additional.title}
|
|
</h2>
|
|
<div className="space-y-1">
|
|
<label className="text-outline uppercase tracking-wider">{data.sections.additional.label}</label>
|
|
<textarea
|
|
className="w-full bg-surface-container-low border border-white/10 rounded-lg px-4 py-3 text-on-surface placeholder:text-outline/40 transition-all resize-none"
|
|
placeholder={data.sections.additional.placeholder}
|
|
rows={4}
|
|
value={form.notes}
|
|
onChange={(e) => setForm((s) => ({ ...s, notes: e.target.value }))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-10 border-t border-white/5 flex flex-col items-center gap-6">
|
|
<button
|
|
className="gold-gradient-bg text-on-primary w-full md:w-auto px-16 py-5 rounded-xl font-[var(--font-display-lg)] text-xl uppercase tracking-wider hover:shadow-[0_0_30px_rgba(197,160,89,0.4)] transition-all duration-300 active:scale-[0.98] disabled:opacity-60 disabled:pointer-events-none"
|
|
type="submit"
|
|
disabled={submitState === "sending"}
|
|
>
|
|
{submitState === "sending" ? "Submitting…" : data.submit.label}
|
|
</button>
|
|
|
|
<SubmitToast
|
|
state={submitState}
|
|
successTitle="Registration received"
|
|
successMessage="Thank you. Your registration was saved and will appear in the admin console under Pending."
|
|
onDismiss={() => {
|
|
setSubmitState("idle");
|
|
setForm({ ...emptyRegistration });
|
|
}}
|
|
/>
|
|
|
|
<p className="mt-[16px] text-center text-outline text-[11px] tracking-widest opacity-60 uppercase">
|
|
{data.submit.disclaimer}
|
|
</p>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|
|
|