From 0ccacecd8a8b20c61634ae1b0ba7efd14e7b5454 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 22 Jan 2026 05:21:30 -0800 Subject: [PATCH] feat: add accent color setting and apply it dynamically across components --- src/app/admin/page.tsx | 20 ++++++++++++++++++++ src/app/api/admin/node/route.ts | 1 + src/app/login/page.tsx | 3 ++- src/components/Icons.tsx | 28 ++++++++++++++++++++++++---- src/components/LayoutWrapper.tsx | 32 ++++++++++++++++++++++++++++++++ src/components/RightSidebar.tsx | 20 ++++++++------------ src/components/Sidebar.tsx | 4 ++-- 7 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index c053712..d5fd957 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -61,6 +61,7 @@ export default function AdminPage() { longDescription: '', rules: '', bannerUrl: '', + accentColor: '#00D4AA', }); const [savingSettings, setSavingSettings] = useState(false); const [isUploadingBanner, setIsUploadingBanner] = useState(false); @@ -123,6 +124,7 @@ export default function AdminPage() { longDescription: data.longDescription || '', rules: data.rules || '', bannerUrl: data.bannerUrl || '', + accentColor: data.accentColor || '#00D4AA', }); } catch { // error @@ -501,6 +503,24 @@ export default function AdminPage() { /> +
+ +
+ setNodeSettings({ ...nodeSettings, accentColor: e.target.value })} + style={{ width: '44px', height: '36px', padding: 0, border: '1px solid var(--border)', background: 'transparent', borderRadius: '8px' }} + /> + setNodeSettings({ ...nodeSettings, accentColor: e.target.value })} + placeholder="#00D4AA" + /> +
+
+
diff --git a/src/app/api/admin/node/route.ts b/src/app/api/admin/node/route.ts index 1f2f1a6..4fb1dc9 100644 --- a/src/app/api/admin/node/route.ts +++ b/src/app/api/admin/node/route.ts @@ -33,6 +33,7 @@ export async function PATCH(req: NextRequest) { longDescription: data.longDescription, rules: data.rules, bannerUrl: data.bannerUrl, + accentColor: data.accentColor, updatedAt: new Date(), }) .where(eq(nodes.id, node.id)) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 079c243..f2cd3ff 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { SynapsisLogo } from '@/components/Icons'; export default function LoginPage() { const router = useRouter(); @@ -58,7 +59,7 @@ export default function LoginPage() { {/* Logo */}
- Synapsis + Synapsis

diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 2e0ba44..a0269ed 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -79,10 +79,30 @@ export const UsersIcon = () => ( ); export const SynapsisLogo = () => ( - - - - + + + + + + + + + + + + + + ); diff --git a/src/components/LayoutWrapper.tsx b/src/components/LayoutWrapper.tsx index bdbeebe..d0f60f5 100644 --- a/src/components/LayoutWrapper.tsx +++ b/src/components/LayoutWrapper.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect } from 'react'; import { usePathname } from 'next/navigation'; import { Sidebar } from './Sidebar'; import { RightSidebar } from './RightSidebar'; @@ -14,6 +15,37 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { pathname?.startsWith('/install') || pathname?.startsWith('/admin'); + useEffect(() => { + const applyAccent = (color?: string | null) => { + if (!color) return; + const cleaned = color.trim(); + const normalized = cleaned.startsWith('#') ? cleaned : `#${cleaned}`; + const hexMatch = /^#([0-9a-fA-F]{6})$/.exec(normalized); + if (!hexMatch) return; + + const hex = hexMatch[1]; + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + + const mix = (channel: number, target: number, amount: number) => + Math.round(channel + (target - channel) * amount); + + const hover = `rgb(${mix(r, 255, 0.12)}, ${mix(g, 255, 0.12)}, ${mix(b, 255, 0.12)})`; + const muted = `rgba(${r}, ${g}, ${b}, 0.12)`; + + const root = document.documentElement; + root.style.setProperty('--accent', `#${hex}`); + root.style.setProperty('--accent-hover', hover); + root.style.setProperty('--accent-muted', muted); + }; + + fetch('/api/node', { cache: 'no-store' }) + .then((res) => res.json()) + .then((data) => applyAccent(data?.accentColor)) + .catch(() => { }); + }, []); + if (isStandalone) { return <>{children}; } diff --git a/src/components/RightSidebar.tsx b/src/components/RightSidebar.tsx index 19f288f..7794257 100644 --- a/src/components/RightSidebar.tsx +++ b/src/components/RightSidebar.tsx @@ -31,22 +31,18 @@ export function RightSidebar() { return (

+
{nodeInfo.bannerUrl && ( - <> -
-
- + borderBottom: '1px solid var(--border)', + }} + /> )} -
+

Welcome to {nodeInfo.name}

{nodeInfo.description} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 8b2823d..1233a63 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -3,7 +3,7 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useAuth } from '@/lib/contexts/AuthContext'; -import { HomeIcon, SearchIcon, BellIcon, UserIcon, ShieldIcon } from './Icons'; +import { HomeIcon, SearchIcon, BellIcon, UserIcon, ShieldIcon, SynapsisLogo } from './Icons'; export function Sidebar() { const { user, isAdmin } = useAuth(); @@ -15,7 +15,7 @@ export function Sidebar() { return (