import { Eye, EyeOff, X } from "lucide-react" import { useState } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Switch } from "@/components/ui/switch" import { useDictionary } from "@/hooks/use-dictionary" import type { SettingDef } from "@/lib/admin/settings-registry" import { formatMessage } from "@/lib/i18n/utils" import { cn } from "@/lib/utils" import { isSecretValue, type SecretValue, type SettingState, savedTextOf, } from "./admin-shared" // ── Small shared UI bits ───────────────────────────────────────────── export function SourceChip({ source }: { source: "file" | "env" | "default" }) { const dict = useDictionary() if (source === "default") return null return ( {source === "file" ? dict.admin.sourceSaved : dict.admin.sourceEnv} ) } export function RestartBadge() { const dict = useDictionary() return ( {dict.admin.restartRequired} ) } // Secret input: shows masked hint as placeholder, typing replaces. // With keepOnEmpty, clearing the field reverts to the stored value // ("keep") instead of deleting it — explicit deletion is via the X button. export function SecretInput({ id, value, disabled, keepOnEmpty, onChange, }: { id: string value: string | SecretValue | undefined disabled?: boolean keepOnEmpty?: boolean onChange: (value: string | SecretValue) => void }) { const dict = useDictionary() const [show, setShow] = useState(false) // The stored marker as it was at mount, to revert to on empty const [original] = useState(value) const hadStored = isSecretValue(original) const text = typeof value === "string" ? value : "" const placeholder = isSecretValue(value) ? formatMessage(dict.admin.savedReplace, { hint: value.hint }) : dict.admin.notSet const handleText = (t: string) => { if (t === "" && keepOnEmpty && hadStored && original) { onChange(original) } else { onChange(t) } } return (
handleText(e.target.value)} /> {keepOnEmpty && (hadStored || text) && !disabled && ( )}
) } // ── General settings field (registry-driven) ───────────────────────── export function SettingField({ def, state, pendingValue, error, disabled, onChange, }: { def: SettingDef state: SettingState | undefined pendingValue: string | null | undefined error?: string disabled: boolean onChange: (value: string | null) => void }) { const dict = useDictionary() const isDirty = pendingValue !== undefined const source = state?.source ?? "default" const currentValue = isDirty ? (pendingValue ?? "") : savedTextOf(state) const secretState = state && isSecretValue(state.value) ? state.value : null // Localized label/description keyed by env var name, falling back to the // registry's English (the registry stays canonical for the server). const t = ( dict.admin.settings as Record< string, { label?: string; description?: string } | undefined > )[def.key] const label = t?.label ?? def.label const description = t?.description ?? def.description const inputId = `setting-${def.key}` const errorId = `${inputId}-error` let control: React.ReactNode switch (def.type) { case "boolean": { // When unset, reflect the built-in runtime default so the toggle // matches actual behavior (e.g. ALLOW_PRIVATE_URLS defaults on). const effective = currentValue !== "" ? currentValue : (def.default ?? "false") // A saved boolean can be cleared back to its env/default value. const canClear = (isDirty && pendingValue !== null) || source === "file" control = (
onChange(checked ? "true" : "false") } /> {canClear && !disabled && ( )}
) break } case "enum": control = ( ) break case "secret": control = (
onChange(typeof v === "string" ? v : "") } />
) break case "number": control = ( onChange(e.target.value)} /> ) break default: control = ( onChange(e.target.value)} /> ) } return (
{def.restartRequired && } {isDirty && ( {dict.admin.modified} )}
{description && (

{description}

)} {control}

{error ?? ""}

) }