import { useEffect, useRef, useState } from 'react' import { api, ApiError, type Integration } from '../lib/api' import { User, Palette, Plug, Bell, Database, Info, Eye, EyeOff, Check, Download, Upload, Trash2, RotateCcw, Camera, } from 'lucide-react' const navSections = [ { id: 'profile', label: 'Profile', icon: User }, { id: 'appearance', label: 'Appearance', icon: Palette }, { id: 'integrations', label: 'Integrations', icon: Plug }, { id: 'notifications', label: 'Notifications', icon: Bell }, { id: 'data', label: 'Data & Backup', icon: Database }, { id: 'about', label: 'About', icon: Info }, ] const accentColors = [ { name: 'Gold', color: '#C8A434' }, { name: 'Teal', color: '#2DD4BF' }, { name: 'Purple', color: '#A855F7' }, { name: 'Blue', color: '#3B82F6' }, { name: 'Green', color: '#2ECC71' }, { name: 'Red', color: '#E74C3C' }, ] type FieldDef = { key: string; label: string; secret?: boolean } const integrationTypeDefs: { type: string; name: string; fields: FieldDef[] }[] = [ { type: 'proxmox', name: 'Proxmox', fields: [{ key: 'baseUrl', label: 'Host URL' }, { key: 'apiKey', label: 'API Token', secret: true }] }, { type: 'docker', name: 'Docker', fields: [{ key: 'baseUrl', label: 'Socket / Remote URL' }] }, { type: 'netbird', name: 'NetBird', fields: [{ key: 'apiKey', label: 'API Key', secret: true }] }, { type: 'cloudflare', name: 'Cloudflare', fields: [{ key: 'apiKey', label: 'API Token', secret: true }, { key: 'zoneId', label: 'Zone ID' }] }, { type: 'aws', name: 'AWS', fields: [{ key: 'accessKey', label: 'Access Key' }, { key: 'secretKey', label: 'Secret', secret: true }, { key: 'region', label: 'Region' }] }, { type: 'uptime_kuma', name: 'Uptime Kuma', fields: [{ key: 'baseUrl', label: 'URL' }, { key: 'apiKey', label: 'API Key', secret: true }] }, { type: 'weather', name: 'Weather API', fields: [{ key: 'location', label: 'Location' }, { key: 'units', label: 'Units' }] }, ] const cardBase: React.CSSProperties = { backgroundColor: 'rgba(10, 10, 12, 0.92)', border: '1px solid rgba(200, 164, 52, 0.08)', borderRadius: '12px', padding: '22px', position: 'relative', } const sectionTitle: React.CSSProperties = { fontSize: '11px', textTransform: 'uppercase', letterSpacing: '1.5px', color: '#7A7D85', fontWeight: 500, marginBottom: '16px', } const labelStyle: React.CSSProperties = { fontSize: '11px', color: '#7A7D85', marginBottom: '6px', display: 'block', } const inputStyle: React.CSSProperties = { width: '100%', height: '34px', borderRadius: '8px', border: '1px solid rgba(200,164,52,0.12)', backgroundColor: 'rgba(255,255,255,0.03)', color: '#E8E6E0', fontSize: '12px', padding: '0 12px', outline: 'none', } function Toggle({ on, onClick }: { on: boolean; onClick: () => void }) { return ( ) } function GoldButton({ children, danger }: { children: React.ReactNode; danger?: boolean }) { return ( ) } function ProfileSection() { const [avatar, setAvatar] = useState(null) const fileInputRef = useRef(null) function handleAvatarChange(e: React.ChangeEvent) { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() reader.onload = () => setAvatar(reader.result as string) reader.readAsDataURL(file) } return (

Profile

fileInputRef.current?.click()} className="relative rounded-full border-2 flex items-center justify-center font-bold cursor-pointer group" style={{ width: '64px', height: '64px', borderColor: '#C8A434', color: '#C8A434', fontSize: '20px', backgroundColor: 'rgba(200,164,52,0.08)', backgroundImage: avatar ? `url(${avatar})` : undefined, backgroundSize: 'cover', backgroundPosition: 'center', overflow: 'hidden', }} title="Upload photo" > {!avatar && 'AO'}
ArchNest Ops Administrator
admin@archnest.io
Save Changes
) } function AppearanceSection() { const [theme, setTheme] = useState<'dark' | 'light'>('dark') const [accent, setAccent] = useState('Gold') const [fontSize, setFontSize] = useState(13) const [radius, setRadius] = useState(12) const [sidebarExpanded, setSidebarExpanded] = useState(true) const [animations, setAnimations] = useState(true) return (

Appearance

Theme
{(['dark', 'light'] as const).map((t) => ( ))}
Accent Color
{accentColors.map((a) => ( ))}
Font Size {fontSize}px
setFontSize(Number(e.target.value))} className="w-full" style={{ accentColor: '#C8A434' }} />
Card Border Radius {radius}px
setRadius(Number(e.target.value))} className="w-full" style={{ accentColor: '#C8A434' }} />
Sidebar Expanded by Default setSidebarExpanded((v) => !v)} />
Animations setAnimations((v) => !v)} />
) } function IntegrationsSection() { const [integrations, setIntegrations] = useState(null) const [revealed, setRevealed] = useState>(new Set()) const [drafts, setDrafts] = useState>>({}) const [statusMsg, setStatusMsg] = useState>({}) const [busy, setBusy] = useState>(new Set()) useEffect(() => { api.listIntegrations().then(({ integrations }) => setIntegrations(integrations)) }, []) function toggleReveal(key: string) { setRevealed((prev) => { const next = new Set(prev) if (next.has(key)) next.delete(key) else next.add(key) return next }) } function setBusyFlag(type: string, value: boolean) { setBusy((prev) => { const next = new Set(prev) if (value) next.add(type) else next.delete(type) return next }) } function setDraftField(type: string, fieldKey: string, value: string) { setDrafts((prev) => ({ ...prev, [type]: { ...prev[type], [fieldKey]: value } })) } async function handleSave(def: (typeof integrationTypeDefs)[number], existing: Integration | undefined) { setBusyFlag(def.type, true) setStatusMsg((prev) => ({ ...prev, [def.type]: '' })) try { const draft = drafts[def.type] ?? {} const config: Record = {} const secrets: Record = {} for (const f of def.fields) { const value = draft[f.key] if (value === undefined) continue if (f.secret) secrets[f.key] = value else config[f.key] = value } let integration: Integration if (existing) { ;({ integration } = await api.updateIntegration(existing.id, { config, secrets })) } else { ;({ integration } = await api.createIntegration({ type: def.type, name: def.name, config, secrets })) } setIntegrations((prev) => { const others = (prev ?? []).filter((i) => i.id !== integration.id) return [...others, integration] }) setStatusMsg((prev) => ({ ...prev, [def.type]: 'Saved' })) } catch (err) { setStatusMsg((prev) => ({ ...prev, [def.type]: err instanceof ApiError ? err.message : 'Save failed' })) } finally { setBusyFlag(def.type, false) } } async function handleTest(def: (typeof integrationTypeDefs)[number], existing: Integration | undefined) { if (!existing) { setStatusMsg((prev) => ({ ...prev, [def.type]: 'Save the integration before testing' })) return } setBusyFlag(def.type, true) try { const result = await api.testIntegration(existing.id) setStatusMsg((prev) => ({ ...prev, [def.type]: result.message })) const { integrations } = await api.listIntegrations() setIntegrations(integrations) } catch (err) { setStatusMsg((prev) => ({ ...prev, [def.type]: err instanceof ApiError ? err.message : 'Test failed' })) } finally { setBusyFlag(def.type, false) } } if (!integrations) { return (

Loading integrations…

) } return (
{integrationTypeDefs.map((def) => { const existing = integrations.find((i) => i.type === def.type) const online = existing?.status === 'connected' const draft = drafts[def.type] ?? {} return (
{def.name}
{statusMsg[def.type] && ( {statusMsg[def.type]} )}
{def.fields.map((f) => { const key = `${def.type}-${f.key}` const isRevealed = revealed.has(key) const savedValue = f.secret ? '' : existing?.config[f.key] ?? '' const value = draft[f.key] ?? savedValue return (
setDraftField(def.type, f.key, e.target.value)} placeholder={f.secret && existing ? '••••••••••••' : 'Not configured'} /> {f.secret && ( )}
) })}
) })}
) } function NotificationsSection() { const [enabled, setEnabled] = useState(true) const [email, setEmail] = useState(true) const [push, setPush] = useState(false) const [sound, setSound] = useState(true) return (

Notifications

Enable Notifications setEnabled((v) => !v)} />
Email Notifications setEmail((v) => !v)} />
Browser Push setPush((v) => !v)} />
Sound setSound((v) => !v)} />
{sound && ( )}
) } function DataBackupSection() { return (

Data & Backup

Export Bookmarks (JSON) Export
Import Bookmarks (JSON) Import
Export Settings Export
Clear Cache Clear
Reset to Defaults Reset
) } function AboutSection() { const rows: [string, string][] = [ ['App', 'ArchNest Dashboard v1.0.0'], ['Author', 'Samuel James'], ['Repo', 'github.com/SamuelSJames/archnest'], ['Stack', 'React 19, Vite, TypeScript'], ['License', 'MIT'], ] return (

About

{rows.map(([label, value]) => (
{label} {value}
))}
) } const sectionComponents: Record JSX.Element> = { profile: ProfileSection, appearance: AppearanceSection, integrations: IntegrationsSection, notifications: NotificationsSection, data: DataBackupSection, about: AboutSection, } export default function Settings() { const [active, setActive] = useState('profile') const ActiveSection = sectionComponents[active] return (
{/* Settings nav */}
{navSections.map((s) => { const Icon = s.icon const isActive = active === s.id return ( ) })}
{/* Content */}
) }