import React, { useId } from 'react' import { HardDrive } from 'lucide-react' import { STORAGE_BACKEND_TYPE_IDS, type StorageBackend, type StorageBackendFieldDef, type StorageBackendTypeId, } from '@trek/shared' import { useTranslation } from '../../../i18n' import CustomSelect from '../../shared/CustomSelect' import { DialogButton, DialogFooter, DialogHeader, DialogSection, DialogShell, DialogTile, FooterSpacer, NEUTRAL_TINT, fs, } from '../../shared/DialogShell' import { EditorField, GRID_2, INPUT } from '../../shared/dialogParts' import { useBackendForm } from './useBackendForm' export interface BackendFormMirrorProps { /** Selectable replica targets (the form excludes its own name at render). */ candidates: string[] initialTargets: string[] } interface BackendFormProps { /** null = new backend */ initial: StorageBackend | null /** Every defined backend name — mirror-target options and the duplicate pre-check. */ backendNames: string[] /** * Present on non-mirror backends: renders the Mirror-targets composer * (replicas-on-primary — panel-supplied chrome, never a registry field). * When present, onCommit's second argument is always an array. */ mirror?: BackendFormMirrorProps onCommit: (backend: StorageBackend, mirrorTargets?: string[]) => void onCancel: () => void } /** A box of checkbox rows, the white list the settings cards use. */ const CHECK_ROWS = 'divide-y divide-edge-faint overflow-hidden rounded-[12px] border border-edge-faint bg-surface-card' const CHECK_ROW = 'flex cursor-pointer items-center gap-3 px-3.5 py-2.5 text-content hover:bg-surface-secondary' const CHECKBOX = 'h-4 w-4 flex-none cursor-pointer accent-accent' /** * Renders whatever STORAGE_BACKEND_TYPES declares, by field kind. The raw * `mirror` type is hidden from the type select — mirrors are composed via the * Mirror-targets prop block and synthesized by the panels (the ref-kind * renderers below stay for future registry types). Drawn as the planner's * editor dialog: head band, fields under eyebrows, the answers in the footer. */ export default function BackendForm({ initial, backendNames, mirror, onCommit, onCancel, }: BackendFormProps): React.ReactElement { const { t } = useTranslation() const labelId = useId() const fieldId = (key: string) => `${labelId}-${key}` const { type, changeType, name, setName, values, setValue, toggleRef, targets, toggleTarget, fields, refOptions, duplicate, canApply, buildBackend, visibleCandidates, } = useBackendForm({ initial, backendNames, mirror }) const apply = () => { const payload = buildBackend() // Arity matters: the landed tests pin single-argument calls when no mirror // block is supplied (toHaveBeenCalledWith treats a trailing undefined as a // mismatch), so the second argument exists only when the composer does. if (mirror) onCommit(payload, targets) else onCommit(payload) } const renderField = (field: StorageBackendFieldDef): React.ReactElement => { const value = values[field.key] if (field.kind === 'backend-ref') { return ( setValue(field.key, String(next))} options={refOptions.map((candidate) => ({ value: candidate, label: candidate }))} placeholder={t(field.labelKey)} size="sm" /> ) } if (field.kind === 'backend-ref-list') { const selected = Array.isArray(value) ? value : [] return (
{refOptions.map((candidate) => ( ))}
) } const inputType = field.kind === 'secret' ? 'password' : field.kind === 'number' ? 'number' : 'text' return ( setValue(field.key, e.target.value)} placeholder={field.defaultValue !== undefined ? String(field.defaultValue) : ''} spellCheck={false} autoComplete="off" className={`${INPUT} ${inputType === 'number' ? 'tabular-nums' : ''}`} /> ) } return ( } tint={NEUTRAL_TINT} labelId={labelId} onClose={onCancel} title={initial ? t('storage.form.editTitle') : t('storage.form.addTitle')} /> } footer={ {t('storage.form.cancel')} {t('storage.form.apply')} } >
setName(e.target.value)} spellCheck={false} autoComplete="off" className={`${INPUT} font-geist`} /> {duplicate && (

{t('storage.form.duplicateName', { name: name.trim() })}

)}
changeType(next as StorageBackendTypeId)} options={STORAGE_BACKEND_TYPE_IDS.filter((id) => id !== 'mirror').map((id) => ({ value: id, label: t(`storage.type.${id}`), }))} size="sm" disabled={initial !== null} />
{fields.length > 0 &&
{fields.map(renderField)}
} {mirror && (

{t('storage.mirror.targetsHelp')}

{visibleCandidates.length > 0 && (
{visibleCandidates.map((candidate) => ( ))}
)} {targets.length > 0 && (

{t('storage.mirror.latencyNote')}

)}
)}
) }