Initial commit
This commit is contained in:
12
front/src/components/core/Loading.js
Normal file
12
front/src/components/core/Loading.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import {Backdrop, CircularProgress} from "@mui/material";
|
||||
|
||||
export function Loading({loading}) {
|
||||
return (
|
||||
<Backdrop
|
||||
sx={{color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1}}
|
||||
open={loading}
|
||||
>
|
||||
<CircularProgress color="inherit"/>
|
||||
</Backdrop>
|
||||
);
|
||||
}
|
||||
10
front/src/components/core/LocalizationProvider.js
Normal file
10
front/src/components/core/LocalizationProvider.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import {LocalizationProvider as Provider} from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import * as React from 'react';
|
||||
import 'dayjs/locale/ru'
|
||||
|
||||
export function LocalizationProvider({children}) {
|
||||
return (
|
||||
<Provider dateAdapter={AdapterDayjs} adapterLocale="ru-Ru">{children}</Provider>
|
||||
);
|
||||
}
|
||||
38
front/src/components/core/Logo.js
Normal file
38
front/src/components/core/Logo.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useColorScheme } from '@mui/material/styles';
|
||||
import {NoSsr} from "./NoSsr";
|
||||
|
||||
const HEIGHT = 60;
|
||||
const WIDTH = 60;
|
||||
|
||||
export function Logo({ color = 'dark', emblem, height = HEIGHT, width = WIDTH }) {
|
||||
let url;
|
||||
|
||||
if (emblem) {
|
||||
url = color === 'light' ? '/assets/logo-emblem.svg' : '/assets/logo-emblem--dark.svg';
|
||||
} else {
|
||||
url = color === 'light' ? '/assets/logo.svg' : '/assets/logo--dark.svg';
|
||||
}
|
||||
|
||||
return <Box alt="logo" component="img" height={height} src={url} width={width} />;
|
||||
}
|
||||
|
||||
export function DynamicLogo({
|
||||
colorDark = 'light',
|
||||
colorLight = 'dark',
|
||||
height = HEIGHT,
|
||||
width = WIDTH,
|
||||
...props
|
||||
}) {
|
||||
const { colorScheme } = useColorScheme();
|
||||
const color = colorScheme === 'dark' ? colorDark : colorLight;
|
||||
|
||||
return (
|
||||
<NoSsr fallback={<Box sx={{ height: `${height}px`, width: `${width}px` }} />}>
|
||||
<Logo color={color} height={height} width={width} {...props} />
|
||||
</NoSsr>
|
||||
);
|
||||
}
|
||||
9
front/src/components/core/ModalDialogStyled.js
Normal file
9
front/src/components/core/ModalDialogStyled.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import {styled} from "@mui/material/styles";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
|
||||
export const ModalDialogStyled = styled(Dialog)(({theme}) => ({
|
||||
backdrop: {
|
||||
margin: '4px',
|
||||
border: 'solid',
|
||||
},
|
||||
}));
|
||||
25
front/src/components/core/NoSsr.js
Normal file
25
front/src/components/core/NoSsr.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
|
||||
|
||||
export function NoSsr(props) {
|
||||
const {children, defer = false, fallback = null} = props;
|
||||
const [mountedState, setMountedState] = React.useState(false);
|
||||
|
||||
useEnhancedEffect(() => {
|
||||
if (!defer) {
|
||||
setMountedState(true);
|
||||
}
|
||||
}, [defer]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (defer) {
|
||||
setMountedState(true);
|
||||
}
|
||||
}, [defer]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{mountedState ? children : fallback}
|
||||
</>
|
||||
)
|
||||
}
|
||||
24
front/src/components/core/TabPanel.js
Normal file
24
front/src/components/core/TabPanel.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import PropTypes from "prop-types";
|
||||
import * as React from "react";
|
||||
|
||||
export function CustomTabPanel(props) {
|
||||
const {children, value, index, ...other} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CustomTabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired,
|
||||
};
|
||||
81
front/src/components/core/ThemeSwitch.js
Normal file
81
front/src/components/core/ThemeSwitch.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import {styled, useColorScheme} from "@mui/material/styles";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import React from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
export function ThemeSwitch() {
|
||||
const {mode, setMode} = useColorScheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
// backgroundColor: 'var(--mui-palette-neutral-950)',
|
||||
border: '1px solid var(--mui-palette-neutral-700)',
|
||||
borderRadius: '12px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
p: '4px 12px',
|
||||
}}
|
||||
>
|
||||
<StyledSwitch
|
||||
checked={mode === 'dark'}
|
||||
onChange={(e) => setMode(e.target.checked ? 'dark' : 'light')}
|
||||
inputProps={{'aria-label': 'controlled'}}
|
||||
/>
|
||||
<Box sx={{flex: '1 1 auto'}}>
|
||||
<Typography color="inherit" variant="subtitle1">
|
||||
{(mode === 'dark' ? "Темная " : "Светлая ") + "тема"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledSwitch = styled(Switch)(({theme}) => ({
|
||||
width: 62,
|
||||
height: 34,
|
||||
padding: 7,
|
||||
'& .MuiSwitch-switchBase': {
|
||||
margin: 1,
|
||||
padding: 0,
|
||||
transform: 'translateX(6px)',
|
||||
'&.Mui-checked': {
|
||||
color: '#fff',
|
||||
transform: 'translateX(22px)',
|
||||
'& .MuiSwitch-thumb:before': {
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||
'#fff',
|
||||
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`,
|
||||
},
|
||||
'& + .MuiSwitch-track': {
|
||||
opacity: 1,
|
||||
backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be',
|
||||
},
|
||||
},
|
||||
},
|
||||
'& .MuiSwitch-thumb': {
|
||||
backgroundColor: theme.palette.mode === 'dark' ? '#003892' : '#001e3c',
|
||||
width: 32,
|
||||
height: 32,
|
||||
'&::before': {
|
||||
content: "''",
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||
'#fff',
|
||||
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`,
|
||||
},
|
||||
},
|
||||
'& .MuiSwitch-track': {
|
||||
opacity: 1,
|
||||
backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be',
|
||||
borderRadius: 20 / 2,
|
||||
},
|
||||
}));
|
||||
92
front/src/components/core/UserPopover.js
Normal file
92
front/src/components/core/UserPopover.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import MenuList from '@mui/material/MenuList';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {SignOut as SignOutIcon} from '@phosphor-icons/react/dist/ssr/SignOut';
|
||||
import {logger} from "../../lib/DefaultLogger";
|
||||
import {useAuth} from "../../hooks/useAuth";
|
||||
import {authClient} from "../../lib/clients/AuthClient";
|
||||
import {useLocation} from "react-router-dom";
|
||||
import {useUser} from "../../hooks/useUser";
|
||||
|
||||
export function UserPopover({anchorEl, onClose, open}) {
|
||||
const {checkSession} = useAuth();
|
||||
const {user, session} = useUser();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const handleSignOut = React.useCallback(async () => {
|
||||
try {
|
||||
const {error} = await authClient.signOut();
|
||||
|
||||
if (error) {
|
||||
logger.error('Sign out error', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the auth state
|
||||
await checkSession?.();
|
||||
|
||||
// UserProvider, for this case, will not refresh the router and we need to do it manually
|
||||
window.location.reload();
|
||||
// After refresh, AuthGuard will handle the redirect
|
||||
} catch (err) {
|
||||
logger.error('Sign out error', err);
|
||||
}
|
||||
}, [checkSession, location]);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{horizontal: 'left', vertical: 'bottom'}}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
slotProps={{paper: {sx: {width: '240px'}}}}
|
||||
>
|
||||
<Box sx={{p: '16px 20px '}}>
|
||||
{userDescriptor(user, session)}
|
||||
</Box>
|
||||
<Divider/>
|
||||
<MenuList disablePadding sx={{p: '8px', '& .MuiMenuItem-root': {borderRadius: 1}}}>
|
||||
{/*<MenuItem component={'a'} href={paths.dashboard.settings} onClick={onClose}>*/}
|
||||
{/* <ListItemIcon>*/}
|
||||
{/* <GearSixIcon fontSize="var(--icon-fontSize-md)"/>*/}
|
||||
{/* </ListItemIcon>*/}
|
||||
{/* Настройки*/}
|
||||
{/*</MenuItem>*/}
|
||||
{/*<MenuItem component={'a'} href={paths.dashboard.account} onClick={onClose}>*/}
|
||||
{/* <ListItemIcon>*/}
|
||||
{/* <UserIcon fontSize="var(--icon-fontSize-md)"/>*/}
|
||||
{/* </ListItemIcon>*/}
|
||||
{/* Профиль*/}
|
||||
{/*</MenuItem>*/}
|
||||
<MenuItem onClick={handleSignOut}>
|
||||
<ListItemIcon>
|
||||
<SignOutIcon fontSize="var(--icon-fontSize-md)"/>
|
||||
</ListItemIcon>
|
||||
Выход
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function userDescriptor(user, session) {
|
||||
if (!user) {
|
||||
return (<Typography variant="subtitle1">Ошибка загрузки данных</Typography>);
|
||||
}
|
||||
|
||||
const open = (session.isActive && user.invited) ? "открыт" : "закрыт";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle1">{user.name + " " + user.lastName}</Typography>
|
||||
<Typography color="text.secondary" variant="body2">{user.id}</Typography>
|
||||
<Typography color="text.secondary" variant="body2">{`Бар ${open}`}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
29
front/src/components/core/descendingComparator.js
Normal file
29
front/src/components/core/descendingComparator.js
Normal file
@@ -0,0 +1,29 @@
|
||||
export function descendingComparator(a, b, orderBy, lastOrder) {
|
||||
if (getValue(b, orderBy) < getValue(a, orderBy)) {
|
||||
return -1;
|
||||
}
|
||||
if (getValue(b, orderBy) > getValue(a, orderBy)) {
|
||||
return 1;
|
||||
}
|
||||
if (lastOrder && orderBy !== lastOrder) {
|
||||
if (getValue(b, lastOrder) < getValue(a, lastOrder)) {
|
||||
return 1;
|
||||
}
|
||||
if (getValue(b, lastOrder) > getValue(a, lastOrder)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getValue(obj, orderBy) {
|
||||
if (!orderBy) {
|
||||
return obj;
|
||||
}
|
||||
const split = orderBy.split(".")
|
||||
let res = obj[split[0]];
|
||||
for (let i = 1; i < split.length; i++) {
|
||||
res = res[split[i]];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
10
front/src/components/core/getComparator.js
Normal file
10
front/src/components/core/getComparator.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import {descendingComparator} from "./descendingComparator";
|
||||
|
||||
export function getComparator(order, orderBy, lastOrder) {
|
||||
if(!order) {
|
||||
order = "asc"
|
||||
}
|
||||
return order === 'desc'
|
||||
? (a, b) => descendingComparator(a, b, orderBy, lastOrder)
|
||||
: (a, b) => -descendingComparator(a, b, orderBy, lastOrder);
|
||||
}
|
||||
14
front/src/components/core/groupByForLoop.js
Normal file
14
front/src/components/core/groupByForLoop.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export const groupByForLoop = (arr, prop) => {
|
||||
const grouped = new Map();
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let key = arr[i][prop];
|
||||
if (!key) {
|
||||
key = "Без категории"
|
||||
}
|
||||
if (!grouped.has(key)) {
|
||||
grouped.set(key, []);
|
||||
}
|
||||
grouped.get(key).push(arr[i]);
|
||||
}
|
||||
return grouped;
|
||||
};
|
||||
37
front/src/components/core/navIcons.js
Normal file
37
front/src/components/core/navIcons.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import {ChartPie as ChartPieIcon} from '@phosphor-icons/react/dist/ssr/ChartPie';
|
||||
import {GearSix as GearSixIcon} from '@phosphor-icons/react/dist/ssr/GearSix';
|
||||
import {PlugsConnected as PlugsConnectedIcon} from '@phosphor-icons/react/dist/ssr/PlugsConnected';
|
||||
import {User as UserIcon} from '@phosphor-icons/react/dist/ssr/User';
|
||||
import {Users as UsersIcon} from '@phosphor-icons/react/dist/ssr/Users';
|
||||
import {XSquare} from '@phosphor-icons/react/dist/ssr/XSquare';
|
||||
import {
|
||||
Basket,
|
||||
BookOpen,
|
||||
Books,
|
||||
Cheers,
|
||||
CoffeeBean,
|
||||
Coins,
|
||||
Martini,
|
||||
Storefront,
|
||||
Users,
|
||||
Wallet
|
||||
} from "@phosphor-icons/react";
|
||||
|
||||
export const navIcons = {
|
||||
'menu': BookOpen,
|
||||
'list': Books,
|
||||
'storefront': Storefront,
|
||||
'wallet': Wallet,
|
||||
'cocktail': Martini,
|
||||
'visitors': Users,
|
||||
'orders': Cheers,
|
||||
'basket': Basket,
|
||||
'coins': Coins,
|
||||
'ingredients': CoffeeBean,
|
||||
'chart-pie': ChartPieIcon,
|
||||
'gear-six': GearSixIcon,
|
||||
'plugs-connected': PlugsConnectedIcon,
|
||||
'x-square': XSquare,
|
||||
user: UserIcon,
|
||||
users: UsersIcon,
|
||||
}
|
||||
6
front/src/components/core/tabProps.js
Normal file
6
front/src/components/core/tabProps.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user