More management tools
Some checks failed
CI / Test (1.22) (push) Failing after -30m28s
CI / Lint (push) Failing after -30m32s
CI / Build (push) Failing after -30m31s
CI / Test (1.23) (push) Failing after -30m31s

This commit is contained in:
2026-03-04 22:30:40 +02:00
parent 4a716bb82d
commit 4b44340c58
25 changed files with 3094 additions and 230 deletions

View File

@@ -1,15 +1,30 @@
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import { AppShell, Burger, Group, Text, NavLink, Button, Avatar, Stack, Badge } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import {
IconDashboard,
IconUsers,
IconWebhook,
IconBrandWhatsapp,
IconFileText,
IconLogout
} from '@tabler/icons-react';
import { useAuthStore } from '../stores/authStore';
import { Outlet, useNavigate, useLocation } from "react-router-dom";
import {
AppShell,
Burger,
Group,
Text,
NavLink,
Button,
Avatar,
Stack,
Badge,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import {
IconDashboard,
IconUsers,
IconWebhook,
IconBrandWhatsapp,
IconSend,
IconBuildingStore,
IconTemplate,
IconCategory,
IconArrowsShuffle,
IconFileText,
IconLogout,
} from "@tabler/icons-react";
import { useAuthStore } from "../stores/authStore";
export default function DashboardLayout() {
const { user, logout } = useAuthStore();
@@ -19,27 +34,19 @@ export default function DashboardLayout() {
const handleLogout = () => {
logout();
navigate('/login');
navigate("/login");
};
const isActive = (path: string) => {
return location.pathname === path;
};
const navItems = [
{ path: '/dashboard', label: 'Dashboard', icon: IconDashboard },
{ path: '/users', label: 'Users', icon: IconUsers },
{ path: '/hooks', label: 'Hooks', icon: IconWebhook },
{ path: '/accounts', label: 'WhatsApp Accounts', icon: IconBrandWhatsapp },
{ path: '/event-logs', label: 'Event Logs', icon: IconFileText },
];
const isActive = (path: string) => location.pathname === path;
const isAnyActive = (paths: string[]) =>
paths.some((path) => location.pathname === path);
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 280,
breakpoint: 'sm',
breakpoint: "sm",
collapsed: { mobile: !opened },
}}
padding="md"
@@ -47,14 +54,25 @@ export default function DashboardLayout() {
<AppShell.Header>
<Group h="100%" px="md" justify="space-between">
<Group>
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
<Text size="xl" fw={700}>WhatsHooked</Text>
<Badge color="blue" variant="light">Admin</Badge>
<Burger
opened={opened}
onClick={toggle}
hiddenFrom="sm"
size="sm"
/>
<Text size="xl" fw={700}>
WhatsHooked
</Text>
<Badge color="blue" variant="light">
Admin
</Badge>
</Group>
<Group>
<Text size="sm" c="dimmed">{user?.username || 'User'}</Text>
<Text size="sm" c="dimmed">
{user?.username || "User"}
</Text>
<Avatar color="blue" radius="xl" size="sm">
{user?.username?.[0]?.toUpperCase() || 'U'}
{user?.username?.[0]?.toUpperCase() || "U"}
</Avatar>
</Group>
</Group>
@@ -63,20 +81,147 @@ export default function DashboardLayout() {
<AppShell.Navbar p="md">
<AppShell.Section grow>
<Stack gap="xs">
{navItems.map((item) => (
<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
key={item.path}
href={item.path}
label={item.label}
leftSection={<item.icon size={20} stroke={1.5} />}
active={isActive(item.path)}
href="/accounts"
label="Account List"
active={isActive("/accounts")}
leftSection={
<IconBrandWhatsapp size={20} stroke={1.5} color="green" />
}
onClick={(e) => {
e.preventDefault();
navigate(item.path);
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();
}}
/>
</Stack>
</AppShell.Section>
@@ -84,8 +229,12 @@ export default function DashboardLayout() {
<Stack gap="xs">
<Group justify="space-between" px="sm">
<div>
<Text size="sm" fw={500}>{user?.username || 'User'}</Text>
<Text size="xs" c="dimmed">{user?.role || 'user'}</Text>
<Text size="sm" fw={500}>
{user?.username || "User"}
</Text>
<Text size="xs" c="dimmed">
{user?.role || "user"}
</Text>
</div>
</Group>
<Button