From e386e327b4d4c09ee198c022bd7133526b828f46 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 18:44:26 +0000 Subject: [PATCH] Add Settings page with Profile, Appearance, Integrations, Notifications, Data & Backup, About sections --- src/App.tsx | 2 + src/pages/Settings.tsx | 509 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+) create mode 100644 src/pages/Settings.tsx diff --git a/src/App.tsx b/src/App.tsx index ced66cc..e66c51b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import TopBar from './components/TopBar' import Glance from './pages/Glance' import Infrastructure from './pages/Infrastructure' import BookNest from './pages/BookNest' +import Settings from './pages/Settings' function App() { const [sidebarCollapsed, setSidebarCollapsed] = useState(false) @@ -60,6 +61,7 @@ function App() { } /> } /> } /> + } /> diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 0000000..8522658 --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,509 @@ +import { useState } from 'react' +import { + User, + Palette, + Plug, + Bell, + Database, + Info, + Eye, + EyeOff, + Check, + Download, + Upload, + Trash2, + RotateCcw, +} 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 IntegrationField = { label: string; value: string; secret?: boolean } + +const integrations: { name: string; online: boolean; fields: IntegrationField[] }[] = [ + { name: 'Proxmox', online: true, fields: [{ label: 'Host URL', value: 'https://pve1.local:8006' }, { label: 'API Token', value: '••••••••••••', secret: true }] }, + { name: 'Docker', online: true, fields: [{ label: 'Socket / Remote URL', value: 'unix:///var/run/docker.sock' }] }, + { name: 'NetBird', online: true, fields: [{ label: 'API Key', value: '••••••••••••', secret: true }] }, + { name: 'Cloudflare', online: true, fields: [{ label: 'API Token', value: '••••••••••••', secret: true }, { label: 'Zone ID', value: 'a1b2c3d4e5f6' }] }, + { name: 'AWS', online: true, fields: [{ label: 'Access Key', value: 'AKIA••••••••' }, { label: 'Secret', value: '••••••••••••', secret: true }, { label: 'Region', value: 'us-east-1' }] }, + { name: 'Uptime Kuma', online: false, fields: [{ label: 'URL', value: '' }, { label: 'API Key', value: '', secret: true }] }, + { name: 'Weather API', online: true, fields: [{ label: 'Location', value: 'Charlotte, NC' }, { label: 'Units', value: 'Imperial' }] }, +] + +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() { + return ( +
+

Profile

+
+
+ 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 [revealed, setRevealed] = useState>(new Set()) + + function toggleReveal(key: string) { + setRevealed((prev) => { + const next = new Set(prev) + if (next.has(key)) next.delete(key) + else next.add(key) + return next + }) + } + + return ( +
+ {integrations.map((integ) => ( +
+
+
+ + {integ.name} +
+ +
+
+ {integ.fields.map((f) => { + const key = `${integ.name}-${f.label}` + const isRevealed = revealed.has(key) + return ( +
+ +
+ + {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 */} +
+ +
+
+ ) +}