- Implement theme switching between light and dark modes - Use Mantine's color scheme for automatic detection - Add tooltip for theme toggle button - Update App component to use 'auto' color scheme
319 lines
9.6 KiB
TypeScript
319 lines
9.6 KiB
TypeScript
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
|
import {
|
|
AppShell,
|
|
Burger,
|
|
Group,
|
|
Text,
|
|
NavLink,
|
|
Button,
|
|
Avatar,
|
|
Stack,
|
|
Image,
|
|
ActionIcon,
|
|
Tooltip,
|
|
useMantineColorScheme,
|
|
} from "@mantine/core";
|
|
import { useDisclosure } from "@mantine/hooks";
|
|
import {
|
|
IconDashboard,
|
|
IconUsers,
|
|
IconWebhook,
|
|
IconBrandWhatsapp,
|
|
IconSend,
|
|
IconBuildingStore,
|
|
IconTemplate,
|
|
IconCategory,
|
|
IconArrowsShuffle,
|
|
IconFileText,
|
|
IconDatabase,
|
|
IconLogout,
|
|
IconSun,
|
|
IconMoon,
|
|
} from "@tabler/icons-react";
|
|
import { useAuthStore } from "../stores/authStore";
|
|
|
|
export default function DashboardLayout() {
|
|
const { user, logout } = useAuthStore();
|
|
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const [opened, { toggle }] = useDisclosure();
|
|
|
|
const handleLogout = () => {
|
|
logout();
|
|
navigate("/login");
|
|
};
|
|
|
|
const isActive = (path: string) => location.pathname === path;
|
|
const isAnyActive = (paths: string[]) =>
|
|
paths.some((path) => location.pathname === path);
|
|
const displayName =
|
|
user?.username?.trim() ||
|
|
user?.full_name?.trim() ||
|
|
user?.email?.trim() ||
|
|
"User";
|
|
const displayInitial = displayName[0]?.toUpperCase() || "U";
|
|
const logoSrc = `${import.meta.env.BASE_URL}logo.png`;
|
|
const swaggerIconSrc = `${import.meta.env.BASE_URL}swagger-icon.svg`;
|
|
const isDark = colorScheme === "dark";
|
|
|
|
const toggleTheme = () => {
|
|
if (colorScheme === "auto") {
|
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
setColorScheme(prefersDark ? "light" : "dark");
|
|
return;
|
|
}
|
|
|
|
setColorScheme(isDark ? "light" : "dark");
|
|
};
|
|
|
|
return (
|
|
<AppShell
|
|
header={{ height: 60 }}
|
|
navbar={{
|
|
width: 280,
|
|
breakpoint: "sm",
|
|
collapsed: { mobile: !opened },
|
|
}}
|
|
padding="md"
|
|
>
|
|
<AppShell.Header>
|
|
<Group h="100%" px="md" justify="space-between">
|
|
<Group>
|
|
<Burger
|
|
opened={opened}
|
|
onClick={toggle}
|
|
hiddenFrom="sm"
|
|
size="sm"
|
|
/>
|
|
<Image src={logoSrc} alt="WhatsHooked logo" w={24} h={24} fit="contain" />
|
|
<Text size="xl" fw={700}>
|
|
WhatsHooked
|
|
</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text size="sm" c="dimmed">
|
|
{displayName}
|
|
</Text>
|
|
<Avatar color="blue" radius="xl" size="sm">
|
|
{displayInitial}
|
|
</Avatar>
|
|
</Group>
|
|
</Group>
|
|
</AppShell.Header>
|
|
|
|
<AppShell.Navbar p="md">
|
|
<AppShell.Section grow>
|
|
<Stack gap="xs">
|
|
<NavLink
|
|
href="/dashboard"
|
|
label="Dashboard"
|
|
leftSection={<IconDashboard size={20} stroke={1.5} />}
|
|
active={isActive("/dashboard")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/dashboard");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/users"
|
|
label="Users"
|
|
leftSection={<IconUsers size={20} stroke={1.5} />}
|
|
active={isActive("/users")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/users");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/hooks"
|
|
label="Hooks"
|
|
leftSection={<IconWebhook size={20} stroke={1.5} />}
|
|
active={isActive("/hooks")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/hooks");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
label="WhatsApp Accounts"
|
|
leftSection={<IconBrandWhatsapp size={20} stroke={1.5} />}
|
|
defaultOpened
|
|
active={isAnyActive([
|
|
"/accounts",
|
|
"/whatsapp-business",
|
|
"/business-templates",
|
|
"/catalogs",
|
|
"/flows",
|
|
])}
|
|
>
|
|
<NavLink
|
|
href="/accounts"
|
|
label="Account List"
|
|
active={isActive("/accounts")}
|
|
leftSection={
|
|
<IconBrandWhatsapp size={20} stroke={1.5} color="green" />
|
|
}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/accounts");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
label="Business Management"
|
|
leftSection={
|
|
<IconBrandWhatsapp size={20} stroke={1.5} color="orange" />
|
|
}
|
|
defaultOpened
|
|
active={isAnyActive([
|
|
"/whatsapp-business",
|
|
"/business-templates",
|
|
"/catalogs",
|
|
"/flows",
|
|
])}
|
|
>
|
|
<NavLink
|
|
href="/whatsapp-business"
|
|
label="Business Management Tools"
|
|
active={isActive("/whatsapp-business")}
|
|
leftSection={<IconBuildingStore size={16} stroke={1.5} />}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/whatsapp-business");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/business-templates"
|
|
label="Templates"
|
|
leftSection={<IconTemplate size={16} stroke={1.5} />}
|
|
active={isActive("/business-templates")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/business-templates");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/catalogs"
|
|
label="Catalogs"
|
|
leftSection={<IconCategory size={16} stroke={1.5} />}
|
|
active={isActive("/catalogs")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/catalogs");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/flows"
|
|
label="Flows"
|
|
leftSection={<IconArrowsShuffle size={16} stroke={1.5} />}
|
|
active={isActive("/flows")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/flows");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
</NavLink>
|
|
</NavLink>
|
|
<NavLink
|
|
href="/send-message"
|
|
label="Send Message"
|
|
leftSection={<IconSend size={20} stroke={1.5} color="green" />}
|
|
active={isActive("/send-message")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/send-message");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/event-logs"
|
|
label="Event Logs"
|
|
leftSection={
|
|
<IconFileText size={20} stroke={1.5} color="maroon" />
|
|
}
|
|
active={isActive("/event-logs")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/event-logs");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/message-cache"
|
|
label="Message Cache"
|
|
leftSection={
|
|
<IconDatabase size={20} stroke={1.5} color="indigo" />
|
|
}
|
|
active={isActive("/message-cache")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/message-cache");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
<NavLink
|
|
href="/sw"
|
|
label="Swagger"
|
|
leftSection={
|
|
<Image src={swaggerIconSrc} alt="Swagger" w={18} h={18} fit="contain" />
|
|
}
|
|
active={isActive("/sw")}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
navigate("/sw");
|
|
if (opened) toggle();
|
|
}}
|
|
/>
|
|
</Stack>
|
|
</AppShell.Section>
|
|
|
|
<AppShell.Section>
|
|
<Stack gap="xs">
|
|
<Group justify="space-between" px="sm">
|
|
<Tooltip label={isDark ? "Switch to light theme" : "Switch to dark theme"}>
|
|
<ActionIcon
|
|
variant="light"
|
|
size="lg"
|
|
onClick={toggleTheme}
|
|
aria-label={isDark ? "Switch to light theme" : "Switch to dark theme"}
|
|
>
|
|
{isDark ? <IconSun size={18} /> : <IconMoon size={18} />}
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
<div>
|
|
<Text size="sm" fw={500}>
|
|
{displayName}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{user?.role || "user"}
|
|
</Text>
|
|
</div>
|
|
</Group>
|
|
<Button
|
|
leftSection={<IconLogout size={16} />}
|
|
variant="light"
|
|
color="red"
|
|
fullWidth
|
|
onClick={handleLogout}
|
|
>
|
|
Logout
|
|
</Button>
|
|
</Stack>
|
|
</AppShell.Section>
|
|
</AppShell.Navbar>
|
|
|
|
<AppShell.Main>
|
|
<Outlet />
|
|
</AppShell.Main>
|
|
</AppShell>
|
|
);
|
|
}
|