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() -}