Workflow-App/Workflow/components/EditEntityModal.tsx
sjcoetzeesj-coder 11d0178ab1 feat: commit Workflow app source; move CIPC credentials to env
Adds the full Next.js app (app/, components/, lib/, scripts/, assets/,
docs/, Dockerfile, compose, configs) to version control.

Security:
- CIPC BizPortal customer-code logins (Login 1 + Login 3) now read
  CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE /
  CIPC_LOGIN3_PASSWORD from .env.local instead of source.
- BizPortal personal login in apply-bizportal route reads
  BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD.
- Added .env.example as the committed template.

.gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright
storage_state.json (live CIPC cookies), scratch dirs, client PDFs and
debug screenshots.
2026-10-18 15:21:45 +02:00

938 lines
56 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
// components/EditEntityModal.tsx
// Pre-filled edit form for existing director or company records.
// Opens from the Edit button on a table row or from the detail page.
// PUT/PATCH via /api/pb/records/:id, invalidates list + detail cache.
import { useState, useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { pbPatch, pbList } from "@/lib/api";
import { dateInputValue } from "@/lib/format";
interface Director {
id: string;
surname?: string | null;
first_names?: string | null;
id_no?: string | null;
/** SARS income-tax reference number (number; added 2026-09-16). */
tax_reference_no?: number | null;
telephone?: string | null;
email?: string | null;
postal_address?: string | null;
director_type?: string | null;
notes?: string | null;
// ─── Structured address fields (added 2026-08-18) ───
residential_address?: string | null;
residential_address_line_1?: string | null;
residential_address_line_2?: string | null;
residential_town_city?: string | null;
residential_province?: string | null;
residential_postal_code?: string | null;
postal_address_line_1?: string | null;
postal_address_line_2?: string | null;
postal_town_city?: string | null;
postal_province?: string | null;
postal_postal_code?: string | null;
date_of_birth?: string | null;
}
interface Company {
id: string;
enterprise_no?: string | null;
enterprise_name?: string | null;
enterprise_type?: string | null;
registration_date?: string | null;
physical_address?: string | null;
postal_address?: string | null;
company_email?: string | null;
director?: string | null;
notes?: string | null;
sars_login_username?: string | null;
sars_login_password?: string | null;
sars_paye_no?: string | null;
sars_vat_no?: string | null;
sars_tax_number?: string | null;
sars_vat_returns_period?: number | null;
sars_paye_return_period?: number | null;
// ─── SARS login behaviour (added 2026-09-11) ───
otp?: boolean | null;
groups?: string[] | null;
// ─── Structured address fields (added 2026-08-18) ───
physical_address_line_1?: string | null;
physical_address_line_2?: string | null;
physical_town_city?: string | null;
physical_province?: string | null;
physical_postal_code?: string | null;
postal_address_line_1?: string | null;
postal_address_line_2?: string | null;
postal_town_city?: string | null;
postal_province?: string | null;
postal_postal_code?: string | null;
// ─── Company-level fields (added 2026-08-18) ───
financial_year_end_month?: number | null;
authorised_shares?: number | null;
company_cell_phone?: string | null;
public_officer_name?: string | null;
company_secretary_name?: string | null;
}
interface GroupOption {
id: string;
name: string;
description?: string | null;
}
const SA_PROVINCES = [
"Eastern Cape",
"Free State",
"Gauteng",
"KwaZulu-Natal",
"Limpopo",
"Mpumalanga",
"Northern Cape",
"North West",
"Western Cape",
];
interface Props {
entity: "director" | "company";
/** Original record data for pre-filling the form */
record: Director | Company;
open: boolean;
onClose: () => void;
/** Called after a successful save */
onAfterSave?: () => void;
}
export default function EditEntityModal({ entity, record, open, onClose, onAfterSave }: Props) {
const qc = useQueryClient();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Director fields
const [surname, setSurname] = useState("");
const [firstNames, setFirstNames] = useState("");
const [idNo, setIdNo] = useState("");
// SARS tax reference — number, optional. Stored as a string in the input
// so we can render empty cleanly; coerced to a number on save (added 2026-09-16).
const [taxReferenceNo, setTaxReferenceNo] = useState("");
const [telephone, setTelephone] = useState("");
const [email, setEmail] = useState("");
const [postalAddress, setPostalAddress] = useState("");
const [residentialAddress, setResidentialAddress] = useState(""); // flat legacy — added 2026-08-20
const [directorType, setDirectorType] = useState("");
const [notes, setNotes] = useState("");
// Director structured address fields (added 2026-08-18)
const [dateOfBirth, setDateOfBirth] = useState("");
const [resAddr1, setResAddr1] = useState("");
const [resAddr2, setResAddr2] = useState("");
const [resTown, setResTown] = useState("");
const [resProvince, setResProvince] = useState("");
const [resPostalCode, setResPostalCode] = useState("");
const [postAddr1, setPostAddr1] = useState("");
const [postAddr2, setPostAddr2] = useState("");
const [postTown, setPostTown] = useState("");
const [postProvince, setPostProvince] = useState("");
const [postPostalCode, setPostPostalCode] = useState("");
// Company fields
const [enterpriseNo, setEnterpriseNo] = useState("");
const [enterpriseName, setEnterpriseName] = useState("");
const [enterpriseType, setEnterpriseType] = useState("");
const [registrationDate, setRegistrationDate] = useState("");
const [physicalAddress, setPhysicalAddress] = useState("");
const [postalAddressCo, setPostalAddressCo] = useState("");
const [companyEmail, setCompanyEmail] = useState("");
const [notesCo, setNotesCo] = useState("");
// Company structured address fields (added 2026-08-18)
const [physAddr1, setPhysAddr1] = useState("");
const [physAddr2, setPhysAddr2] = useState("");
const [physTown, setPhysTown] = useState("");
const [physProvince, setPhysProvince] = useState("");
const [physPostalCode, setPhysPostalCode] = useState("");
const [postCoAddr1, setPostCoAddr1] = useState("");
const [postCoAddr2, setPostCoAddr2] = useState("");
const [postCoTown, setPostCoTown] = useState("");
const [postCoProvince, setPostCoProvince] = useState("");
const [postCoPostalCode, setPostCoPostalCode] = useState("");
// Company-level fields (added 2026-08-18)
const [financialYearEndMonth, setFinancialYearEndMonth] = useState("");
const [authorisedShares, setAuthorisedShares] = useState("");
const [companyCellPhone, setCompanyCellPhone] = useState("");
const [publicOfficerName, setPublicOfficerName] = useState("");
const [companySecretaryName, setCompanySecretaryName] = useState("");
// SARS fields
const [sarsLoginUsername, setSarsLoginUsername] = useState("");
const [sarsLoginPassword, setSarsLoginPassword] = useState("");
const [sarsPayeNo, setSarsPayeNo] = useState("");
const [sarsVatNo, setSarsVatNo] = useState("");
const [sarsTaxNo, setSarsTaxNo] = useState("");
const [sarsVatReturnsPeriod, setSarsVatReturnsPeriod] = useState("");
const [sarsPayeReturnPeriod, setSarsPayeReturnPeriod] = useState("");
// OTP-required flag — when ON, the SARS login script expects an
// SMS one-time PIN after the password and waits for it (added 2026-09-11).
const [otpRequired, setOtpRequired] = useState(false);
// Group fields
const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
const [groupOptions, setGroupOptions] = useState<GroupOption[]>([]);
// Populate form when modal opens or record changes
useEffect(() => {
if (!open) return;
setError(null);
if (entity === "director") {
const d = record as Director;
setSurname(d.surname ?? "");
setFirstNames(d.first_names ?? "");
setIdNo(d.id_no ?? "");
setTaxReferenceNo(d.tax_reference_no != null ? String(d.tax_reference_no) : "");
setTelephone(d.telephone ?? "");
setEmail(d.email ?? "");
setPostalAddress(d.postal_address ?? "");
setResidentialAddress(d.residential_address ?? "");
setDirectorType(d.director_type ?? "");
setNotes(d.notes ?? "");
setDateOfBirth(dateInputValue(d.date_of_birth));
setResAddr1(d.residential_address_line_1 ?? "");
setResAddr2(d.residential_address_line_2 ?? "");
setResTown(d.residential_town_city ?? "");
setResProvince(d.residential_province ?? "");
setResPostalCode(d.residential_postal_code ?? "");
setPostAddr1(d.postal_address_line_1 ?? "");
setPostAddr2(d.postal_address_line_2 ?? "");
setPostTown(d.postal_town_city ?? "");
setPostProvince(d.postal_province ?? "");
setPostPostalCode(d.postal_postal_code ?? "");
} else {
const c = record as Company;
setEnterpriseNo(c.enterprise_no ?? "");
setEnterpriseName(c.enterprise_name ?? "");
setEnterpriseType(c.enterprise_type ?? "");
setRegistrationDate(dateInputValue(c.registration_date));
setPhysicalAddress(c.physical_address ?? "");
setPostalAddressCo(c.postal_address ?? "");
setCompanyEmail(c.company_email ?? "");
setNotesCo(c.notes ?? "");
setPhysAddr1(c.physical_address_line_1 ?? "");
setPhysAddr2(c.physical_address_line_2 ?? "");
setPhysTown(c.physical_town_city ?? "");
setPhysProvince(c.physical_province ?? "");
setPhysPostalCode(c.physical_postal_code ?? "");
setPostCoAddr1(c.postal_address_line_1 ?? "");
setPostCoAddr2(c.postal_address_line_2 ?? "");
setPostCoTown(c.postal_town_city ?? "");
setPostCoProvince(c.postal_province ?? "");
setPostCoPostalCode(c.postal_postal_code ?? "");
setFinancialYearEndMonth(c.financial_year_end_month != null ? String(c.financial_year_end_month) : "");
setAuthorisedShares(c.authorised_shares != null ? String(c.authorised_shares) : "");
setCompanyCellPhone(c.company_cell_phone ?? "");
setPublicOfficerName(c.public_officer_name ?? "");
setCompanySecretaryName(c.company_secretary_name ?? "");
setSarsLoginUsername(c.sars_login_username ?? "");
setSarsLoginPassword(c.sars_login_password ?? "");
setSarsPayeNo(c.sars_paye_no ?? "");
setSarsVatNo(c.sars_vat_no ?? "");
setSarsTaxNo(c.sars_tax_number ?? "");
setSarsVatReturnsPeriod(c.sars_vat_returns_period != null ? String(c.sars_vat_returns_period) : "");
setSarsPayeReturnPeriod(c.sars_paye_return_period != null ? String(c.sars_paye_return_period) : "");
setOtpRequired(Boolean(c.otp));
setSelectedGroups(c.groups ?? []);
}
}, [open, entity, record]);
// Fetch group options when modal opens (company entity only)
useEffect(() => {
if (!open || entity !== "company") return;
pbList<GroupOption>({ collection: "groups", perPage: 200, sort: "name" })
.then((res) => setGroupOptions(res.items))
.catch(() => {});
}, [open, entity]);
if (!open) return null;
const isDirector = entity === "director";
const title = isDirector ? `Edit director — ${(record as Director).surname}, ${(record as Director).first_names}` : `Edit company — ${(record as Company).enterprise_name}`;
const collection = isDirector ? "ysterbaard_directors" : "ysterbaard_companies";
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
const fields: Record<string, string | number | boolean | string[]> = {};
// Always send every field — empty string clears the value in PB.
// The previous "if (value.trim())" guard LOST the user's intent to
// clear a field: empty fields were silently skipped, so a delete in
// the UI never landed in PB. We now send the literal value the user
// left in the field, including "" to clear, and only validate
// required fields up-front.
if (isDirector) {
if (!surname.trim() || !firstNames.trim()) {
setError("Surname and first names are required.");
setLoading(false);
return;
}
fields.surname = surname.trim();
fields.first_names = firstNames.trim();
fields.id_no = idNo.trim();
// tax_reference_no: PB `number` field. Empty string means clear it
// (send 0 — PB coerces to null on the way out for `required:false`
// number fields, matching how we already handle financial_year_end
// month and authorised_shares below). Added 2026-09-16.
fields.tax_reference_no = taxReferenceNo.trim() !== "" ? Number(taxReferenceNo) : 0;
fields.telephone = telephone.trim();
fields.email = email.trim();
fields.postal_address = postalAddress.trim();
fields.residential_address = residentialAddress.trim();
fields.director_type = directorType.trim();
fields.notes = notes.trim();
// Structured address fields
fields.date_of_birth = dateOfBirth.trim();
fields.residential_address_line_1 = resAddr1.trim();
fields.residential_address_line_2 = resAddr2.trim();
fields.residential_town_city = resTown.trim();
fields.residential_province = resProvince.trim();
fields.residential_postal_code = resPostalCode.trim();
fields.postal_address_line_1 = postAddr1.trim();
fields.postal_address_line_2 = postAddr2.trim();
fields.postal_town_city = postTown.trim();
fields.postal_province = postProvince.trim();
fields.postal_postal_code = postPostalCode.trim();
} else {
if (!enterpriseNo.trim() || !enterpriseName.trim()) {
setError("Enterprise number and name are required.");
setLoading(false);
return;
}
fields.enterprise_no = enterpriseNo.trim().toUpperCase();
fields.enterprise_name = enterpriseName.trim().toUpperCase();
fields.enterprise_type = enterpriseType.trim();
fields.registration_date = registrationDate.trim();
fields.physical_address = physicalAddress.trim();
fields.postal_address = postalAddressCo.trim();
fields.company_email = companyEmail.trim();
fields.notes = notesCo.trim();
// Structured address fields
fields.physical_address_line_1 = physAddr1.trim();
fields.physical_address_line_2 = physAddr2.trim();
fields.physical_town_city = physTown.trim();
fields.physical_province = physProvince.trim();
fields.physical_postal_code = physPostalCode.trim();
fields.postal_address_line_1 = postCoAddr1.trim();
fields.postal_address_line_2 = postCoAddr2.trim();
fields.postal_town_city = postCoTown.trim();
fields.postal_province = postCoProvince.trim();
fields.postal_postal_code = postCoPostalCode.trim();
// Company-level fields
fields.financial_year_end_month = financialYearEndMonth !== "" ? Number(financialYearEndMonth) : 0;
fields.authorised_shares = authorisedShares !== "" ? Number(authorisedShares) : 0;
fields.company_cell_phone = companyCellPhone.trim();
fields.public_officer_name = publicOfficerName.trim();
fields.company_secretary_name = companySecretaryName.trim();
fields.sars_login_username = sarsLoginUsername.trim();
fields.sars_login_password = sarsLoginPassword.trim();
fields.sars_paye_no = sarsPayeNo.trim();
fields.sars_vat_no = sarsVatNo.trim();
fields.sars_tax_number = sarsTaxNo.trim();
fields.sars_vat_returns_period = sarsVatReturnsPeriod !== "" ? Number(sarsVatReturnsPeriod) : 0;
fields.sars_paye_return_period = sarsPayeReturnPeriod !== "" ? Number(sarsPayeReturnPeriod) : 0;
fields.otp = otpRequired;
// Groups — always send current selection (empty array is valid to clear)
fields.groups = selectedGroups;
}
// Pre-check for ID collisions on directors before PATCH (added 2026-08-20).
// PB's UNIQUE index on id_no_masked rejects the PATCH with a 502 if
// the typed id_no masks to a value already on another director.
// Detect this client-side and offer to open the existing record.
if (isDirector && fields.id_no && (fields.id_no as string).length >= 12) {
const typedId = fields.id_no as string;
const masked = typedId.slice(0, 6) + "XXXX" + typedId.slice(10, 12) + "X";
try {
const existing = await pbList<{ id: string; surname: string; first_names: string; id_no_masked: string }>({
collection: "ysterbaard_directors",
perPage: 5,
filter: `id_no_masked="${masked}" || id_no="${typedId}"`,
});
const conflict = existing.items.find((d) => d.id !== record.id);
if (conflict) {
setLoading(false);
setError(
`A director with this ID already exists: ${conflict.surname}, ${conflict.first_names}. ` +
`Open their record instead?`
);
return;
}
} catch {
// If pre-check fails, let the PATCH proceed and surface the 502.
}
}
await pbPatch({ collection, id: record.id, fields });
// Invalidate both list and detail caches
// Invalidate every key the detail page or list pages might be holding.
// The list pages use "companies" / "directors" keys (not "pb-list"),
// and the detail page uses "director" / "company" keys.
await qc.invalidateQueries({ queryKey: ["pb-list", collection] });
await qc.invalidateQueries({ queryKey: ["companies"] });
await qc.invalidateQueries({ queryKey: ["directors"] });
await qc.invalidateQueries({ queryKey: ["director", record.id] });
await qc.invalidateQueries({ queryKey: ["company", record.id] });
onAfterSave?.();
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
} finally {
setLoading(false);
}
};
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
role="dialog"
aria-modal="true"
aria-label={title}
>
<div className="bg-surface border border-border rounded-xl shadow-2xl w-full max-w-lg flex flex-col max-h-[90vh]">
{/* Header */}
<div className="px-6 pt-6 pb-4 border-b border-border flex-shrink-0">
<div className="flex items-start justify-between">
<div>
<h2 className="text-lg font-bold text-fg">Edit {isDirector ? "director" : "company"}</h2>
<p className="text-sm text-muted mt-0.5">{title}</p>
</div>
<button
onClick={onClose}
className="ml-4 text-muted hover:text-fg text-xl leading-none p-1 rounded transition-colors"
aria-label="Close"
>
×
</button>
</div>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto px-6 py-5 space-y-4">
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3 text-sm text-red-400">
{error}
</div>
)}
{isDirector ? (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-surname">Surname <span className="text-red-400">*</span></label>
<input id="edit-surname" type="text" value={surname}
onChange={(e) => setSurname(e.target.value.toUpperCase())}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
required />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-firstnames">First names <span className="text-red-400">*</span></label>
<input id="edit-firstnames" type="text" value={firstNames}
onChange={(e) => setFirstNames(e.target.value.toUpperCase())}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
required />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-idno">SA ID number <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-idno" type="tel" value={idNo}
onChange={(e) => setIdNo(e.target.value.replace(/\D/g, "").slice(0, 13))}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
maxLength={13} />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-taxrefno">Tax reference number <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-taxrefno" type="number" inputMode="numeric" value={taxReferenceNo}
onChange={(e) => setTaxReferenceNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 9004615283" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-dob">Date of birth <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-dob" type="date" value={dateOfBirth}
onChange={(e) => setDateOfBirth(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-tel">Telephone <span className="text-xs text-muted font-normal">(optional, digits only)</span></label>
<input id="edit-tel" type="tel" value={telephone}
onChange={(e) => setTelephone(e.target.value.replace(/\D/g, "").slice(0, 11))}
inputMode="numeric"
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-email">Email <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-email" type="email" value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-postal">CIPC Postal address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-postal" value={postalAddress} rows={2}
onChange={(e) => setPostalAddress(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-residential">Residential address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-residential" value={residentialAddress} rows={2}
onChange={(e) => setResidentialAddress(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Structured residential address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Residential address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-res1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-res1" type="text" value={resAddr1}
onChange={(e) => setResAddr1(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-res2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-res2" type="text" value={resAddr2}
onChange={(e) => setResAddr2(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-restown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-restown" type="text" value={resTown}
onChange={(e) => setResTown(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-resprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-resprov" value={resProvince}
onChange={(e) => setResProvince(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-reszip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-reszip" type="text" value={resPostalCode}
onChange={(e) => setResPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
{/* Structured postal address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Postal address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-post1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-post1" type="text" value={postAddr1}
onChange={(e) => setPostAddr1(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-post2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-post2" type="text" value={postAddr2}
onChange={(e) => setPostAddr2(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-posttown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-posttown" type="text" value={postTown}
onChange={(e) => setPostTown(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-postprov" value={postProvince}
onChange={(e) => setPostProvince(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postzip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postzip" type="text" value={postPostalCode}
onChange={(e) => setPostPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-type">Director type <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-type" type="text" value={directorType}
onChange={(e) => setDirectorType(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. Individual, Corporate" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-notes">Notes <span className="text-xs text-muted font-normal">(optional)</span></label>
<textarea id="edit-notes" value={notes} rows={2}
onChange={(e) => setNotes(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</>
) : (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-eno">Enterprise No. <span className="text-red-400">*</span></label>
<input id="edit-eno" type="text" value={enterpriseNo}
onChange={(e) => setEnterpriseNo(e.target.value.toUpperCase())}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
required />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-etype">Enterprise type <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-etype" type="text" value={enterpriseType}
onChange={(e) => setEnterpriseType(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="Private Company" />
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-ename">Enterprise name <span className="text-red-400">*</span></label>
<input id="edit-ename" type="text" value={enterpriseName}
onChange={(e) => setEnterpriseName(e.target.value.toUpperCase())}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
required />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-rdate">Registration date <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-rdate" type="date" value={registrationDate}
onChange={(e) => setRegistrationDate(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-phys">CIPC Physical address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-phys" value={physicalAddress} rows={2}
onChange={(e) => setPhysicalAddress(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-postalco">CIPC Postal address <span className="text-xs text-muted font-normal">(optional, free-text)</span></label>
<textarea id="edit-postalco" value={postalAddressCo} rows={2}
onChange={(e) => setPostalAddressCo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Structured physical address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Physical address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-phys1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-phys1" type="text" value={physAddr1}
onChange={(e) => setPhysAddr1(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-phys2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-phys2" type="text" value={physAddr2}
onChange={(e) => setPhysAddr2(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-phystown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-phystown" type="text" value={physTown}
onChange={(e) => setPhysTown(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-physprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-physprov" value={physProvince}
onChange={(e) => setPhysProvince(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-physzip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-physzip" type="text" value={physPostalCode}
onChange={(e) => setPhysPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
{/* Structured postal address (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Postal address <span className="text-xs text-muted font-normal">(structured)</span></p>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postco1">Address line 1 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postco1" type="text" value={postCoAddr1}
onChange={(e) => setPostCoAddr1(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postco2">Address line 2 <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postco2" type="text" value={postCoAddr2}
onChange={(e) => setPostCoAddr2(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postcotown">Town / city <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postcotown" type="text" value={postCoTown}
onChange={(e) => setPostCoTown(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postcoprov">Province <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-postcoprov" value={postCoProvince}
onChange={(e) => setPostCoProvince(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
{SA_PROVINCES.map((p) => (
<option key={p} value={p}>{p}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-postcozip">Postal code <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-postcozip" type="text" value={postCoPostalCode}
onChange={(e) => setPostCoPostalCode(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-emailco">Company email <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-emailco" type="email" value={companyEmail}
onChange={(e) => setCompanyEmail(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="company@example.com" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-cellco">Company cell phone <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-cellco" type="tel" value={companyCellPhone}
onChange={(e) => setCompanyCellPhone(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Company-level fields (added 2026-08-18) */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">Company details</p>
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-fye">Financial year end <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-fye" value={financialYearEndMonth}
onChange={(e) => setFinancialYearEndMonth(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
{monthNames.map((m, i) => (
<option key={i + 1} value={String(i + 1)}>{m}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-shares">Authorised shares <span className="text-xs text-muted font-normal">(optional)</span></label>
<select id="edit-shares" value={authorisedShares}
onChange={(e) => setAuthorisedShares(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
<option value="100">100</option>
<option value="1000">1,000</option>
<option value="10000">10,000</option>
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-officer">Public officer <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-officer" type="text" value={publicOfficerName}
onChange={(e) => setPublicOfficerName(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
<div>
<label className="block text-sm font-medium text-fg-2 mb-1.5" htmlFor="edit-secretary">Company secretary <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-secretary" type="text" value={companySecretaryName}
onChange={(e) => setCompanySecretaryName(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-notesco">Notes <span className="text-xs text-muted font-normal">(optional)</span></label>
<textarea id="edit-notesco" value={notesCo} rows={2}
onChange={(e) => setNotesCo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg resize-none focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" />
</div>
{/* Group membership */}
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-groups">Group Membership <span className="text-xs text-muted font-normal">(optional)</span></label>
<p className="text-xs text-muted mb-2">Hold Ctrl (or Cmd on Mac) to select multiple groups.</p>
<select
id="edit-groups"
multiple
value={selectedGroups}
onChange={(e) => {
const opts = Array.from(e.target.selectedOptions).map((o) => o.value);
setSelectedGroups(opts);
}}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent min-h-[100px]">
{groupOptions.length === 0 ? (
<option value="" disabled>No groups created yet — save and add groups via PocketBase</option>
) : (
groupOptions.map((g) => (
<option key={g.id} value={g.id}>{g.name}</option>
))
)}
</select>
</div>
{/* SARS credentials */}
<div className="border-t border-border pt-4 mt-2">
<p className="text-sm font-semibold text-fg mb-3">SARS eFiling Details</p>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-user">SARS Login Username <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-user" type="text" value={sarsLoginUsername}
onChange={(e) => setSarsLoginUsername(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="eFiling username" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-pass">SARS Login Password <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-pass" type="text" value={sarsLoginPassword}
onChange={(e) => setSarsLoginPassword(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="eFiling password" />
</div>
</div>
<div className="grid grid-cols-2 gap-4 mt-3">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-paye">SARS PAYE No. <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-paye" type="text" value={sarsPayeNo}
onChange={(e) => setSarsPayeNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 7020829828" />
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-vat">SARS VAT No. <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-vat" type="text" value={sarsVatNo}
onChange={(e) => setSarsVatNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 4123456789" />
</div>
</div>
<div className="mt-3">
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-tax">SARS Tax No. <span className="text-xs text-muted font-normal">(optional)</span></label>
<input id="edit-sars-tax" type="text" value={sarsTaxNo}
onChange={(e) => setSarsTaxNo(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
placeholder="e.g. 9004615283" />
</div>
<div className="grid grid-cols-2 gap-4 mt-3">
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-vat-period">SARS VAT Returns Period</label>
<select id="edit-sars-vat-period" value={sarsVatReturnsPeriod}
onChange={(e) => setSarsVatReturnsPeriod(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
<option value="0">None</option>
<option value="1">Every Month</option>
<option value="2">Even Month</option>
<option value="3">Uneven Months</option>
<option value="6">Bi Annually</option>
<option value="12">Annually</option>
</select>
</div>
<div>
<label className="block text-sm font-semibold text-fg mb-1.5" htmlFor="edit-sars-paye-period">SARS PAYE Returns Period</label>
<select id="edit-sars-paye-period" value={sarsPayeReturnPeriod}
onChange={(e) => setSarsPayeReturnPeriod(e.target.value)}
className="w-full rounded-lg border border-border bg-surface-warm px-3 py-2.5 text-sm text-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<option value="">— select —</option>
<option value="0">None</option>
<option value="1">Every month</option>
</select>
</div>
</div>
{/* OTP toggle (added 2026-09-11) — when ON, the SARS login
script expects an SMS one-time PIN after the password
and pauses for the agent to inject it via CDP. */}
<div className="mt-4 flex items-start gap-2">
<input
id="edit-sars-otp"
type="checkbox"
checked={otpRequired}
onChange={(e) => setOtpRequired(e.target.checked)}
className="mt-0.5 h-4 w-4 rounded border-border text-accent focus:ring-accent cursor-pointer"
/>
<label htmlFor="edit-sars-otp" className="cursor-pointer">
<span className="text-sm font-semibold text-fg">OTP</span>
<span className="block text-xs text-muted font-normal mt-0.5">
SARS sends an SMS one-time PIN on every login as this account.
Tick if you've seen this — the login script will pause and wait
for the PIN instead of waiting the full dashboard timeout.
</span>
</label>
</div>
</div>
</>
)}
<div className="pt-2 flex gap-3">
<button type="button" onClick={onClose}
className="flex-1 px-4 py-2.5 rounded-lg border border-border bg-surface-warm text-sm font-semibold text-fg hover:bg-surface transition-colors">
Cancel
</button>
<button type="submit" disabled={loading}
className="flex-1 px-4 py-2.5 rounded-lg bg-accent text-accent-on text-sm font-bold hover:bg-accent-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed">
{loading ? "Saving..." : "Save changes"}
</button>
</div>
</form>
</div>
</div>
);
}