lots of ui updates

This commit is contained in:
dekzter 2025-03-17 16:04:25 -04:00
parent 7a7cd0711d
commit ebd569b0c0
17 changed files with 217 additions and 321 deletions

View file

@ -8,7 +8,11 @@ server {
}
location /assets/ {
root /app/static; # Base directory for static files
root /app/static;
}
location /static/ {
root /app;
}
# admin disabled when not in dev mode

View file

@ -62,7 +62,6 @@ export const WebsocketProvider = ({ children }) => {
break;
case 'channel_stats':
console.log(JSON.parse(event.data.stats));
setChannelStats(JSON.parse(event.data.stats));
break;

View file

@ -849,8 +849,8 @@ export default class API {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${await API.getAuthToken()}`,
body: JSON.stringify({ client_id: clientId }),
},
body: JSON.stringify({ client_id: clientId }),
});
const retval = await response.json();

View file

@ -7,6 +7,8 @@ import {
SlidersHorizontal,
LayoutGrid,
Settings as LucideSettings,
Copy,
ChartLine,
} from 'lucide-react';
import {
Avatar,
@ -23,7 +25,6 @@ import logo from '../images/logo.png';
import useChannelsStore from '../store/channels';
import './sidebar.css';
import useSettingsStore from '../store/settings';
import { ContentCopy } from '@mui/icons-material';
const NavLink = ({ item, isActive, collapsed }) => {
return (
@ -79,7 +80,7 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
path: '/stream-profiles',
},
{ label: 'TV Guide', icon: <LayoutGrid size={20} />, path: '/guide' },
{ label: 'Stats', icon: <LayoutGrid size={20} />, path: '/stats' },
{ label: 'Stats', icon: <ChartLine size={20} />, path: '/stats' },
{
label: 'Settings',
icon: <LucideSettings size={20} />,
@ -196,7 +197,7 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
color="gray.9"
onClick={copyPublicIP}
>
<ContentCopy />
<Copy />
</ActionIcon>
}
/>

View file

@ -4,7 +4,6 @@ import * as Yup from 'yup';
import useChannelsStore from '../../store/channels';
import API from '../../api';
import useStreamProfilesStore from '../../store/streamProfiles';
import { Add as AddIcon, Remove as RemoveIcon } from '@mui/icons-material';
import useStreamsStore from '../../store/streams';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import ChannelGroupForm from './ChannelGroup';

View file

@ -1,7 +1,6 @@
import React, { useState, useMemo } from 'react';
import API from '../../api';
import M3UProfile from './M3UProfile';
import { Delete as DeleteIcon, Edit as EditIcon } from '@mui/icons-material';
import usePlaylistsStore from '../../store/playlists';
import {
Card,
@ -13,6 +12,7 @@ import {
ActionIcon,
Text,
} from '@mantine/core';
import { SquareMinus, SquarePen } from 'lucide-react';
const M3UProfiles = ({ playlist = null, isOpen, onClose }) => {
const profiles = usePlaylistsStore((state) => state.profiles[playlist.id]);
@ -69,14 +69,19 @@ const M3UProfiles = ({ playlist = null, isOpen, onClose }) => {
onChange={() => toggleActive(item)}
color="primary"
/>
<ActionIcon onClick={() => editProfile(item)} color="yellow.5">
<EditIcon />
<ActionIcon
onClick={() => editProfile(item)}
color="yellow.5"
variant="transparent"
>
<SquarePen size="18" />
</ActionIcon>
<ActionIcon
onClick={() => deleteProfile(item.id)}
color="error"
color="red.9"
variant="transparent"
>
<DeleteIcon />
<SquareMinus size="18" />
</ActionIcon>
</Box>
</Card>

View file

@ -2,15 +2,6 @@ import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import useChannelsStore from '../../store/channels';
import { notifications } from '@mantine/notifications';
import {
Add as AddIcon,
LiveTv as LiveTvIcon,
ContentCopy,
IndeterminateCheckBox,
CompareArrows,
Code,
AddBox,
} from '@mui/icons-material';
import API from '../../api';
import ChannelForm from '../forms/Channel';
import { TableHelper } from '../../helpers';
@ -29,6 +20,7 @@ import {
Binary,
ArrowDown01,
SquarePlus,
Copy,
} from 'lucide-react';
import ghostImage from '../../images/ghost.svg';
import {
@ -641,7 +633,7 @@ const ChannelsTable = ({}) => {
variant="transparent"
color="gray.5"
>
<ContentCopy size="18" fontSize="small" />
<Copy size="18" fontSize="small" />
</ActionIcon>
</Group>
</Popover.Dropdown>
@ -671,7 +663,7 @@ const ChannelsTable = ({}) => {
variant="transparent"
color="gray.5"
>
<ContentCopy size="18" fontSize="small" />
<Copy size="18" fontSize="small" />
</ActionIcon>
</Group>
</Popover.Dropdown>
@ -702,7 +694,7 @@ const ChannelsTable = ({}) => {
variant="transparent"
color="gray.5"
>
<ContentCopy size="18" fontSize="small" />
<Copy size="18" fontSize="small" />
</ActionIcon>
</Group>
</Popover.Dropdown>
@ -712,25 +704,13 @@ const ChannelsTable = ({}) => {
</Flex>
{/* Paper container: contains top toolbar and table (or ghost state) */}
<Paper
style={{
// bgcolor: theme.palette.background.paper,
// borderRadius: 2,
// overflow: 'hidden',
// display: 'flex',
// flexDirection: 'column',
height: 'calc(100vh - 75px)',
}}
>
<Paper>
{/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */}
<Box
style={{
display: 'flex',
// alignItems: 'center',
// backgroundColor: theme.palette.background.paper,
justifyContent: 'flex-end',
padding: 10,
// gap: 1,
}}
>
<Flex gap={6}>

View file

@ -1,12 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
Add as AddIcon,
Refresh as RefreshIcon,
} from '@mui/icons-material';
import useEPGsStore from '../../store/epgs';
import EPGForm from '../forms/EPG';
import { TableHelper } from '../../helpers';

View file

@ -1,25 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
Add as AddIcon,
SwapVert as SwapVertIcon,
Check as CheckIcon,
Close as CloseIcon,
} from '@mui/icons-material';
import {
LiveTv as LiveTvIcon,
ContentCopy,
Tv as TvIcon,
Clear as ClearIcon,
IndeterminateCheckBox,
CompareArrows,
Code,
AddBox,
Hd as HdIcon,
} from '@mui/icons-material';
import usePlaylistsStore from '../../store/playlists';
import M3UForm from '../forms/M3U';
import { TableHelper } from '../../helpers';
@ -46,6 +27,8 @@ import {
TvMinimalPlay,
SquarePen,
RefreshCcw,
Check,
X,
} from 'lucide-react';
import {
IconArrowDown,
@ -103,11 +86,7 @@ const Example = () => {
},
Cell: ({ cell }) => (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{cell.getValue() ? (
<CheckIcon color="success" />
) : (
<CloseIcon color="error" />
)}
{cell.getValue() ? <Check color="green.5" /> : <X color="red.9" />}
</Box>
),
},

View file

@ -1,13 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
Add as AddIcon,
Check as CheckIcon,
Close as CloseIcon,
} from '@mui/icons-material';
import StreamProfileForm from '../forms/StreamProfile';
import useStreamProfilesStore from '../../store/streamProfiles';
import { TableHelper } from '../../helpers';
@ -24,7 +17,7 @@ import {
useMantineTheme,
} from '@mantine/core';
import { IconSquarePlus } from '@tabler/icons-react';
import { SquareMinus, SquarePen } from 'lucide-react';
import { SquareMinus, SquarePen, Check, X } from 'lucide-react';
const StreamProfiles = () => {
const [profile, setProfile] = useState(null);
@ -62,11 +55,7 @@ const StreamProfiles = () => {
},
Cell: ({ cell }) => (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
{cell.getValue() ? (
<CheckIcon color="success" />
) : (
<CloseIcon color="error" />
)}
{cell.getValue() ? <Check color="success" /> : <X color="error" />}
</Box>
),
Filter: ({ column }) => (

View file

@ -1,22 +1,17 @@
import { useEffect, useMemo, useCallback, useState, useRef } from 'react';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import API from '../../api';
import { useTheme } from '@mui/material/styles';
import {
Delete as DeleteIcon,
Edit as EditIcon,
Add as AddIcon,
MoreVert as MoreVertIcon,
PlaylistAdd as PlaylistAddIcon,
IndeterminateCheckBox,
AddBox,
} from '@mui/icons-material';
import { TableHelper } from '../../helpers';
import StreamForm from '../forms/Stream';
import usePlaylistsStore from '../../store/playlists';
import useChannelsStore from '../../store/channels';
import { useDebounce } from '../../utils';
import { SquarePlus, ListPlus, SquareMinus } from 'lucide-react';
import {
SquarePlus,
ListPlus,
SquareMinus,
EllipsisVertical,
} from 'lucide-react';
import {
TextInput,
ActionIcon,
@ -49,8 +44,6 @@ import {
import { useNavigate } from 'react-router-dom';
const StreamsTable = ({}) => {
const theme = useTheme();
/**
* useState
*/
@ -538,7 +531,7 @@ const StreamsTable = ({}) => {
variant="transparent"
size="sm"
>
<MoreVertIcon />
<EllipsisVertical size="18" />
</ActionIcon>
</Menu.Target>
@ -620,25 +613,13 @@ const StreamsTable = ({}) => {
</Text>
</Flex>
<Paper
style={{
// bgcolor: theme.palette.background.paper,
// borderRadius: 2,
// overflow: 'hidden',
height: 'calc(100vh - 75px)',
// display: 'flex',
// flexDirection: 'column',
}}
>
<Paper>
{/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */}
<Box
style={{
display: 'flex',
// alignItems: 'center',
// backgroundColor: theme.palette.background.paper,
justifyContent: 'flex-end',
padding: 10,
// gap: 1,
}}
>
<Flex gap={6}>
@ -647,6 +628,7 @@ const StreamsTable = ({}) => {
variant="default"
size="xs"
onClick={deleteStreams}
disabled={rowSelection.length === 0}
>
Remove
</Button>

View file

@ -1,13 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import API from '../../api';
import {
Delete as DeleteIcon,
Edit as EditIcon,
Add as AddIcon,
Check as CheckIcon,
Close as CloseIcon,
} from '@mui/icons-material';
import { Check as CheckIcon, Close as CloseIcon } from '@mui/icons-material';
import useUserAgentsStore from '../../store/userAgents';
import UserAgentForm from '../forms/UserAgent';
import { TableHelper } from '../../helpers';
@ -24,14 +18,7 @@ import {
Box,
Button,
} from '@mantine/core';
import {
IconArrowDown,
IconArrowUp,
IconDeviceDesktopSearch,
IconSelector,
IconSortAscendingNumbers,
IconSquarePlus,
} from '@tabler/icons-react';
import { IconSquarePlus } from '@tabler/icons-react';
import { SquareMinus, SquarePen } from 'lucide-react';
const UserAgentsTable = () => {

View file

@ -26,13 +26,17 @@ export default {
size: 'xs',
},
mantineTableBodyRowProps: ({ isDetailPanel, row }) => {
if (isDetailPanel && row.getIsSelected()) {
return {
style: {
backgroundColor: '#163632',
},
};
}
return {
style: {
...(isDetailPanel && {
border: 'none',
}),
...(isDetailPanel &&
row.getIsSelected() && {
backgroundColor: '#163632',
}),
},
};
},
mantineTableBodyCellProps: {
style: {
@ -48,6 +52,9 @@ export default {
style: {
paddingLeft: 10,
paddingRight: 10,
paddingTop: 2,
paddingBottom: 2,
fontWeight: 'normal',
color: '#CFCFCF',
backgroundColor: '#383A3F',
borderColor: '#444',

View file

@ -7,34 +7,10 @@ const ChannelsPage = () => {
return (
<Grid style={{ padding: 18 }}>
<Grid.Col span={6}>
<Box
style={{
height: '100vh - 20px', // Full viewport height
paddingTop: 0, // Top padding
paddingBottom: 1, // Bottom padding
paddingRight: 0.5,
paddingLeft: 0,
boxSizing: 'border-box', // Include padding in height calculation
overflow: 'hidden', // Prevent parent scrolling
}}
>
<ChannelsTable />
</Box>
<ChannelsTable />
</Grid.Col>
<Grid.Col span={6}>
<Box
style={{
height: '100vh - 20px', // Full viewport height
paddingTop: 0, // Top padding
paddingBottom: 1, // Bottom padding
paddingRight: 0,
paddingLeft: 0.5,
boxSizing: 'border-box', // Include padding in height calculation
overflow: 'hidden', // Prevent parent scrolling
}}
>
<StreamsTable />
</Box>
<StreamsTable />
</Grid.Col>
</Grid>
);

View file

@ -1,5 +1,5 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import { ActionIcon, Box, Center, Grid } from '@mantine/core';
import { ActionIcon, Box, Center, Grid, Text } from '@mantine/core';
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
import { TableHelper } from '../helpers';
import API from '../api';
@ -18,7 +18,7 @@ import {
} from 'lucide-react';
const ChannelsPage = () => {
const { channels, stats: channelStats } = useChannelsStore();
const { channels, channelsByUUID, stats: channelStats } = useChannelsStore();
const [activeChannels, setActiveChannels] = useState([]);
const [clients, setClients] = useState([]);
@ -106,8 +106,8 @@ const ChannelsPage = () => {
await API.stopChannel(id);
};
const stopClient = async (id) => {
await API.stopClient(id);
const stopClient = async (channelId, clientId) => {
await API.stopClient(channelId, clientId);
};
const channelsTable = useMantineReactTable({
@ -165,6 +165,12 @@ const ChannelsPage = () => {
header: 'Channel',
accessorKey: 'channel.name',
size: 100,
mantineTableBodyCellProps: {
style: {
whiteSpace: 'nowrap',
maxWidth: 100,
},
},
},
{
header: 'User-Agent',
@ -210,19 +216,21 @@ const ChannelsPage = () => {
</Center>
</Box>
),
mantineTableContainerProps: {
style: {
height: '100%',
overflowY: 'auto',
},
},
});
useEffect(() => {
const stats = channelStats.channels.map((ch) => ({
...ch,
...Object.values(channels).filter(
(channel) => channel.uuid === channelStats.channels[0].channel_id
)[0],
...channels[channelsByUUID[ch.channel_id]],
}));
setActiveChannels(stats);
console.log(stats);
const clientStats = stats.reduce((acc, ch) => {
return acc.concat(
ch.clients.map((client) => ({
@ -232,66 +240,47 @@ const ChannelsPage = () => {
);
}, []);
setClients(clientStats);
console.log(clientStats);
}, [channelStats]);
// const fetchData = useCallback(async () => {
// const response = await API.getChannelStats();
// const channelStats = response.channels.map((ch) => ({
// ...ch,
// ...Object.values(channels).filter(
// (channel) => channel.uuid === response.channels[0].channel_id
// )[0],
// }));
// setActiveChannels(channelStats);
// console.log(channelStats);
// const clientStats = channelStats.reduce((acc, ch) => {
// return acc.concat(
// ch.clients.map((client) => ({
// ...client,
// channel: ch,
// }))
// );
// }, []);
// setClients(clientStats);
// console.log(clientStats);
// }, [channels]);
// useEffect(() => {
// fetchData();
// }, [fetchData]);
return (
<Grid style={{ padding: 18 }}>
<Grid.Col span={6}>
<Box
<Text
w={88}
h={24}
style={{
height: '100vh - 20px', // Full viewport height
paddingTop: 0, // Top padding
paddingBottom: 1, // Bottom padding
paddingRight: 0.5,
paddingLeft: 0,
boxSizing: 'border-box', // Include padding in height calculation
overflow: 'hidden', // Prevent parent scrolling
fontFamily: 'Inter, sans-serif',
fontWeight: 500,
fontSize: '20px',
lineHeight: 1,
letterSpacing: '-0.3px',
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
marginBottom: 0,
}}
>
Channels
</Text>
<Box style={{ paddingTop: 10 }}>
<MantineReactTable table={channelsTable} />
</Box>
</Grid.Col>
<Grid.Col span={6}>
<Box
<Text
w={88}
h={24}
style={{
height: '100vh - 20px', // Full viewport height
paddingTop: 0, // Top padding
paddingBottom: 1, // Bottom padding
paddingRight: 0,
paddingLeft: 0.5,
boxSizing: 'border-box', // Include padding in height calculation
overflow: 'hidden', // Prevent parent scrolling
fontFamily: 'Inter, sans-serif',
fontWeight: 500,
fontSize: '20px',
lineHeight: 1,
letterSpacing: '-0.3px',
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
marginBottom: 0,
}}
>
Clients
</Text>
<Box style={{ paddingTop: 10 }}>
<MantineReactTable table={clientsTable} />
</Box>
</Grid.Col>

View file

@ -1,11 +1,15 @@
import { create } from 'zustand';
import api from '../api';
import { notifications } from '@mantine/notifications';
const useChannelsStore = create((set) => ({
const useChannelsStore = create((set, get) => ({
channels: [],
channelsByUUID: {},
channelGroups: [],
channelsPageSelection: [],
stats: [],
stats: {},
activeChannels: {},
activeClients: {},
isLoading: false,
error: null,
@ -13,11 +17,15 @@ const useChannelsStore = create((set) => ({
set({ isLoading: true, error: null });
try {
const channels = await api.getChannels();
const channelsByUUID = {};
const channelsByID = channels.reduce((acc, channel) => {
acc[channel.id] = channel;
channelsByUUID[channel.uuid] = channel.id;
return acc;
}, {});
set({
channels: channels.reduce((acc, channel) => {
acc[channel.id] = channel;
return acc;
}, {}),
channels: channelsByID,
channelsByUUID,
isLoading: false,
});
} catch (error) {
@ -43,35 +51,59 @@ const useChannelsStore = create((set) => ({
...state.channels,
[newChannel.id]: newChannel,
},
})),
addChannels: (newChannels) =>
set((state) => ({
channels: {
...state.channels,
...newChannels.reduce((acc, channel) => {
acc[channel.id] = channel;
return acc;
}, {}),
channelsByUUID: {
...state.channelsByUUID,
[newChannel.uuid]: newChannel.id,
},
})),
addChannels: (newChannels) => {
const channelsByUUID = {};
const channelsByID = newChannels.reduce((acc, channel) => {
acc[channel.id] = channel;
channelsByUUID[channel.uuid] = channel.id;
return acc;
}, {});
return set((state) => ({
channels: {
...state.channels,
...channelsByID,
},
channelsByUUID: {
...state.channelsByUUID,
...channelsByUUID,
},
}));
},
updateChannel: (channel) =>
set((state) => ({
channels: {
...state.channels,
[channel.id]: channel,
},
channelsByUUID: {
...state.channelsByUUID,
[channel.uuid]: channel.id,
},
})),
removeChannels: (channelIds) =>
set((state) => {
const updatedChannels = { ...state.channels };
const channelsByUUID = { ...state.channelsByUUID };
for (const id of channelIds) {
delete updatedChannels[id];
for (const uuid in channelsByUUID) {
if (channelsByUUID[uuid] == id) {
delete channelsByUUID[uuid];
break;
}
}
}
return { channels: updatedChannels };
return { channels: updatedChannels, channelsByUUID };
}),
addChannelGroup: (newChannelGroup) =>
@ -89,10 +121,75 @@ const useChannelsStore = create((set) => ({
setChannelsPageSelection: (channelsPageSelection) =>
set((state) => ({ channelsPageSelection })),
setChannelStats: (stats) =>
set((state) => ({
setChannelStats: (stats) => {
const {
channels,
stats: currentStats,
activeChannels: oldChannels,
activeClients: oldClients,
channelsByUUID,
} = get();
const newClients = {};
const newChannels = stats.channels.reduce((acc, ch) => {
acc[ch.channel_id] = ch;
if (currentStats.channels) {
if (oldChannels[ch.channel_id] === undefined) {
notifications.show({
title: 'New channel streaming',
message: channels[channelsByUUID[ch.channel_id]].name,
color: 'blue.5',
});
}
}
ch.clients.map((client) => {
newClients[client.client_id] = client;
// This check prevents the notifications if streams are active on page load
if (currentStats.channels) {
if (oldClients[client.client_id] === undefined) {
notifications.show({
title: 'New client started streaming',
message: `Client streaming from ${client.ip_address}`,
color: 'blue.5',
});
}
}
});
return acc;
}, {});
// This check prevents the notifications if streams are active on page load
if (currentStats.channels) {
for (const uuid in oldChannels) {
if (newChannels[uuid] === undefined) {
notifications.show({
title: 'Channel streaming stopped',
message: channels[channelsByUUID[uuid]].name,
color: 'blue.5',
});
}
}
for (const clientId in oldClients) {
if (newClients[clientId] === undefined) {
notifications.show({
title: 'Client stopped streaming',
message: `Client stopped streaming from ${oldClients[clientId].ip_address}`,
color: 'blue.5',
});
}
}
}
return set((state) => ({
stats,
})),
activeChannels: newChannels,
activeClients: newClients,
}));
},
}));
export default useChannelsStore;

View file

@ -1,92 +0,0 @@
// src/theme.js
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
mode: 'dark',
background: {
default: '#18181b', // Global background color (Tailwind zinc-900)
paper: '#27272a', // Paper background (Tailwind zinc-800)
},
primary: {
main: '#4A90E2',
contrastText: '#FFFFFF',
},
secondary: {
main: '#F5A623',
contrastText: '#FFFFFF',
},
text: {
primary: '#FFFFFF',
secondary: '#d4d4d8', // Updated secondary text color (Tailwind zinc-300)
},
// Custom colors for components (chip buttons, borders, etc.)
custom: {
// For chip buttons:
greenMain: '#90C43E',
greenHoverBg: 'rgba(144,196,62,0.1)',
indigoMain: '#4F39F6',
indigoHoverBg: 'rgba(79,57,246,0.1)',
greyBorder: '#707070',
greyHoverBg: 'rgba(112,112,112,0.1)',
greyText: '#a0a0a0',
// Common border colors:
borderDefault: '#3f3f46', // Tailwind zinc-700
borderHover: '#5f5f66', // Approximate Tailwind zinc-600
// For the "Add" button:
successBorder: '#00a63e',
successBg: '#0d542b',
successBgHover: '#0a4020',
successIcon: '#05DF72',
},
},
typography: {
// Set Inter as the global font
fontFamily: 'Inter, sans-serif',
h1: { fontSize: '2.5rem', fontWeight: 700 },
h2: { fontSize: '2rem', fontWeight: 700 },
body1: { fontSize: '1rem' },
},
spacing: 8,
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 4,
textTransform: 'none',
fontWeight: 500,
},
},
},
MuiDrawer: {
styleOverrides: {
paper: {
backgroundColor: '#27272a', // Use the same paper color
color: '#FFFFFF',
},
},
},
MuiAppBar: {
styleOverrides: {
root: {
backgroundColor: '#18181b',
},
},
},
},
custom: {
sidebar: {
activeBackground: 'rgba(21, 69, 62, 0.67)',
activeBorder: '#14917e',
hoverBackground: '#27272a',
hoverBorder: '#3f3f46',
fontFamily: 'Inter, sans-serif',
},
},
});
export default theme;