feat: split director residential and postal addresses into structured fields

This commit is contained in:
Dave 2026-08-14 20:49:44 +02:00
parent 8f26181514
commit 4c0a256d63
4 changed files with 132 additions and 25 deletions

View file

@ -115,7 +115,19 @@ export async function POST(
inactive_companies: 0,
first_seen: new Date().toISOString().slice(0, 10),
last_checked: new Date().toISOString().slice(0, 10),
notes: `Created from onboarding submission ${submission.reference_no}. Residential: ${d.residential_address}; Postal: ${d.postal_address}; Email: ${d.email}; DOB: ${d.date_of_birth}.`,
residential_address_line_1: d.residential_address_line_1?.trim() ?? "",
residential_address_line_2: d.residential_address_line_2?.trim() ?? "",
residential_town_city: d.residential_town_city?.trim() ?? "",
residential_province: d.residential_province?.trim() ?? "",
residential_postal_code: d.residential_postal_code?.trim() ?? "",
postal_address_line_1: d.postal_address_line_1?.trim() ?? "",
postal_address_line_2: d.postal_address_line_2?.trim() ?? "",
postal_town_city: d.postal_town_city?.trim() ?? "",
postal_province: d.postal_province?.trim() ?? "",
postal_postal_code: d.postal_postal_code?.trim() ?? "",
email: d.email?.trim() ?? "",
date_of_birth: d.date_of_birth ?? "",
notes: `Created from onboarding submission ${submission.reference_no}. Email: ${d.email}; DOB: ${d.date_of_birth}.`,
});
created.push(created_rec.id);
} catch (err) {

View file

@ -98,8 +98,14 @@ function validateDirector(d: OnboardingDirectorPayload): string | null {
if (!d.surname?.trim()) return "Director surname required";
if (!/^\d{13}$/.test(d.id_no ?? "")) return "Director ID must be 13 digits";
if (!d.date_of_birth) return "Director date of birth required";
if (!d.residential_address?.trim()) return "Director residential address required";
if (!d.postal_address?.trim()) return "Director postal address required";
if (!d.residential_address_line_1?.trim()) return "Director residential address line 1 required";
if (!d.residential_town_city?.trim()) return "Director residential town/city required";
if (!d.residential_province?.trim()) return "Director residential province required";
if (!d.residential_postal_code?.trim()) return "Director residential postal code required";
if (!d.postal_address_line_1?.trim()) return "Director postal address line 1 required";
if (!d.postal_town_city?.trim()) return "Director postal town/city required";
if (!d.postal_province?.trim()) return "Director postal province required";
if (!d.postal_postal_code?.trim()) return "Director postal postal code required";
if (!d.email?.trim()) return "Director email required";
return null;
}

View file

@ -60,7 +60,17 @@ function blankDirector(): OnboardingDirector {
id_no: "",
date_of_birth: "",
residential_address: "",
residential_address_line_1: "",
residential_address_line_2: "",
residential_town_city: "",
residential_province: "",
residential_postal_code: "",
postal_address: "",
postal_address_line_1: "",
postal_address_line_2: "",
postal_town_city: "",
postal_province: "",
postal_postal_code: "",
cell_phone: "",
email: ""
};
@ -149,8 +159,14 @@ function validateDirector(d: OnboardingDirector): string[] {
const idErr = validateIdNo(d.id_no);
if (idErr) errs.push(idErr);
if (!d.date_of_birth) errs.push("Date of birth required");
if (!d.residential_address.trim()) errs.push("Residential address required");
if (!d.postal_address.trim()) errs.push("Postal address required");
if (!d.residential_address_line_1.trim()) errs.push("Residential address line 1 required");
if (!d.residential_town_city.trim()) errs.push("Residential town/city required");
if (!d.residential_province.trim()) errs.push("Residential province required");
if (!d.residential_postal_code.trim()) errs.push("Residential postal code required");
if (!d.postal_address_line_1.trim()) errs.push("Postal address line 1 required");
if (!d.postal_town_city.trim()) errs.push("Postal town/city required");
if (!d.postal_province.trim()) errs.push("Postal province required");
if (!d.postal_postal_code.trim()) errs.push("Postal postal code required");
if (!d.cell_phone.trim()) errs.push("Cell phone required");
const emailErr = validateEmail(d.email);
if (emailErr) errs.push(emailErr);
@ -864,24 +880,75 @@ function DirectorsStep({
</Field>
</div>
<Field label="Residential address" required>
<textarea
className={INPUT_CLS + " min-h-[80px] resize-y"}
value={dir.residential_address}
onChange={(e) => onUpdate(dir.ui_key, { residential_address: e.target.value })}
placeholder="12 Beach Road, Sea Point, Cape Town, 8005"
required
/>
</Field>
<Field label="Postal address" required>
<textarea
className={INPUT_CLS + " min-h-[80px] resize-y"}
value={dir.postal_address}
onChange={(e) => onUpdate(dir.ui_key, { postal_address: e.target.value })}
placeholder="PO Box 1234, Cape Town, 8000"
required
/>
</Field>
<div className="space-y-1">
<p className="text-xs font-semibold text-muted uppercase tracking-wider">Residential address</p>
<Field label="Address line 1" required>
<input className={INPUT_CLS} value={dir.residential_address_line_1}
onChange={(e) => onUpdate(dir.ui_key, { residential_address_line_1: e.target.value })}
placeholder="12 Beach Road" required />
</Field>
<Field label="Address line 2">
<input className={INPUT_CLS} value={dir.residential_address_line_2}
onChange={(e) => onUpdate(dir.ui_key, { residential_address_line_2: e.target.value })}
placeholder="Sea Point" />
</Field>
<div className="grid grid-cols-2 gap-3">
<Field label="Town / City" required>
<input className={INPUT_CLS} value={dir.residential_town_city}
onChange={(e) => onUpdate(dir.ui_key, { residential_town_city: e.target.value })}
placeholder="Cape Town" required />
</Field>
<Field label="Province" required>
<select className={INPUT_CLS} value={dir.residential_province}
onChange={(e) => onUpdate(dir.ui_key, { residential_province: e.target.value })} required>
<option value="">Select province</option>
{["Eastern Cape","Free State","Gauteng","KwaZulu-Natal","Limpopo","Mpumalanga","Northern Cape","North West","Western Cape"].map(p => (
<option key={p} value={p}>{p}</option>
))}
</select>
</Field>
</div>
<Field label="Postal code" required>
<input className={INPUT_CLS} value={dir.residential_postal_code}
onChange={(e) => onUpdate(dir.ui_key, { residential_postal_code: e.target.value })}
placeholder="8005" required />
</Field>
</div>
<div className="space-y-1">
<p className="text-xs font-semibold text-muted uppercase tracking-wider">Postal address <span className="text-muted font-normal normal-case">(leave blank if same as residential)</span></p>
<Field label="Address line 1">
<input className={INPUT_CLS} value={dir.postal_address_line_1}
onChange={(e) => onUpdate(dir.ui_key, { postal_address_line_1: e.target.value })}
placeholder="PO Box 1234" />
</Field>
<Field label="Address line 2">
<input className={INPUT_CLS} value={dir.postal_address_line_2}
onChange={(e) => onUpdate(dir.ui_key, { postal_address_line_2: e.target.value })}
placeholder="Cape Town" />
</Field>
<div className="grid grid-cols-2 gap-3">
<Field label="Town / City">
<input className={INPUT_CLS} value={dir.postal_town_city}
onChange={(e) => onUpdate(dir.ui_key, { postal_town_city: e.target.value })}
placeholder="Cape Town" />
</Field>
<Field label="Province">
<select className={INPUT_CLS} value={dir.postal_province}
onChange={(e) => onUpdate(dir.ui_key, { postal_province: e.target.value })}>
<option value="">Select province</option>
{["Eastern Cape","Free State","Gauteng","KwaZulu-Natal","Limpopo","Mpumalanga","Northern Cape","North West","Western Cape"].map(p => (
<option key={p} value={p}>{p}</option>
))}
</select>
</Field>
</div>
<Field label="Postal code">
<input className={INPUT_CLS} value={dir.postal_postal_code}
onChange={(e) => onUpdate(dir.ui_key, { postal_postal_code: e.target.value })}
placeholder="8000" />
</Field>
</div>
<DocUploadBlock
directorKey={dir.ui_key}
@ -1217,8 +1284,20 @@ function ReviewStep({
<SummaryRow label="Date of birth" value={d.date_of_birth || "—"} />
<SummaryRow label="Cell phone" value={d.cell_phone || "—"} />
<SummaryRow label="Email" value={d.email || "—"} />
<SummaryRow label="Residential" value={d.residential_address || "—"} />
<SummaryRow label="Postal" value={d.postal_address || "—"} />
<SummaryRow label="Residential address" value={[
d.residential_address_line_1,
d.residential_address_line_2,
d.residential_town_city,
d.residential_province,
d.residential_postal_code,
].filter(Boolean).join(", ") || "—"} />
<SummaryRow label="Postal address" value={[
d.postal_address_line_1,
d.postal_address_line_2,
d.postal_town_city,
d.postal_province,
d.postal_postal_code,
].filter(Boolean).join(", ") || "—"} />
<SummaryRow
label="ID document"
value={docs.some((doc) => doc.directorKey === d.ui_key && doc.kind === "id") ? "Uploaded" : "Missing"}

View file

@ -141,7 +141,17 @@ export interface OnboardingDirector {
id_no: string;
date_of_birth: string; // YYYY-MM-DD
residential_address: string;
residential_address_line_1: string;
residential_address_line_2: string;
residential_town_city: string;
residential_province: string;
residential_postal_code: string;
postal_address: string;
postal_address_line_1: string;
postal_address_line_2: string;
postal_town_city: string;
postal_province: string;
postal_postal_code: string;
cell_phone: string;
email: string;
}