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 ?? ""}
)
}