"use client"; import * as React from "react"; import { TextFieldRoot, TextFieldInput, SwitchRoot, SwitchControl, SwitchThumb, SwitchLabel, SwitchHiddenInput, Skeleton, } from "@seed-design/react"; import { MercuryButton } from "@/ds/MercuryButton"; import { useSettings } from "@/api/hooks/reads"; import { useSaveSettings } from "@/api/hooks/mutations"; import type { Settings } from "@/api/schemas"; import { profileStrings } from "./strings"; interface FormState { holderName: string; employer: string; salaryKeywords: string; payDays: string; ownAccounts: string; peerAccounts: string; hideAmounts: boolean; } const EMPTY_FORM: FormState = { holderName: "", employer: "", salaryKeywords: "", payDays: "", ownAccounts: "", peerAccounts: "", hideAmounts: false, }; function toFormState(settings: Settings | undefined): FormState { if (!settings) return EMPTY_FORM; return { holderName: settings.holderName, employer: settings.employer, salaryKeywords: settings.salaryKeywords.join(", "), payDays: settings.payDays.join(", "), ownAccounts: settings.ownAccounts.join(", "), peerAccounts: settings.peerAccounts.join(", "), hideAmounts: settings.hideAmounts ?? false, }; } function splitList(value: string): string[] { return value .split(",") .map((v) => v.trim()) .filter((v) => v.length > 0); } function splitNumberList(value: string): number[] { return splitList(value) .map((v) => Number(v)) .filter((n) => Number.isFinite(n)); } export interface SettingsFormProps { onBack?: () => void; } /** Edits the account-level settings (holder name, employer, salary detection * keywords/pay days, own/peer account lists, hide-amounts) that back the * backend's auto-categorization — not currently exposed anywhere in the iOS * app, but commissioned for the web app per the Task 13 brief. */ export function SettingsForm({ onBack }: SettingsFormProps) { const query = useSettings(); const save = useSaveSettings(); const initialized = React.useRef(Boolean(query.data)); const [form, setForm] = React.useState(() => toFormState(query.data)); const [savedAt, setSavedAt] = React.useState(null); React.useEffect(() => { if (!initialized.current && query.data) { setForm(toFormState(query.data)); initialized.current = true; } }, [query.data]); function field(key: keyof Omit) { return { value: form[key], onValueChange: (v: string) => setForm((f) => ({ ...f, [key]: v })), }; } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSavedAt(null); await save.mutateAsync({ holderName: form.holderName.trim(), employer: form.employer.trim(), salaryKeywords: splitList(form.salaryKeywords), payDays: splitNumberList(form.payDays), ownAccounts: splitList(form.ownAccounts), peerAccounts: splitList(form.peerAccounts), hideAmounts: form.hideAmounts, }); setSavedAt(Date.now()); } if (query.isLoading && !query.data) { return (
); } return (
{onBack && ( )}

{profileStrings.settings.title}

setForm((f) => ({ ...f, hideAmounts: v }))} > {/* The actual interactive/accessible element (role="switch", checked/onChange) lives on the hidden input — SwitchControl and SwitchThumb are purely decorative (aria-hidden). Without this the switch renders but nothing is clickable or announced to assistive tech (same bug as ds/HideAmountsToggle.tsx). */} {profileStrings.settings.hideAmounts} {save.isError && (

{profileStrings.settings.error}

)} {savedAt && !save.isPending && (

{profileStrings.settings.saved}

)} {profileStrings.settings.save}
); } function Labeled({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) { return ( ); }