From 6b73ca364586b23f7668266824e12d98aeb29757 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Sat, 15 Aug 2026 21:19:41 +0200 Subject: [PATCH] Put settings in the sidebar and let the sidebar frost composite The footer was a user card with the theme picker and log out buried in its dropdown. It is now two plain entries, Settings and Log Out, and the theme moved into the settings page as its own tab. The dark sidebar never matched the canvas because _canvas.tsx painted bg-card across the whole viewport underneath it, so the floating panel's bg-card/80 resolved to card over card and its gutter was card too. Nothing there should paint a surface; the panel now reads the same as the other frosted chrome. The superuser guard sliced the first three tabs, which silently changed meaning when a fourth arrived. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KkmeRiyeYmVZqJVwuyHq9o --- frontend/src/components/Common/Appearance.tsx | 59 +------------------ .../src/components/Sidebar/AppSidebar.tsx | 21 ++++--- frontend/src/components/Sidebar/Main.tsx | 29 ++++++--- .../components/UserSettings/Appearance.tsx | 50 ++++++++++++++++ frontend/src/routes/_canvas.tsx | 9 ++- frontend/src/routes/_layout/settings.tsx | 5 +- frontend/src/utils.ts | 9 --- 7 files changed, 94 insertions(+), 88 deletions(-) create mode 100644 frontend/src/components/UserSettings/Appearance.tsx diff --git a/frontend/src/components/Common/Appearance.tsx b/frontend/src/components/Common/Appearance.tsx index 1c56f6c..7e2a67c 100644 --- a/frontend/src/components/Common/Appearance.tsx +++ b/frontend/src/components/Common/Appearance.tsx @@ -1,6 +1,6 @@ import { Monitor, Moon, Sun } from "lucide-react" -import { type Theme, useTheme } from "@/components/theme-provider" +import { useTheme } from "@/components/theme-provider" import { Button } from "@/components/ui/button" import { DropdownMenu, @@ -8,63 +8,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "@/components/ui/sidebar" - -type LucideIcon = React.FC> - -const ICON_MAP: Record = { - system: Monitor, - light: Sun, - dark: Moon, -} - -export const SidebarAppearance = () => { - const { isMobile } = useSidebar() - const { setTheme, theme } = useTheme() - const Icon = ICON_MAP[theme] - - return ( - - - - - - Appearance - Toggle theme - - - - setTheme("light")} - > - - Light - - setTheme("dark")} - > - - Dark - - setTheme("system")}> - - System - - - - - ) -} export const Appearance = () => { const { setTheme } = useTheme() diff --git a/frontend/src/components/Sidebar/AppSidebar.tsx b/frontend/src/components/Sidebar/AppSidebar.tsx index 7f91df6..ca55afc 100644 --- a/frontend/src/components/Sidebar/AppSidebar.tsx +++ b/frontend/src/components/Sidebar/AppSidebar.tsx @@ -1,6 +1,5 @@ -import { Home, Users, Workflow } from "lucide-react" +import { Home, LogOut, Settings, Users, Workflow } from "lucide-react" -import { SidebarAppearance } from "@/components/Common/Appearance" import { Logo } from "@/components/Common/Logo" import { Sidebar, @@ -11,7 +10,6 @@ import { } from "@/components/ui/sidebar" import useAuth from "@/hooks/useAuth" import { type Item, Main } from "./Main" -import { User } from "./User" const baseItems: Item[] = [ { icon: Home, title: "Dashboard", path: "/" }, @@ -19,15 +17,20 @@ const baseItems: Item[] = [ ] export function AppSidebar() { - const { user: currentUser } = useAuth() + const { user: currentUser, logout } = useAuth() const items = currentUser?.is_superuser ? [...baseItems, { icon: Users, title: "Admin", path: "/admin" }] : baseItems + const footerItems: Item[] = [ + { icon: Settings, title: "Settings", path: "/settings" }, + { icon: LogOut, title: "Log Out", onClick: logout }, + ] + return ( - // Floating frosted chrome over the bg-card content region; see the root - // DESIGN-GUIDELINES.md → Shells and → Overlay surfaces & content chips. + // Floating frosted chrome over whatever surface the shell paints; see the + // root DESIGN-GUIDELINES.md → Shells and → Overlay surfaces & content chips.
- - - + {/* Main already pads horizontally; the footer only adds the bottom gap. */} + +
) diff --git a/frontend/src/components/Sidebar/Main.tsx b/frontend/src/components/Sidebar/Main.tsx index 6bb5c5e..e361a62 100644 --- a/frontend/src/components/Sidebar/Main.tsx +++ b/frontend/src/components/Sidebar/Main.tsx @@ -10,10 +10,12 @@ import { useSidebar, } from "@/components/ui/sidebar" +/** A nav entry is either a route (`path`) or an action (`onClick`), never both. */ export type Item = { icon: LucideIcon title: string - path: string + path?: string + onClick?: () => void } interface MainProps { @@ -36,22 +38,31 @@ export function Main({ items }: MainProps) { {items.map((item) => { - const isActive = - item.path === "/" + const path = item.path + const isActive = path + ? path === "/" ? currentPath === "/" - : currentPath.startsWith(item.path) + : currentPath.startsWith(path) + : false + const label = ( + <> + + {item.title} + + ) return ( { + handleMenuClick() + item.onClick?.() + }} > - - - {item.title} - + {path ? {label} : label} ) diff --git a/frontend/src/components/UserSettings/Appearance.tsx b/frontend/src/components/UserSettings/Appearance.tsx new file mode 100644 index 0000000..e0aa118 --- /dev/null +++ b/frontend/src/components/UserSettings/Appearance.tsx @@ -0,0 +1,50 @@ +import { type LucideIcon, Monitor, Moon, Sun } from "lucide-react" + +import { type Theme, useTheme } from "@/components/theme-provider" +import { cn } from "@/lib/utils" + +const options: { value: Theme; title: string; icon: LucideIcon }[] = [ + { value: "light", title: "Light", icon: Sun }, + { value: "dark", title: "Dark", icon: Moon }, + { value: "system", title: "System", icon: Monitor }, +] + +const Appearance = () => { + const { theme, setTheme } = useTheme() + + return ( +
+

Appearance

+

+ Choose how Fluksio looks to you. System follows your device setting. +

+ {/* The one segmented shape: a single border pill, transparent segments, + bg-accent on the selected one (root DESIGN-GUIDELINES.md). */} +
+ {options.map((option) => ( + + ))} +
+
+ ) +} + +export default Appearance diff --git a/frontend/src/routes/_canvas.tsx b/frontend/src/routes/_canvas.tsx index 4e9813e..997c4a1 100644 --- a/frontend/src/routes/_canvas.tsx +++ b/frontend/src/routes/_canvas.tsx @@ -8,6 +8,11 @@ import { isLoggedIn } from "@/hooks/useAuth" * The full-bleed shell. Same frosted sidebar as `_layout`, but the content * region is the whole viewport: the flow editor floats its own chrome over the * canvas instead of sitting inside a padded column. + * + * Nothing here paints a surface. The canvas is `--background`, so an opaque + * `bg-card` on the shell would sit *under* the floating sidebar and turn its + * `bg-card/80 backdrop-blur-md` into a plain `bg-card` slab with a card-coloured + * moat around it — the frost has to composite against the canvas to read right. */ export const Route = createFileRoute("/_canvas")({ component: CanvasLayout, @@ -20,9 +25,9 @@ export const Route = createFileRoute("/_canvas")({ function CanvasLayout() { return ( - + - +
diff --git a/frontend/src/routes/_layout/settings.tsx b/frontend/src/routes/_layout/settings.tsx index 41d9729..fd473fc 100644 --- a/frontend/src/routes/_layout/settings.tsx +++ b/frontend/src/routes/_layout/settings.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from "@tanstack/react-router" +import Appearance from "@/components/UserSettings/Appearance" import ChangePassword from "@/components/UserSettings/ChangePassword" import DeleteAccount from "@/components/UserSettings/DeleteAccount" import UserInformation from "@/components/UserSettings/UserInformation" @@ -9,6 +10,7 @@ import useAuth from "@/hooks/useAuth" const tabsConfig = [ { value: "my-profile", title: "My profile", component: UserInformation }, { value: "password", title: "Password", component: ChangePassword }, + { value: "appearance", title: "Appearance", component: Appearance }, { value: "danger-zone", title: "Danger zone", component: DeleteAccount }, ] @@ -25,8 +27,9 @@ export const Route = createFileRoute("/_layout/settings")({ function UserSettings() { const { user: currentUser } = useAuth() + // A superuser deleting its own account would lock everyone out. const finalTabs = currentUser?.is_superuser - ? tabsConfig.slice(0, 3) + ? tabsConfig.filter((tab) => tab.value !== "danger-zone") : tabsConfig if (!currentUser) { diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index fa491eb..15d2492 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -20,12 +20,3 @@ export const handleError = function ( const errorMessage = extractErrorMessage(err) this(errorMessage) } - -export const getInitials = (name: string): string => { - return name - .split(" ") - .slice(0, 2) - .map((word) => word[0]) - .join("") - .toUpperCase() -}