fix: theme toggle

This commit is contained in:
ghost2023
2026-08-10 11:52:03 +03:00
parent 84081156f6
commit 10af12bfc6
18 changed files with 47 additions and 341 deletions

View File

@@ -1,11 +1,13 @@
@import "tailwindcss";
@import "@edr/ui-common/theme.css" layer(theme);
/* The app toggles dark mode by setting the `dark` class on <html> (see
main.tsx / FreightDashboardLayout). Without this, Tailwind v4 compiles
`dark:` utilities to `@media (prefers-color-scheme: dark)` and they follow
the OS setting instead of the in-app toggle. */
@custom-variant dark (&:where(.dark, .dark *));
/* The backoffice is light-only — there is no theme toggle and nothing sets the
`dark` class. This override is load-bearing: without it Tailwind v4 compiles
`dark:` utilities to `@media (prefers-color-scheme: dark)`, so every
leftover `dark:` class (the vendored IAM UI is full of them) would activate
on an OS-level dark setting. Binding the variant to a class that is never
rendered keeps those utilities inert no matter what the OS says. */
@custom-variant dark (&:where(.edr-dark-disabled));
/* Bridge the central Mantine theme into Tailwind. freightMantineTheme
(createTheme) is the single source of truth; these just alias its generated

View File

@@ -144,7 +144,6 @@ const DashboardShell = () => {
sidebarSections={sidebarSections}
activeHref={location.pathname}
onNavigate={navigate}
enableThemeToggle
userName={displayName}
userEmail={user?.email}
onLogout={logout}

View File

@@ -15,9 +15,7 @@ import {
FileSignature,
Languages,
LogOut,
Moon,
Search,
Sun,
User,
} from "lucide-react";
import { type ReactNode } from "react";
@@ -31,13 +29,10 @@ import type { PageMeta } from "./types";
export interface FreightDashboardHeaderProps {
pageMeta: PageMeta;
headerRight?: ReactNode;
enableThemeToggle?: boolean;
userName?: string;
userEmail?: string;
userInitials?: string;
onLogout?: () => void;
theme: "light" | "dark";
onToggleTheme: () => void;
mobileOpened: boolean;
onToggleMobile: () => void;
/** Hide the mobile burger when the shell has no sidebar to open. */
@@ -51,13 +46,10 @@ const ISLAND =
const FreightDashboardHeader = ({
headerRight,
enableThemeToggle = false,
userName = "User",
userEmail,
userInitials,
onLogout,
theme,
onToggleTheme,
mobileOpened,
onToggleMobile,
hideSidebarBurger = false,
@@ -129,26 +121,6 @@ const FreightDashboardHeader = ({
<NotificationBellContainer />
{enableThemeToggle && (
<Tooltip
label={theme === "dark" ? "Light mode" : "Dark mode"}
withArrow
openDelay={300}
>
<UnstyledButton
className={ISLAND}
onClick={onToggleTheme}
aria-label="Toggle theme"
>
{theme === "dark" ? (
<Sun size={17} strokeWidth={1.8} />
) : (
<Moon size={17} strokeWidth={1.8} />
)}
</UnstyledButton>
</Tooltip>
)}
{/* Avatar pill */}
<Menu width={240} position="bottom-end" withinPortal shadow="md" offset={8} radius="md">
<Menu.Target>

View File

@@ -1,26 +1,15 @@
import { AppShell, Box } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { type ReactNode, useEffect, useState } from "react";
import { type ReactNode } from "react";
import FreightDashboardHeader from "./FreightDashboardHeader";
import FreightSidebar from "./FreightSidebar";
import { getPageMeta } from "./route-meta";
import type { SidebarSection } from "./types";
type Theme = "light" | "dark";
const THEME_STORAGE_KEY = "edr-theme";
const HEADER_HEIGHT = 64;
const NAVBAR_WIDTH = 280;
function getInitialTheme(): Theme {
if (typeof window === "undefined") return "light";
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
if (stored === "dark" || stored === "light") return stored;
return window.matchMedia?.("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
export interface FreightDashboardLayoutProps {
sidebarSections: SidebarSection[];
/** Render the shell with no navbar at all (used by GL clearance-only users). */
@@ -28,7 +17,6 @@ export interface FreightDashboardLayoutProps {
activeHref?: string;
onNavigate?: (href: string) => void;
headerRight?: ReactNode;
enableThemeToggle?: boolean;
userName?: string;
userEmail?: string;
userInitials?: string;
@@ -42,7 +30,6 @@ const FreightDashboardLayout = ({
activeHref = "",
onNavigate,
headerRight,
enableThemeToggle = false,
userName,
userEmail,
userInitials,
@@ -53,20 +40,6 @@ const FreightDashboardLayout = ({
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] =
useDisclosure(false);
const [theme, setTheme] = useState<Theme>(() =>
enableThemeToggle ? getInitialTheme() : "light",
);
useEffect(() => {
if (!enableThemeToggle) return;
const root = document.documentElement;
root.classList.toggle("dark", theme === "dark");
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
}, [theme, enableThemeToggle]);
const toggleTheme = () =>
setTheme((current) => (current === "dark" ? "light" : "dark"));
const navigate = (href: string) => {
closeMobile();
onNavigate?.(href);
@@ -93,13 +66,10 @@ const FreightDashboardLayout = ({
<FreightDashboardHeader
pageMeta={pageMeta}
headerRight={headerRight}
enableThemeToggle={enableThemeToggle}
userName={userName}
userEmail={userEmail}
userInitials={userInitials}
onLogout={onLogout}
theme={theme}
onToggleTheme={toggleTheme}
mobileOpened={mobileOpened}
onToggleMobile={toggleMobile}
hideSidebarBurger={hideSidebar}

View File

@@ -6,8 +6,6 @@ import {
LogOut,
User,
Key,
Moon,
Sun,
Home,
Menu,
} from "lucide-react";
@@ -25,7 +23,6 @@ import { NavLink, useNavigate, useLocation } from "react-router-dom";
import { useRef, useEffect, useState } from "react";
import { FiBell } from "react-icons/fi";
import { useUser } from "@/shared/context/UserContext";
import { useDarkMode } from "@/shared/hooks/useDarkMode";
import {
UI_LANGUAGE_OPTIONS,
getUiLanguageLabel,
@@ -49,7 +46,6 @@ export const TopBar = () => {
const { pathname } = useLocation();
const { logout } = useAuthUser();
const userDetails = useUser();
const { isDarkMode, toggleDarkMode } = useDarkMode();
const { config: tenantConfig } = useTenantConfig();
const moduleConfig = resolveModuleConfig(tenantConfig);
const isAdminModuleVisible = userDetails?.roles?.some(
@@ -66,7 +62,6 @@ export const TopBar = () => {
moduleConfig.siteManagement && isAdminModuleVisible,
].filter(Boolean).length;
const shouldShowHomePageLink = visibleModuleCount > 1;
const isObjectiveManagementRoute = pathname.startsWith("/objective-management");
const fullName = userDetails?.name?.en || t("user");
const splittedName = fullName.trim().split(" ");
const initials =
@@ -269,19 +264,6 @@ export const TopBar = () => {
)}
</div>
<Button
onClick={toggleDarkMode}
aria-label="Toggle Dark Mode"
className="p-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200"
title={isDarkMode ? "Switch to light mode" : "Switch to dark mode"}
style={{ display: isObjectiveManagementRoute ? "none" : "block" }}>
{isDarkMode ? (
<Sun className="h-5 w-5 text-yellow-500" />
) : (
<Moon className="h-5 w-5 text-gray-600" />
)}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button

View File

@@ -18,11 +18,8 @@ import {
Info,
Menu,
X,
Moon,
Sun,
} from "lucide-react";
import { useUser } from "@/shared/context/UserContext";
import { useDarkMode } from "@/shared/hooks/useDarkMode";
import { useTenantConfig, resolveModuleConfig } from "@/layout/components/TenantConfig";
import {
UI_LANGUAGE_OPTIONS,
@@ -55,7 +52,6 @@ const Header = () => {
const location = useLocation();
const isComplaintContext = isComplaintAuthContext(location.pathname);
const { isAuthenticated, showExternalPortalChrome } = useExternalPortalSession();
const { isDarkMode, toggleDarkMode } = useDarkMode();
const { config: tenantConfig } = useTenantConfig();
const moduleConfig = resolveModuleConfig(tenantConfig);
@@ -480,22 +476,6 @@ const Header = () => {
</SelectContent>
</Select>
</div>
{/* Dark Mode Toggle */}
<motion.button
onClick={toggleDarkMode}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="p-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200"
title={
isDarkMode ? "Switch to light mode" : "Switch to dark mode"
}
>
{isDarkMode ? (
<Sun className="h-5 w-5 text-yellow-500" />
) : (
<Moon className="h-5 w-5 text-gray-600" />
)}
</motion.button>
<motion.button
onClick={handleSignIn}
whileHover={{
@@ -646,26 +626,6 @@ const Header = () => {
</Select>
</div>
{/* Dark Mode Toggle for Mobile */}
<motion.button
onClick={toggleDarkMode}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="block w-full px-4 py-3 text-base font-medium text-center text-gray-700 dark:text-gray-200 bg-gray-100 dark:bg-gray-700 rounded-lg shadow hover:bg-gray-200 dark:hover:bg-gray-600 transition-all duration-200 flex items-center justify-center gap-2"
>
{isDarkMode ? (
<>
<Sun className="h-5 w-5 text-yellow-500" />
{t("landingPage.lightMode")}
</>
) : (
<>
<Moon className="h-5 w-5 text-gray-600" />
{t("landingPage.darkMode")}
</>
)}
</motion.button>
<motion.button
onClick={() => {
handleSignIn();

View File

@@ -31,24 +31,19 @@ import { QueryClientProvider } from "@tanstack/react-query";
const THEME_STORAGE_KEY = "edr-theme";
const applyStoredTheme = () => {
const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
const prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)").matches;
const theme = storedTheme === "dark" || storedTheme === "light"
? storedTheme
: prefersDark
? "dark"
: "light";
if (theme === "dark") {
document.documentElement.classList.add("dark");
return;
}
/**
* The backoffice is light-only. This runs before render for two reasons:
* strip the `dark` class in case anything ever adds it, and evict the
* `edr-theme` key that earlier builds persisted — without the eviction, a user
* who had toggled dark before this change keeps a "dark" value in
* localStorage that any future re-read would resurrect.
*/
const forceLightTheme = () => {
document.documentElement.classList.remove("dark");
window.localStorage.removeItem(THEME_STORAGE_KEY);
};
applyStoredTheme();
forceLightTheme();
// Must run before render so replay and exception capture cover startup errors.
// No-ops when VITE_POSTHOG_KEY is unset.
@@ -63,7 +58,7 @@ if (!rootElement) {
createRoot(rootElement).render(
<PostHogProvider client={posthog}>
<QueryClientProvider client={queryClient}>
<MantineProvider theme={freightMantineTheme}>
<MantineProvider theme={freightMantineTheme} forceColorScheme="light">
<StrictMode>
<BrowserRouter>
<AuthProvider>

View File

@@ -25,8 +25,6 @@ import {
Cog,
Bolt,
Globe,
Moon,
Sun,
} from "lucide-react";
import {
DropdownMenu,
@@ -39,7 +37,6 @@ import { Button } from "@/shared/common/ui/button";
import { useTheme } from "@/user-management/web-Management/hooks/useTheme";
import { useLocalizedName } from "@/shared/common/localizedName";
import { useModules } from "@/user-management/web-Management/hooks/useModules";
import { useDarkMode } from "@/shared/hooks/useDarkMode";
import {
useTenantConfig,
resolveModuleConfig,
@@ -98,7 +95,6 @@ const Header = () => {
logout();
};
const { isDarkMode, toggleDarkMode } = useDarkMode();
useEffect(() => {
if (!localStorage.getItem("i18nextLng")) {
@@ -440,22 +436,6 @@ const Header = () => {
))}
</div>
<div className="hidden md:flex items-center gap-3 lg:gap-4">
<motion.button
onClick={toggleDarkMode}
className="p-2 rounded-lg text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition-colors"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
aria-label={
isDarkMode ? t("header.lightMode") : t("header.darkMode")
}
>
{isDarkMode ? (
<Sun className="w-5 h-5" />
) : (
<Moon className="w-5 h-5" />
)}
</motion.button>
<div className="w-32 lg:w-36">
<Select
value={currentLanguage}
@@ -544,21 +524,6 @@ const Header = () => {
</div>
<div className="flex items-center md:hidden gap-2">
<motion.button
onClick={toggleDarkMode}
className="p-2 rounded-lg text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition-colors"
whileTap={{ scale: 0.9 }}
aria-label={
isDarkMode ? t("header.lightMode") : t("header.darkMode")
}
>
{isDarkMode ? (
<Sun className="h-5 w-5" />
) : (
<Moon className="h-5 w-5" />
)}
</motion.button>
<motion.button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="inline-flex items-center justify-center p-2 rounded-lg text-gray-600 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-all"

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef } from "react";
import { Editor } from "@tinymce/tinymce-react";
import type { Editor as TinyMCEEditor } from "tinymce";
@@ -34,16 +34,10 @@ export default function RichTextEditor({
shouldClear?: boolean;
}) {
const editorRef = useRef<TinyMCEEditor | null>(null);
const [isDarkMode, setIsDarkMode] = useState(() => {
if (typeof window === "undefined") {
return false;
}
const root = document.documentElement;
return (
root.classList.contains("dark") || root.getAttribute("data-theme") === "dark"
);
});
// The app is light-only — nothing sets `dark` / data-theme on <html>, so this
// is a constant rather than observed state. Kept as a named flag so the
// TinyMCE skin/content-style branches below stay readable.
const isDarkMode = false;
// ✅ When shouldClear becomes true → clear editor + remove draft
useEffect(() => {
@@ -54,28 +48,6 @@ export default function RichTextEditor({
}
}, [shouldClear]);
useEffect(() => {
const root = document.documentElement;
const updateThemeMode = () => {
setIsDarkMode(
root.classList.contains("dark") ||
root.getAttribute("data-theme") === "dark",
);
};
updateThemeMode();
const observer = new MutationObserver(updateThemeMode);
observer.observe(root, {
attributes: true,
attributeFilter: ["class", "data-theme"],
});
return () => {
observer.disconnect();
};
}, []);
const init = useMemo(
() => ({
// Point self-hosted TinyMCE at the static assets copied into /public/tinymce.

View File

@@ -21,8 +21,6 @@ import {
Key,
LogOut,
LucideIcon,
Moon,
Sun,
User,
UserPen,
UsersRound,
@@ -37,7 +35,6 @@ import UserApprovalDropdown from "../PendingUsersList";
import { usePendingUsers } from "@/user-management/userManagement/hooks/usePendingUsersHook";
import { useUserDetail } from "../hooks/useUserDetail";
import { MeDto } from "@/shared/dto/user/meDto";
import { useDarkMode } from "@/shared/hooks/useDarkMode";
import {
UI_LANGUAGE_OPTIONS,
getUiLanguageLabel,
@@ -133,7 +130,6 @@ const Top: React.FC<HeaderProps> = ({
logout();
};
const { isDarkMode, toggleDarkMode } = useDarkMode();
const { pendingUsers } = usePendingUsers({ take: 5, skip: 0 });
const pendingUsersCount = pendingUsers?.count || 0;
@@ -359,21 +355,6 @@ const Top: React.FC<HeaderProps> = ({
</div>
<div className="flex max-w-[56vw] flex-shrink-0 items-center space-x-1.5 sm:max-w-none sm:space-x-2.5">
<Button
variant="ghost"
size="icon"
className="h-9 w-9 rounded-xl bg-white/90 text-gray-600 shadow-sm ring-1 ring-inset ring-black/5 transition-colors hover:bg-primary-50 hover:text-primary-700 hover:ring-primary-200 dark:bg-gray-800/90 dark:text-gray-300 dark:ring-white/10 dark:hover:bg-primary-900/20 dark:hover:text-primary-300 dark:hover:ring-primary-700/40 sm:h-10 sm:w-10"
aria-label={isDarkMode ? "Light mode" : "Dark mode"}
onClick={toggleDarkMode}
title={isDarkMode ? "Switch to light mode" : "Switch to dark mode"}
>
{isDarkMode ? (
<Sun className="h-4 w-4 text-yellow-500" />
) : (
<Moon className="h-4 w-4 text-gray-700 dark:text-gray-300" />
)}
</Button>
{canActivateUsers && (
<div className="relative">
<Button

View File

@@ -1,12 +1,13 @@
import { useTheme } from "next-themes"
import { Toaster as Sonner, ToasterProps } from "sonner"
// Pinned to "light": the app is light-only. This previously read next-themes'
// useTheme, which falls back to "system" when no ThemeProvider is mounted (and
// none is) — sonner then resolves "system" against prefers-color-scheme and
// rendered dark toasts over a light UI on any OS set to dark.
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
theme="light"
className="toaster group"
toastOptions={{
classNames: {

View File

@@ -13,8 +13,6 @@ import {
Mail,
Phone,
User,
Moon,
Sun,
} from "lucide-react";
import { useAuthUser } from "@/shared/hooks/useAuthUser";
import { useTenantConfig } from "@/layout/components/TenantConfig";
@@ -26,13 +24,11 @@ import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useAuth } from "@/shared/context/AuthContext";
import OTPModal from "./OTPModal";
import { useDarkMode } from "@/shared/hooks/useDarkMode";
import { getRememberMePreference } from "@/shared/utils/authPersistence";
export const Login = () => {
const { login: authLogin, isLoggingIn } = useAuthUser();
const { config } = useTenantConfig();
const { isDarkMode, toggleDarkMode } = useDarkMode();
const detectLoginMethod = (value: string): "email" | "phone" | "username" => {
if (value.includes("@") && value.includes(".com")) return "email";
if (/^\+?\d+$/.test(value)) return "phone";
@@ -121,19 +117,6 @@ export const Login = () => {
</span>
</Link>
{/* Dark Mode Toggle */}
<button
onClick={toggleDarkMode}
className="fixed top-4 right-4 md:top-6 md:right-6 z-50 p-2 rounded-full bg-white dark:bg-gray-800 shadow-md hover:shadow-lg transition-all duration-300 hover:scale-105 border border-gray-200 dark:border-gray-700"
title={isDarkMode ? "Switch to light mode" : "Switch to dark mode"}
>
{isDarkMode ? (
<Sun className="w-5 h-5 text-yellow-500" />
) : (
<Moon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
)}
</button>
<div className="w-full max-w-6xl bg-white dark:bg-gray-800 rounded-2xl shadow-2xl overflow-hidden flex flex-col lg:flex-row">
{/* Left Panel - Login Form */}
<div className="lg:w-1/2 w-full p-6 sm:p-8 md:p-10 lg:p-12 xl:p-16 flex flex-col justify-center">

View File

@@ -1,33 +0,0 @@
import { useEffect, useState } from "react";
// Same storage key/values as main.tsx and FreightDashboardLayout so the
// vendored IAM UI toggle and the host dashboard toggle stay in sync.
const THEME_STORAGE_KEY = "edr-theme";
export const useDarkMode = () => {
const [isDarkMode, setIsDarkMode] = useState(() => {
// Initialize from localStorage or system preference
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
if (savedTheme === "dark" || savedTheme === "light") {
return savedTheme === "dark";
}
return window.matchMedia("(prefers-color-scheme: dark)").matches;
});
useEffect(() => {
// Apply dark mode class on mount and when isDarkMode changes
if (isDarkMode) {
document.documentElement.classList.add("dark");
localStorage.setItem(THEME_STORAGE_KEY, "dark");
} else {
document.documentElement.classList.remove("dark");
localStorage.setItem(THEME_STORAGE_KEY, "light");
}
}, [isDarkMode]);
const toggleDarkMode = () => {
setIsDarkMode((prev) => !prev);
};
return { isDarkMode, toggleDarkMode };
};

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect } from "react";
import React, { useMemo } from "react";
import { Editor as TinyMCEEditor } from "@/record-management/common/editor/rte";
import type { Editor as TinyMCEEditorType } from "tinymce";
@@ -21,18 +21,11 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
placeholders,
}) => {
const hasPlaceholders = placeholders && placeholders.length > 0;
const [isDarkMode, setIsDarkMode] = useState(() =>
typeof window !== "undefined" && document.documentElement.classList.contains("dark")
);
useEffect(() => {
const observer = new MutationObserver(() => {
setIsDarkMode(document.documentElement.classList.contains("dark"));
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
return () => observer.disconnect();
}, []);
// The app is light-only — nothing sets the `dark` class on <html>, so this is
// a constant rather than observed state. Kept as a named flag so the TinyMCE
// skin/content-style branches below stay readable.
const isDarkMode = false;
const init = useMemo(() => {
return {
height: 600,

View File

@@ -1,10 +1,13 @@
@import "tailwindcss";
@import "@edr/ui-common/theme.css" layer(theme);
/* Dark mode is toggled via the `dark` class on <html>. Without this, Tailwind
v4 compiles `dark:` utilities to `@media (prefers-color-scheme: dark)` and
they follow the OS setting instead of the in-app toggle. */
@custom-variant dark (&:where(.dark, .dark *));
/* The portal is light-only — there is no theme toggle and nothing sets the
`dark` class. This override is load-bearing: without it Tailwind v4 compiles
`dark:` utilities to `@media (prefers-color-scheme: dark)`, so every
leftover `dark:` class in the tree would activate on an OS-level dark
setting. Binding the variant to a class that is never rendered keeps those
utilities inert no matter what the OS (or a stray `.dark` class) says. */
@custom-variant dark (&:where(.edr-dark-disabled));
/* Bridge the central Mantine theme into Tailwind. Mantine (createTheme) is the
single source of truth; these just alias its generated CSS variables so

View File

@@ -317,7 +317,6 @@ const App = () => {
sidebarItems={sidebarItems}
activeHref={location.pathname}
onNavigate={navigate}
enableThemeToggle
userName={displayName}
userEmail={userEmail}
companyProfiles={companyProfiles}

View File

@@ -15,8 +15,6 @@ import {
Stack,
Text,
UnstyledButton,
useComputedColorScheme,
useMantineColorScheme,
useMantineTheme,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
@@ -26,17 +24,15 @@ import {
FileSignature,
LogOut,
Menu as MenuIcon,
Moon,
Plus,
RefreshCw,
Search,
Settings,
Sun,
Upload,
User,
X,
} from "lucide-react";
import { type CSSProperties, Fragment, type ReactNode, useState } from "react";
import { Fragment, type ReactNode, useState } from "react";
import { PROFILE_TYPE_LABELS } from "@/constants/profileMode";
import NotificationBellContainer from "@/features/notifications/NotificationBellContainer";
import SupportWidget from "@/features/support/SupportWidget";
@@ -59,7 +55,6 @@ export interface AppLayoutProps {
* to the new-booking wizard. Compatible with react-router's `navigate`.
*/
onNavigate?: (href: string, options?: { state?: unknown }) => void;
enableThemeToggle?: boolean;
userName?: string;
userEmail?: string;
/** Operational profiles for the company — surfaced as reference chips in the account menu. */
@@ -154,7 +149,6 @@ export function AppLayout({
sidebarItems,
activeHref = "",
onNavigate,
enableThemeToggle = false,
userName = "User",
userEmail,
companyProfiles = [],
@@ -165,9 +159,6 @@ export function AppLayout({
}: AppLayoutProps) {
const [mobileOpen, { toggle: toggleMobile }] = useDisclosure();
const theme = useMantineTheme();
const { setColorScheme } = useMantineColorScheme();
const computedColorScheme = useComputedColorScheme("light");
const borderColor = theme.colors["edr-border"][6];
const mutedColor = theme.colors["edr-muted"][6];
const textColor = theme.colors["edr-text"][6];
@@ -178,10 +169,6 @@ export function AppLayout({
const navigate = (href: string, options?: { state?: unknown }) =>
onNavigate?.(href, options);
const toggleTheme = () => {
setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
};
const initials = getInitials(userName);
const activePage = getActivePage(sidebarItems, activePath);
@@ -279,20 +266,6 @@ export function AppLayout({
activePath === item.href.toLowerCase() ||
activePath.startsWith(item.href.toLowerCase() + "/");
// Shared header "island" styles — every control is a consistent 36px chip.
const islandStyle: CSSProperties = {
width: 36,
height: 36,
borderRadius: 999,
border: `1px solid ${borderColor}`,
backgroundColor: "#fff",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
cursor: "pointer",
};
return (
<AppShell
layout="alt"
@@ -457,20 +430,6 @@ export function AppLayout({
{/* Notifications */}
<NotificationBellContainer />
{enableThemeToggle && (
<UnstyledButton
onClick={toggleTheme}
style={islandStyle}
aria-label="Toggle theme"
>
{computedColorScheme === "dark" ? (
<Sun size={17} color={textColor} strokeWidth={1.8} />
) : (
<Moon size={17} color={textColor} strokeWidth={1.8} />
)}
</UnstyledButton>
)}
{/* Avatar pill */}
<Menu
width={220}

View File

@@ -48,7 +48,10 @@ if (!rootElement) {
createRoot(document.getElementById("root")!).render(
<StrictMode>
<PostHogProvider client={posthog}>
<MantineProvider theme={mantineTheme} defaultColorScheme="light">
{/* forceColorScheme (not defaultColorScheme) — the app is light-only.
`default` still lets Mantine flip to dark via useMantineColorScheme
or a persisted color-scheme value; `force` pins it. */}
<MantineProvider theme={mantineTheme} forceColorScheme="light">
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<PostHogErrorBoundary fallback={<AppErrorFallback />}>