'use client' import { useState, useCallback } from 'react' import type { LinkEntry } from '@/lib/links-db' interface Props { initialEntries: LinkEntry[] } const inputStyle: React.CSSProperties = { background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.1)', color: '#e2e8f0', borderRadius: '0.625rem', padding: '0.5rem 0.75rem', fontSize: '0.875rem', outline: 'none', width: '100%', fontFamily: 'var(--font-geist-mono)', } export default function LinksClient({ initialEntries }: Props) { const [entries, setEntries] = useState(initialEntries) const [editing, setEditing] = useState>({}) const [saving, setSaving] = useState(null) const [resetting, setResetting] = useState(null) const [error, setError] = useState('') const [successMsg, setSuccessMsg] = useState('') function flash(msg: string) { setSuccessMsg(msg) setTimeout(() => setSuccessMsg(''), 3000) } const refresh = useCallback(async () => { const res = await fetch('/api/admin/links') if (res.ok) setEntries((await res.json()) as LinkEntry[]) }, []) function startEdit(key: string, current: string) { setEditing((prev) => ({ ...prev, [key]: current })) } function cancelEdit(key: string) { setEditing((prev) => { const next = { ...prev } delete next[key] return next }) setError('') } async function handleSave(key: string) { const value = (editing[key] ?? '').trim() if (!value) { setError('Value cannot be empty') return } setError('') setSaving(key) const res = await fetch('/api/admin/links', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, value }), }) const data = (await res.json()) as { error?: string } setSaving(null) if (!res.ok) { setError(data.error ?? 'Failed to save') return } cancelEdit(key) await refresh() flash('Link updated — changes are live immediately') } async function handleReset(key: string, defaultValue: string) { if (!confirm(`Reset "${key}" to default?\n\nDefault: ${defaultValue}`)) return setError('') setResetting(key) const res = await fetch(`/api/admin/links/${encodeURIComponent(key)}`, { method: 'DELETE' }) const data = (await res.json()) as { error?: string } setResetting(null) if (!res.ok) { setError(data.error ?? 'Failed to reset') return } cancelEdit(key) await refresh() flash(`"${key}" reset to default`) } // Group entries const groups = Array.from(new Set(entries.map((e) => e.group))) return (
{/* Flash messages */} {successMsg && (
{successMsg}
)} {error && (
{error}
)}

Overrides are stored in the database and take effect immediately — no redeploy needed. Any key without an override falls back to the built-in default.

{groups.map((group) => (

{group}

{entries .filter((e) => e.group === group) .map((entry) => { const isEditing = entry.key in editing const hasOverride = entry.override !== null const isSaving = saving === entry.key const isResetting = resetting === entry.key return (
{entry.label} {hasOverride && ( overridden )}

{entry.description}

{!isEditing && ( )} {hasOverride && !isEditing && ( )}
{/* Current value display */} {!isEditing && (
{entry.currentValue}
)} {/* Edit form */} {isEditing && (
setEditing((prev) => ({ ...prev, [entry.key]: e.target.value }))} onKeyDown={(e) => { if (e.key === 'Enter') handleSave(entry.key) if (e.key === 'Escape') cancelEdit(entry.key) }} autoFocus style={inputStyle} placeholder={entry.defaultValue} /> {hasOverride && (

Default: {entry.defaultValue}

)}
)}
) })}
))}
) }