import { useEffect, useState } from "react"; import { Link, Navigate, useLocation } from "react-router-dom"; import DateField from "@/components/DateField"; import Select from "@/components/Select"; import { Alert, Button, Field, TextInput } from "@/components/ui"; import { api, ApiError } from "@/lib/api"; import { toIsoDate } from "@/lib/date"; import type { Atoll, RegistrationTicket } from "@/lib/api"; const TERMS_URL = "https://sarlink.net/terms"; const POLICY_URL = "https://sarlink.net/policy"; type Errors = Record; /** * The registration form, reached only after the number was confirmed by SMS: * `location.state` carries the registration ticket from /login. * * Submitting does not create a usable account - it files an application an * admin has to approve. */ export default function Register() { const location = useLocation(); const ticket = location.state as RegistrationTicket | null; const [atolls, setAtolls] = useState(null); const [loadError, setLoadError] = useState(null); const [fullName, setFullName] = useState(""); const [idnumber, setIdnumber] = useState(""); const [dateOfBirth, setDateOfBirth] = useState(undefined); const [atollId, setAtollId] = useState(""); const [islandId, setIslandId] = useState(""); const [terms, setTerms] = useState(false); const [policy, setPolicy] = useState(false); const [errors, setErrors] = useState({}); const [formError, setFormError] = useState(null); // The ticket expired or was already used: the number has to be re-verified. const [needsReverify, setNeedsReverify] = useState(false); const [busy, setBusy] = useState(false); const [submitted, setSubmitted] = useState(null); useEffect(() => { let active = true; api .atolls() .then((result) => { if (active) setAtolls(result); }) .catch(() => { if (active) setLoadError("Couldn't load the island list. Reload to retry."); }); return () => { active = false; }; }, []); // No ticket means the number was never verified - start over. if (!ticket?.registration_token && !submitted) { return ; } const islands = atolls?.find((atoll) => `${atoll.id}` === atollId)?.islands ?? []; async function submit(event: React.FormEvent) { event.preventDefault(); setBusy(true); setErrors({}); setFormError(null); try { const result = await api.register({ registration_token: ticket!.registration_token, full_name: fullName, idnumber: idnumber, date_of_birth: dateOfBirth ? toIsoDate(dateOfBirth) : "", atoll: Number(atollId), island: Number(islandId), terms_accepted: terms, policy_accepted: policy, }); setSubmitted(result.detail); } catch (err) { if (err instanceof ApiError) { const fieldErrors: Errors = {}; for (const [field, messages] of Object.entries(err.errors ?? {})) { if (Array.isArray(messages)) fieldErrors[field] = messages[0]; } setErrors(fieldErrors); const ticketError = fieldErrors.registration_token ?? fieldErrors.mobile; if (ticketError) { setNeedsReverify(true); setFormError(ticketError); } else { // Field errors are shown inline; only a general failure needs the banner. setFormError(Object.keys(fieldErrors).length > 0 ? null : err.message); } } else { setFormError("Something went wrong. Try again."); } } finally { setBusy(false); } } if (submitted) { return (

{submitted}

Back to sign in
); } return ( {formError ? ( {formError} {needsReverify ? ( <> {" "} Start over ) : null} ) : null} {loadError ? {loadError} : null}
setFullName(event.target.value)} required /> setIdnumber(event.target.value)} required />
I agree to the{" "} terms and conditions. I understand the privacy policy.
); } function Shell({ title, subtitle, children, }: { title: string; subtitle?: string; children: React.ReactNode; }) { return (

{title}

{subtitle ? (

{subtitle}

) : null}
{children}
); } function Checkbox({ name, checked, onChange, error, children, }: { name: string; checked: boolean; onChange: (value: boolean) => void; error?: string; children: React.ReactNode; }) { return (
{error ? (

{error}

) : null}
); } function ExternalLink({ href, children }: { href: string; children: React.ReactNode }) { return ( {children} ); }