import { useState } from 'react' import { Button, useToast } from '@sims/ui' /** * Reveal-only secret cell: shows `••••••••` for a stored value, an owner/manager-gated * audited Reveal → Copy/Hide, and an optional inline Set/Update editor. Factors out the * three near-identical copies in ClientDetail (finding #4): * - SupportCard DB password → onReveal: revealDbPassword(id), onSave: patchClient(id, { dbPassword }) * - ServiceDataCard portal password → onReveal: revealModulePassword(cmId), onSave: patchClientModule(cmId, { password }) * - SecretField module secret → onReveal: revealModuleSecret(cmId, k), onSave: setModuleSecret(cmId, k, v) * * Adoption note: ClientDetail.tsx is currently owned by another agent, so those three * are NOT rewired here — swap them to this component in a later pass to delete the copies. */ export function SecretField(props: { /** Uppercased eyebrow label (pass `${field.label} *` yourself for required fields). */ label: string /** Whether a value is stored — drives `••••` vs "not set". */ hasValue: boolean /** Owner/manager gate: Reveal + Set/Update render only when true. */ canManage: boolean /** Decrypt-and-return the value (audited server-side). */ onReveal: () => Promise /** Store/replace the value ('' may clear server-side). Omit to hide the Set/Update editor. */ onSave?: (value: string) => Promise /** Toast shown after a successful save (default: `${label} stored (encrypted)`). */ savedMessage?: string /** Set/Update input placeholder (default: the label). */ placeholder?: string /** Extra classes for the value/input span (e.g. 'mono'). Defaults to 'mono'. */ valueClassName?: string }) { const toast = useToast() const { label, hasValue, canManage, onReveal, onSave } = props const cls = props.valueClassName ?? 'mono' const [revealed, setRevealed] = useState() const [setting, setSetting] = useState(false) const [val, setVal] = useState('') const [busy, setBusy] = useState(false) const copy = (text: string) => { navigator.clipboard.writeText(text) .then(() => toast.ok(`${label} copied`)) .catch(() => toast.err('Copy failed')) } const reveal = () => { if (busy) return setBusy(true) onReveal() .then((v) => setRevealed(v)) .catch((e: Error) => toast.err(e.message)) .finally(() => setBusy(false)) } const save = () => { if (busy || onSave === undefined) return setBusy(true) onSave(val) .then(() => { toast.ok(props.savedMessage ?? `${label} stored (encrypted)`) setSetting(false); setVal(''); setRevealed(undefined) }) .catch((e: Error) => toast.err(e.message)) .finally(() => setBusy(false)) } return ( {label} {revealed !== undefined ? ( <> {revealed} ) : hasValue ? <>••••••••{canManage && } : not set} {onSave !== undefined && canManage && !setting && ( )} {onSave !== undefined && canManage && setting && ( <> setVal(e.target.value)} /> )} ) }