This commit is contained in:
louiscklaw
2025-01-31 20:14:02 +08:00
parent 49e275d85d
commit 5c584709c4
706 changed files with 40207 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
DB_HOST=localhost
DB_PORT=6033
DB_NAME=app_db
DB_USER=db_user
DB_PASSWORD=db_user_pass

View File

@@ -0,0 +1,12 @@
```batch
> .\dc_up.bat
```
*nix
```batch
> .\dc_up.sh
```

View File

@@ -0,0 +1,51 @@
// ** MUI Imports
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import CardContent from '@mui/material/CardContent';
// ** Icons Imports
import DotsVertical from 'mdi-material-ui/DotsVertical';
const CardStatsVertical = props => {
// ** Props
const { title, subtitle, color, icon, stats, trend, trendNumber } = props;
return (
<Card>
<CardContent>
<Box sx={{ display: 'flex', marginBottom: 5.5, alignItems: 'flex-start', justifyContent: 'space-between' }}>
<Avatar sx={{ boxShadow: 3, marginRight: 4, color: 'common.white', backgroundColor: `${color}.main` }}>
{icon}
</Avatar>
<IconButton size="small" aria-label="settings" className="card-more-options" sx={{ color: 'text.secondary' }}>
<DotsVertical />
</IconButton>
</Box>
<Typography sx={{ fontWeight: 600, fontSize: '0.875rem' }}>{title}</Typography>
<Box sx={{ marginTop: 1.5, display: 'flex', flexWrap: 'wrap', marginBottom: 1.5, alignItems: 'flex-start' }}>
<Typography variant="h6" sx={{ mr: 2 }}>
{stats}
</Typography>
<Typography
component="sup"
variant="caption"
sx={{ color: trend === 'positive' ? 'success.main' : 'error.main' }}
>
{trendNumber}
</Typography>
</Box>
<Typography variant="caption">{subtitle}</Typography>
</CardContent>
</Card>
);
};
export default CardStatsVertical;
CardStatsVertical.defaultProps = {
color: 'primary',
trend: 'positive',
};

View File

@@ -0,0 +1,7 @@
// ** Next Import
import dynamic from 'next/dynamic';
// ! To avoid 'Window is not defined' error
const ReactApexcharts = dynamic(() => import('react-apexcharts'), { ssr: false });
export default ReactApexcharts;

View File

@@ -0,0 +1,39 @@
// ** MUI Imports
import Zoom from '@mui/material/Zoom';
import { styled } from '@mui/material/styles';
import useScrollTrigger from '@mui/material/useScrollTrigger';
const ScrollToTopStyled = styled('div')(({ theme }) => ({
zIndex: 11,
position: 'fixed',
right: theme.spacing(6),
bottom: theme.spacing(10),
}));
const ScrollToTop = props => {
// ** Props
const { children, className } = props;
// ** init trigger
const trigger = useScrollTrigger({
threshold: 400,
disableHysteresis: true,
});
const handleClick = () => {
const anchor = document.querySelector('body');
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth' });
}
};
return (
<Zoom in={trigger}>
<ScrollToTopStyled className={className} onClick={handleClick} role="presentation">
{children}
</ScrollToTopStyled>
</Zoom>
);
};
export default ScrollToTop;

View File

@@ -0,0 +1,30 @@
// ** React Imports
import { createContext, useState } from 'react';
// ** ThemeConfig Import
import themeConfig from 'src/configs/themeConfig';
const initialSettings = {
themeColor: 'primary',
mode: themeConfig.mode,
contentWidth: themeConfig.contentWidth,
};
// ** Create Context
export const SettingsContext = createContext({
saveSettings: () => null,
settings: initialSettings,
});
export const SettingsProvider = ({ children }) => {
// ** State
const [settings, setSettings] = useState({ ...initialSettings });
const saveSettings = updatedSettings => {
setSettings(updatedSettings);
};
return <SettingsContext.Provider value={{ settings, saveSettings }}>{children}</SettingsContext.Provider>;
};
export const SettingsConsumer = SettingsContext.Consumer;

View File

@@ -0,0 +1,4 @@
import { useContext } from 'react';
import { SettingsContext } from 'src/@core/context/settingsContext';
export const useSettings = () => useContext(SettingsContext);

View File

@@ -0,0 +1,37 @@
// ** MUI Imports
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
// Styled component for Blank Layout component
const BlankLayoutWrapper = styled(Box)(({ theme }) => ({
height: '100vh',
// For V1 Blank layout pages
'& .content-center': {
display: 'flex',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
padding: theme.spacing(5),
},
// For V2 Blank layout pages
'& .content-right': {
display: 'flex',
minHeight: '100vh',
overflowX: 'hidden',
position: 'relative',
},
}));
const BlankLayout = ({ children }) => {
return (
<BlankLayoutWrapper className="layout-wrapper">
<Box className="app-content" sx={{ minHeight: '100vh', overflowX: 'hidden', position: 'relative' }}>
{children}
</Box>
</BlankLayoutWrapper>
);
};
export default BlankLayout;

View File

@@ -0,0 +1,104 @@
// ** MUI Imports
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import { Badge, Button, IconButton, Stack, Typography } from '@mui/material';
import { useRouter } from 'next/router';
import HomeIcon from '@mui/icons-material/Home';
import { CartConsumer, CartContext, CartProvider } from 'src/contexts/cart';
import { useContext, useEffect, useState } from 'react';
import CartIcon from './components/CartIcon';
import AccountBoxIcon from '@mui/icons-material/AccountBox';
import CheckSession from 'src/api/checkSession';
import { AuthContext, AuthProvider } from 'src/contexts/auth';
// Styled component for Blank Layout component
const ShopfrontLayoutWrapper = styled(Box)(({ theme }) => ({
height: '100vh',
// For V1 Blank layout pages
'& .content-center': {
display: 'flex',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
padding: theme.spacing(5),
},
// For V2 Blank layout pages
'& .content-right': {
display: 'flex',
minHeight: '100vh',
overflowX: 'hidden',
position: 'relative',
},
}));
const ShopfrontLayout = ({ children }) => {
const router = useRouter();
const [data, setData] = useState({});
const { username, setUsername } = useContext(AuthContext);
useEffect(() => {
const check = async () => {
try {
let { session } = JSON.parse(localStorage.getItem('session')) || '';
let data = await CheckSession({ session });
if (data['status'] != 'OK') {
return route.replace('/shopfront/login');
} else {
setData(data);
setUsername(data['username']);
}
} catch (error) {}
};
return check();
}, []);
return (
<CartProvider>
<ShopfrontLayoutWrapper className="layout-wrapper">
<Box className="app-content" sx={{ minHeight: '100vh', overflowX: 'hidden', position: 'relative' }}>
<Stack direction="column" alignItems={'center'} justifyContent={'center'}>
<Box sx={{ width: '80vw' }}>
<Stack direction="column">
<Stack direction="row" justifyContent={'space-between'} alignItems={'center'} minHeight={'10vh'}>
<Box style={{ width: 'calc ( 80vw / 3 )' }}>
<Typography variant="h5">Hi, {username || 'Guest'}</Typography>
</Box>
<Box style={{ width: 'calc ( 80vw / 3 )' }}>
<Typography variant="h5">VTKH Mall</Typography>
</Box>
<Box style={{ width: 'calc ( 80vw / 3 )' }}>
<IconButton
onClick={() => {
router.push('/shopfront/customer/profile');
}}
>
<AccountBoxIcon />
</IconButton>
<IconButton
onClick={() => {
router.push('/shopfront');
}}
>
<HomeIcon />
</IconButton>
<CartIcon />
</Box>
</Stack>
{children}
<Box height={'5rem'}></Box>
</Stack>
</Box>
</Stack>
</Box>
</ShopfrontLayoutWrapper>
</CartProvider>
);
};
export default ShopfrontLayout;

View File

@@ -0,0 +1,109 @@
// ** React Imports
import { useState } from 'react';
// ** MUI Imports
import Fab from '@mui/material/Fab';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
// ** Icons Imports
import ArrowUp from 'mdi-material-ui/ArrowUp';
// ** Theme Config Import
import themeConfig from 'src/configs/themeConfig';
// ** Components
import AppBar from './components/vertical/appBar';
import Navigation from './components/vertical/navigation';
import Footer from './components/shared-components/footer';
import ScrollToTop from 'src/@core/components/scroll-to-top';
// ** Styled Component
import DatePickerWrapper from 'src/@core/styles/libs/react-datepicker';
const VerticalLayoutWrapper = styled('div')({
height: '100%',
display: 'flex',
});
const MainContentWrapper = styled(Box)({
flexGrow: 1,
minWidth: 0,
display: 'flex',
minHeight: '100vh',
flexDirection: 'column',
});
const ContentWrapper = styled('main')(({ theme }) => ({
flexGrow: 1,
width: '100%',
padding: theme.spacing(6),
transition: 'padding .25s ease-in-out',
[theme.breakpoints.down('sm')]: {
paddingLeft: theme.spacing(4),
paddingRight: theme.spacing(4),
},
}));
const VerticalLayout = props => {
// ** Props
const { settings, children, scrollToTop } = props;
// ** Vars
const { contentWidth } = settings;
const navWidth = themeConfig.navigationSize;
// ** States
const [navVisible, setNavVisible] = useState(false);
// ** Toggle Functions
const toggleNavVisibility = () => setNavVisible(!navVisible);
return (
<>
<VerticalLayoutWrapper className="layout-wrapper">
<Navigation
navWidth={navWidth}
navVisible={navVisible}
setNavVisible={setNavVisible}
toggleNavVisibility={toggleNavVisibility}
{...props}
/>
<MainContentWrapper className="layout-content-wrapper">
<AppBar toggleNavVisibility={toggleNavVisibility} {...props} />
<ContentWrapper
className="layout-page-content"
sx={{
...(contentWidth === 'boxed' && {
mx: 'auto',
'@media (min-width:1440px)': { maxWidth: 1440 },
'@media (min-width:1200px)': { maxWidth: '100%' },
}),
}}
>
{children}
</ContentWrapper>
<Footer {...props} />
<DatePickerWrapper sx={{ zIndex: 11 }}>
<Box id="react-datepicker-portal"></Box>
</DatePickerWrapper>
</MainContentWrapper>
</VerticalLayoutWrapper>
{scrollToTop ? (
scrollToTop(props)
) : (
<ScrollToTop className="mui-fixed">
<Fab color="primary" size="small" aria-label="scroll back to top">
<ArrowUp />
</Fab>
</ScrollToTop>
)}
</>
);
};
export default VerticalLayout;

View File

@@ -0,0 +1,29 @@
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import { Badge, IconButton } from '@mui/material';
import { useRouter } from 'next/router';
import { useContext } from 'react';
import { CartContext } from 'src/contexts/cart';
function CartIcon() {
let router = useRouter();
let { cart } = useContext(CartContext);
if (!cart) return <></>;
return (
<>
<IconButton
onClick={() => {
router.push('/shopfront/cart');
}}
>
<Badge badgeContent={cart?.length} color="primary">
<ShoppingCartIcon />
</Badge>
</IconButton>
</>
);
}
export default CartIcon;

View File

@@ -0,0 +1,30 @@
import IconButton from '@mui/material/IconButton';
// ** Icons Imports
import WeatherNight from 'mdi-material-ui/WeatherNight';
import WeatherSunny from 'mdi-material-ui/WeatherSunny';
const ModeToggler = props => {
// ** Props
const { settings, saveSettings } = props;
const handleModeChange = mode => {
saveSettings({ ...settings, mode });
};
const handleModeToggle = () => {
if (settings.mode === 'light') {
handleModeChange('dark');
} else {
handleModeChange('light');
}
};
return (
<IconButton color="inherit" aria-haspopup="true" onClick={handleModeToggle}>
{settings.mode === 'dark' ? <WeatherSunny /> : <WeatherNight />}
</IconButton>
);
};
export default ModeToggler;

View File

@@ -0,0 +1,217 @@
// ** React Imports
import { useState, Fragment } from 'react';
// ** MUI Imports
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import { styled } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import MuiMenu from '@mui/material/Menu';
import MuiAvatar from '@mui/material/Avatar';
import MuiMenuItem from '@mui/material/MenuItem';
import Typography from '@mui/material/Typography';
// ** Icons Imports
import BellOutline from 'mdi-material-ui/BellOutline';
// ** Third Party Components
import PerfectScrollbarComponent from 'react-perfect-scrollbar';
// ** Styled Menu component
const Menu = styled(MuiMenu)(({ theme }) => ({
'& .MuiMenu-paper': {
width: 380,
overflow: 'hidden',
marginTop: theme.spacing(4),
[theme.breakpoints.down('sm')]: {
width: '100%',
},
},
'& .MuiMenu-list': {
padding: 0,
},
}));
// ** Styled MenuItem component
const MenuItem = styled(MuiMenuItem)(({ theme }) => ({
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(3),
borderBottom: `1px solid ${theme.palette.divider}`,
}));
const styles = {
maxHeight: 349,
'& .MuiMenuItem-root:last-of-type': {
border: 0,
},
};
// ** Styled PerfectScrollbar component
const PerfectScrollbar = styled(PerfectScrollbarComponent)({
...styles,
});
// ** Styled Avatar component
const Avatar = styled(MuiAvatar)({
width: '2.375rem',
height: '2.375rem',
fontSize: '1.125rem',
});
// ** Styled component for the title in MenuItems
const MenuItemTitle = styled(Typography)(({ theme }) => ({
fontWeight: 600,
flex: '1 1 100%',
overflow: 'hidden',
fontSize: '0.875rem',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
marginBottom: theme.spacing(0.75),
}));
// ** Styled component for the subtitle in MenuItems
const MenuItemSubtitle = styled(Typography)({
flex: '1 1 100%',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
});
const NotificationDropdown = () => {
// ** States
const [anchorEl, setAnchorEl] = useState(null);
// ** Hook
const hidden = useMediaQuery(theme => theme.breakpoints.down('lg'));
const handleDropdownOpen = event => {
setAnchorEl(event.currentTarget);
};
const handleDropdownClose = () => {
setAnchorEl(null);
};
const ScrollWrapper = ({ children }) => {
if (hidden) {
return <Box sx={{ ...styles, overflowY: 'auto', overflowX: 'hidden' }}>{children}</Box>;
} else {
return (
<PerfectScrollbar options={{ wheelPropagation: false, suppressScrollX: true }}>{children}</PerfectScrollbar>
);
}
};
return (
<Fragment>
<IconButton color="inherit" aria-haspopup="true" onClick={handleDropdownOpen} aria-controls="customized-menu">
<BellOutline />
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleDropdownClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<MenuItem disableRipple>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
<Typography sx={{ fontWeight: 600 }}>Notifications</Typography>
<Chip
size="small"
label="8 New"
color="primary"
sx={{ height: 20, fontSize: '0.75rem', fontWeight: 500, borderRadius: '10px' }}
/>
</Box>
</MenuItem>
<ScrollWrapper>
<MenuItem onClick={handleDropdownClose}>
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center' }}>
<Avatar alt="Flora" src="/images/avatars/4.png" />
<Box sx={{ mx: 4, flex: '1 1', display: 'flex', overflow: 'hidden', flexDirection: 'column' }}>
<MenuItemTitle>Congratulation Flora! 🎉</MenuItemTitle>
<MenuItemSubtitle variant="body2">Won the monthly best seller badge</MenuItemSubtitle>
</Box>
<Typography variant="caption" sx={{ color: 'text.disabled' }}>
Today
</Typography>
</Box>
</MenuItem>
<MenuItem onClick={handleDropdownClose}>
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center' }}>
<Avatar sx={{ color: 'common.white', backgroundColor: 'primary.main' }}>VU</Avatar>
<Box sx={{ mx: 4, flex: '1 1', display: 'flex', overflow: 'hidden', flexDirection: 'column' }}>
<MenuItemTitle>New user registered.</MenuItemTitle>
<MenuItemSubtitle variant="body2">5 hours ago</MenuItemSubtitle>
</Box>
<Typography variant="caption" sx={{ color: 'text.disabled' }}>
Yesterday
</Typography>
</Box>
</MenuItem>
<MenuItem onClick={handleDropdownClose}>
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center' }}>
<Avatar alt="message" src="/images/avatars/5.png" />
<Box sx={{ mx: 4, flex: '1 1', display: 'flex', overflow: 'hidden', flexDirection: 'column' }}>
<MenuItemTitle>New message received 👋🏻</MenuItemTitle>
<MenuItemSubtitle variant="body2">You have 10 unread messages</MenuItemSubtitle>
</Box>
<Typography variant="caption" sx={{ color: 'text.disabled' }}>
11 Aug
</Typography>
</Box>
</MenuItem>
<MenuItem onClick={handleDropdownClose}>
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center' }}>
<img width={38} height={38} alt="paypal" src="/images/misc/paypal.png" />
<Box sx={{ mx: 4, flex: '1 1', display: 'flex', overflow: 'hidden', flexDirection: 'column' }}>
<MenuItemTitle>Paypal</MenuItemTitle>
<MenuItemSubtitle variant="body2">Received Payment</MenuItemSubtitle>
</Box>
<Typography variant="caption" sx={{ color: 'text.disabled' }}>
25 May
</Typography>
</Box>
</MenuItem>
<MenuItem onClick={handleDropdownClose}>
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center' }}>
<Avatar alt="order" src="/images/avatars/3.png" />
<Box sx={{ mx: 4, flex: '1 1', display: 'flex', overflow: 'hidden', flexDirection: 'column' }}>
<MenuItemTitle>Revised Order 📦</MenuItemTitle>
<MenuItemSubtitle variant="body2">New order revised from john</MenuItemSubtitle>
</Box>
<Typography variant="caption" sx={{ color: 'text.disabled' }}>
19 Mar
</Typography>
</Box>
</MenuItem>
<MenuItem onClick={handleDropdownClose}>
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center' }}>
<img width={38} height={38} alt="chart" src="/images/misc/chart.png" />
<Box sx={{ mx: 4, flex: '1 1', display: 'flex', overflow: 'hidden', flexDirection: 'column' }}>
<MenuItemTitle>Finance report has been generated</MenuItemTitle>
<MenuItemSubtitle variant="body2">25 hrs ago</MenuItemSubtitle>
</Box>
<Typography variant="caption" sx={{ color: 'text.disabled' }}>
27 Dec
</Typography>
</Box>
</MenuItem>
</ScrollWrapper>
<MenuItem
disableRipple
sx={{ py: 3.5, borderBottom: 0, borderTop: theme => `1px solid ${theme.palette.divider}` }}
>
<Button fullWidth variant="contained" onClick={handleDropdownClose}>
Read All Notifications
</Button>
</MenuItem>
</Menu>
</Fragment>
);
};
export default NotificationDropdown;

View File

@@ -0,0 +1,143 @@
// ** React Imports
import { useState, Fragment } from 'react';
// ** Next Import
import { useRouter } from 'next/router';
// ** MUI Imports
import Box from '@mui/material/Box';
import Menu from '@mui/material/Menu';
import Badge from '@mui/material/Badge';
import Avatar from '@mui/material/Avatar';
import Divider from '@mui/material/Divider';
import MenuItem from '@mui/material/MenuItem';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
// ** Icons Imports
import CogOutline from 'mdi-material-ui/CogOutline';
import CurrencyUsd from 'mdi-material-ui/CurrencyUsd';
import EmailOutline from 'mdi-material-ui/EmailOutline';
import LogoutVariant from 'mdi-material-ui/LogoutVariant';
import AccountOutline from 'mdi-material-ui/AccountOutline';
import MessageOutline from 'mdi-material-ui/MessageOutline';
import HelpCircleOutline from 'mdi-material-ui/HelpCircleOutline';
// ** Styled Components
const BadgeContentSpan = styled('span')(({ theme }) => ({
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: theme.palette.success.main,
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
}));
const UserDropdown = () => {
// ** States
const [anchorEl, setAnchorEl] = useState(null);
// ** Hooks
const router = useRouter();
const handleDropdownOpen = event => {
setAnchorEl(event.currentTarget);
};
const handleDropdownClose = url => {
if (url) {
router.push(url);
}
setAnchorEl(null);
};
const styles = {
py: 2,
px: 4,
width: '100%',
display: 'flex',
alignItems: 'center',
color: 'text.primary',
textDecoration: 'none',
'& svg': {
fontSize: '1.375rem',
color: 'text.secondary',
},
};
return (
<Fragment>
<Badge>Hello user,</Badge>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => handleDropdownClose()}
sx={{ '& .MuiMenu-paper': { width: 230, marginTop: 4 } }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Box sx={{ pt: 2, pb: 3, px: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Badge
overlap="circular"
badgeContent={<BadgeContentSpan />}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<Avatar alt="John Doe" src="/images/avatars/1.png" sx={{ width: '2.5rem', height: '2.5rem' }} />
</Badge>
<Box sx={{ display: 'flex', marginLeft: 3, alignItems: 'flex-start', flexDirection: 'column' }}>
<Typography sx={{ fontWeight: 600 }}>John Doe</Typography>
<Typography variant="body2" sx={{ fontSize: '0.8rem', color: 'text.disabled' }}>
Admin
</Typography>
</Box>
</Box>
</Box>
<Divider sx={{ mt: 0, mb: 1 }} />
<MenuItem sx={{ p: 0 }} onClick={() => handleDropdownClose()}>
<Box sx={styles}>
<AccountOutline sx={{ marginRight: 2 }} />
Profile
</Box>
</MenuItem>
<MenuItem sx={{ p: 0 }} onClick={() => handleDropdownClose()}>
<Box sx={styles}>
<EmailOutline sx={{ marginRight: 2 }} />
Inbox
</Box>
</MenuItem>
<MenuItem sx={{ p: 0 }} onClick={() => handleDropdownClose()}>
<Box sx={styles}>
<MessageOutline sx={{ marginRight: 2 }} />
Chat
</Box>
</MenuItem>
<Divider />
<MenuItem sx={{ p: 0 }} onClick={() => handleDropdownClose()}>
<Box sx={styles}>
<CogOutline sx={{ marginRight: 2 }} />
Settings
</Box>
</MenuItem>
<MenuItem sx={{ p: 0 }} onClick={() => handleDropdownClose()}>
<Box sx={styles}>
<CurrencyUsd sx={{ marginRight: 2 }} />
Pricing
</Box>
</MenuItem>
<MenuItem sx={{ p: 0 }} onClick={() => handleDropdownClose()}>
<Box sx={styles}>
<HelpCircleOutline sx={{ marginRight: 2 }} />
FAQ
</Box>
</MenuItem>
<Divider />
<MenuItem sx={{ py: 2 }} onClick={() => handleDropdownClose('/pages/login')}>
<LogoutVariant sx={{ marginRight: 2, fontSize: '1.375rem', color: 'text.secondary' }} />
Logout
</MenuItem>
</Menu>
</Fragment>
);
};
export default UserDropdown;

View File

@@ -0,0 +1,53 @@
// ** MUI Imports
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import useMediaQuery from '@mui/material/useMediaQuery';
const FooterContent = () => {
// ** Var
const hidden = useMediaQuery(theme => theme.breakpoints.down('md'));
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography sx={{ mr: 2 }}>
{`© ${new Date().getFullYear()}, Made with `}
<Box component="span" sx={{ color: 'error.main' }}>
</Box>
{` by `}
<Link target="_blank" href="https://themeselection.com/">
ThemeSelection
</Link>{' '}
and for project
</Typography>
{hidden ? null : (
<Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', '& :not(:last-child)': { mr: 4 } }}>
<Link
target="_blank"
href="https://github.com/themeselection/materio-mui-react-nextjs-admin-template-free/blob/main/LICENSE"
>
MIT License
</Link>
<Link target="_blank" href="https://themeselection.com/">
More Themes
</Link>
<Link
target="_blank"
href="https://github.com/themeselection/materio-mui-react-nextjs-admin-template-free/blob/main/README.md"
>
Documentation
</Link>
<Link
target="_blank"
href="https://github.com/themeselection/materio-mui-react-nextjs-admin-template-free/issues"
>
Support
</Link>
</Box>
)}
</Box>
);
};
export default FooterContent;

View File

@@ -0,0 +1,45 @@
// ** MUI Imports
import Box from '@mui/material/Box';
import { useTheme } from '@mui/material/styles';
// ** Footer Content Component
import FooterContent from './FooterContent';
const Footer = props => {
// ** Props
const { settings, footerContent: userFooterContent } = props;
// ** Hook
const theme = useTheme();
// ** Vars
const { contentWidth } = settings;
return (
<Box
component="footer"
className="layout-footer"
sx={{
zIndex: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Box
className="footer-content-container"
sx={{
width: '100%',
borderTopLeftRadius: 14,
borderTopRightRadius: 14,
padding: theme.spacing(4, 6),
...(contentWidth === 'boxed' && { '@media (min-width:1440px)': { maxWidth: 1440 } }),
}}
>
{userFooterContent ? userFooterContent(props) : <FooterContent />}
</Box>
</Box>
);
};
export default Footer;

View File

@@ -0,0 +1,56 @@
// ** MUI Imports
import { styled, useTheme } from '@mui/material/styles';
import MuiAppBar from '@mui/material/AppBar';
import MuiToolbar from '@mui/material/Toolbar';
const AppBar = styled(MuiAppBar)(({ theme }) => ({
transition: 'none',
alignItems: 'center',
justifyContent: 'center',
padding: theme.spacing(0, 6),
backgroundColor: 'transparent',
color: theme.palette.text.primary,
minHeight: theme.mixins.toolbar.minHeight,
[theme.breakpoints.down('sm')]: {
paddingLeft: theme.spacing(4),
paddingRight: theme.spacing(4),
},
}));
const Toolbar = styled(MuiToolbar)(({ theme }) => ({
width: '100%',
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
padding: `${theme.spacing(0)} !important`,
minHeight: `${theme.mixins.toolbar.minHeight}px !important`,
transition:
'padding .25s ease-in-out, box-shadow .25s ease-in-out, backdrop-filter .25s ease-in-out, background-color .25s ease-in-out',
}));
const LayoutAppBar = props => {
// ** Props
const { settings, verticalAppBarContent: userVerticalAppBarContent } = props;
// ** Hooks
const theme = useTheme();
// ** Vars
const { contentWidth } = settings;
return (
<AppBar elevation={0} color="default" className="layout-navbar" position="static">
<Toolbar
className="navbar-content-container"
sx={{
...(contentWidth === 'boxed' && {
'@media (min-width:1440px)': { maxWidth: `calc(1440px - ${theme.spacing(6)} * 2)` },
}),
}}
>
{(userVerticalAppBarContent && userVerticalAppBarContent(props)) || null}
</Toolbar>
</AppBar>
);
};
export default LayoutAppBar;

View File

@@ -0,0 +1,66 @@
// ** MUI Imports
import { styled, useTheme } from '@mui/material/styles';
import MuiSwipeableDrawer from '@mui/material/SwipeableDrawer';
const SwipeableDrawer = styled(MuiSwipeableDrawer)({
overflowX: 'hidden',
transition: 'width .25s ease-in-out',
'& ul': {
listStyle: 'none',
},
'& .MuiListItem-gutters': {
paddingLeft: 4,
paddingRight: 4,
},
'& .MuiDrawer-paper': {
left: 'unset',
right: 'unset',
overflowX: 'hidden',
transition: 'width .25s ease-in-out, box-shadow .25s ease-in-out',
},
});
const Drawer = props => {
// ** Props
const { hidden, children, navWidth, navVisible, setNavVisible } = props;
// ** Hook
const theme = useTheme();
// Drawer Props for Mobile & Tablet screens
const MobileDrawerProps = {
open: navVisible,
onOpen: () => setNavVisible(true),
onClose: () => setNavVisible(false),
ModalProps: {
keepMounted: true, // Better open performance on mobile.
},
};
// Drawer Props for Desktop screens
const DesktopDrawerProps = {
open: true,
onOpen: () => null,
onClose: () => null,
};
return (
<SwipeableDrawer
className="layout-vertical-nav"
variant={hidden ? 'temporary' : 'permanent'}
{...(hidden ? { ...MobileDrawerProps } : { ...DesktopDrawerProps })}
PaperProps={{ sx: { width: navWidth } }}
sx={{
width: navWidth,
'& .MuiDrawer-paper': {
borderRight: 0,
backgroundColor: theme.palette.background.default,
},
}}
>
{children}
</SwipeableDrawer>
);
};
export default Drawer;

View File

@@ -0,0 +1,119 @@
// ** Next Import
import Link from 'next/link';
// ** MUI Imports
import Box from '@mui/material/Box';
import { styled, useTheme } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
// ** Configs
import themeConfig from 'src/configs/themeConfig';
// ** Styled Components
const MenuHeaderWrapper = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
paddingRight: theme.spacing(4.5),
transition: 'padding .25s ease-in-out',
minHeight: theme.mixins.toolbar.minHeight,
}));
const HeaderTitle = styled(Typography)(({ theme }) => ({
fontWeight: 600,
lineHeight: 'normal',
textTransform: 'uppercase',
color: theme.palette.text.primary,
transition: 'opacity .25s ease-in-out, margin .25s ease-in-out',
}));
const StyledLink = styled('a')({
display: 'flex',
alignItems: 'center',
textDecoration: 'none',
});
const VerticalNavHeader = props => {
// ** Props
const { verticalNavMenuBranding: userVerticalNavMenuBranding } = props;
// ** Hooks
const theme = useTheme();
return (
<MenuHeaderWrapper className="nav-header" sx={{ pl: 6 }}>
{userVerticalNavMenuBranding ? (
userVerticalNavMenuBranding(props)
) : (
<Link href="/" passHref>
<StyledLink>
<svg
width={30}
height={25}
version="1.1"
viewBox="0 0 30 23"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Artboard" transform="translate(-95.000000, -51.000000)">
<g id="logo" transform="translate(95.000000, 50.000000)">
<path
id="Combined-Shape"
fill={theme.palette.primary.main}
d="M30,21.3918362 C30,21.7535219 29.9019196,22.1084381 29.7162004,22.4188007 C29.1490236,23.366632 27.9208668,23.6752135 26.9730355,23.1080366 L26.9730355,23.1080366 L23.714971,21.1584295 C23.1114106,20.7972624 22.7419355,20.1455972 22.7419355,19.4422291 L22.7419355,19.4422291 L22.741,12.7425689 L15,17.1774194 L7.258,12.7425689 L7.25806452,19.4422291 C7.25806452,20.1455972 6.88858935,20.7972624 6.28502902,21.1584295 L3.0269645,23.1080366 C2.07913318,23.6752135 0.850976404,23.366632 0.283799571,22.4188007 C0.0980803893,22.1084381 2.0190442e-15,21.7535219 0,21.3918362 L0,3.58469444 L0.00548573643,3.43543209 L0.00548573643,3.43543209 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 L15,9.19354839 L26.9548759,1.86636639 C27.2693965,1.67359571 27.6311047,1.5715689 28,1.5715689 C29.1045695,1.5715689 30,2.4669994 30,3.5715689 L30,3.5715689 Z"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.7505183 7.25806452 16.8305646"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.6445567 7.25806452 15.1370162"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.7417372 30 16.9537453"
transform="translate(26.370968, 12.771227) scale(-1, 1) translate(-26.370968, -12.771227) "
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.6409734 30 15.2601969"
transform="translate(26.370968, 11.924453) scale(-1, 1) translate(-26.370968, -11.924453) "
/>
<path
id="Rectangle"
fillOpacity="0.15"
fill={theme.palette.common.white}
d="M3.04512412,1.86636639 L15,9.19354839 L15,9.19354839 L15,17.1774194 L0,8.58649679 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 Z"
/>
<path
id="Rectangle"
fillOpacity="0.35"
fill={theme.palette.common.white}
transform="translate(22.500000, 8.588710) scale(-1, 1) translate(-22.500000, -8.588710) "
d="M18.0451241,1.86636639 L30,9.19354839 L30,9.19354839 L30,17.1774194 L15,8.58649679 L15,3.5715689 C15,2.4669994 15.8954305,1.5715689 17,1.5715689 C17.3688953,1.5715689 17.7306035,1.67359571 18.0451241,1.86636639 Z"
/>
</g>
</g>
</g>
</svg>
<HeaderTitle variant="h6" sx={{ ml: 3 }}>
{themeConfig.templateName}
</HeaderTitle>
</StyledLink>
</Link>
)}
</MenuHeaderWrapper>
);
};
export default VerticalNavHeader;

View File

@@ -0,0 +1,24 @@
// ** Custom Menu Components
import VerticalNavLink from './VerticalNavLink';
import VerticalNavSectionTitle from './VerticalNavSectionTitle';
const resolveNavItemComponent = item => {
if (item.sectionTitle) return VerticalNavSectionTitle;
return VerticalNavLink;
};
const VerticalNavItems = props => {
// ** Props
const { verticalNavItems } = props;
const RenderMenuItems = verticalNavItems?.map((item, index) => {
const TagName = resolveNavItemComponent(item);
return <TagName {...props} key={index} item={item} />;
});
return <>{RenderMenuItems}</>;
};
export default VerticalNavItems;

View File

@@ -0,0 +1,119 @@
// ** Next Imports
import Link from 'next/link';
import { useRouter } from 'next/router';
// ** MUI Imports
import Chip from '@mui/material/Chip';
import ListItem from '@mui/material/ListItem';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemButton from '@mui/material/ListItemButton';
// ** Configs Import
import themeConfig from 'src/configs/themeConfig';
// ** Custom Components Imports
import UserIcon from 'src/layouts/components/UserIcon';
// ** Utils
import { handleURLQueries } from 'src/@core/layouts/utils';
// ** Styled Components
const MenuNavLink = styled(ListItemButton)(({ theme }) => ({
width: '100%',
borderTopRightRadius: 100,
borderBottomRightRadius: 100,
color: theme.palette.text.primary,
padding: theme.spacing(2.25, 3.5),
transition: 'opacity .25s ease-in-out',
'&.active, &.active:hover': {
boxShadow: theme.shadows[3],
backgroundImage: `linear-gradient(98deg, ${theme.palette.customColors.primaryGradient}, ${theme.palette.primary.main} 94%)`,
},
'&.active .MuiTypography-root, &.active .MuiSvgIcon-root': {
color: `${theme.palette.common.white} !important`,
},
}));
const MenuItemTextMetaWrapper = styled(Box)({
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
transition: 'opacity .25s ease-in-out',
...(themeConfig.menuTextTruncate && { overflow: 'hidden' }),
});
const VerticalNavLink = ({ item, navVisible, toggleNavVisibility }) => {
// ** Hooks
const router = useRouter();
const IconTag = item.icon;
const isNavLinkActive = () => {
if (router.pathname === item.path || handleURLQueries(router, item.path)) {
return true;
} else {
return false;
}
};
return (
<ListItem
disablePadding
className="nav-link"
disabled={item.disabled || false}
sx={{ mt: 1.5, px: '0 !important' }}
>
<Link passHref href={item.path === undefined ? '/' : `${item.path}`}>
<MenuNavLink
component={'a'}
className={isNavLinkActive() ? 'active' : ''}
{...(item.openInNewTab ? { target: '_blank' } : null)}
onClick={e => {
if (item.path === undefined) {
e.preventDefault();
e.stopPropagation();
}
if (navVisible) {
toggleNavVisibility();
}
}}
sx={{
pl: 5.5,
...(item.disabled ? { pointerEvents: 'none' } : { cursor: 'pointer' }),
}}
>
<ListItemIcon
sx={{
mr: 2.5,
color: 'text.primary',
transition: 'margin .25s ease-in-out',
}}
>
<UserIcon icon={IconTag} />
</ListItemIcon>
<MenuItemTextMetaWrapper>
<Typography {...(themeConfig.menuTextTruncate && { noWrap: true })}>{item.title}</Typography>
{item.badgeContent ? (
<Chip
label={item.badgeContent}
color={item.badgeColor || 'primary'}
sx={{
height: 20,
fontWeight: 500,
marginLeft: 1.25,
'& .MuiChip-label': { px: 1.5, textTransform: 'capitalize' },
}}
/>
) : null}
</MenuItemTextMetaWrapper>
</MenuNavLink>
</Link>
</ListItem>
);
};
export default VerticalNavLink;

View File

@@ -0,0 +1,63 @@
// ** MUI Imports
import Divider from '@mui/material/Divider';
import { styled, useTheme } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import MuiListSubheader from '@mui/material/ListSubheader';
// ** Styled Components
const ListSubheader = styled(props => <MuiListSubheader component="li" {...props} />)(({ theme }) => ({
lineHeight: 1,
display: 'flex',
position: 'relative',
marginTop: theme.spacing(7),
marginBottom: theme.spacing(2),
backgroundColor: 'transparent',
transition: 'padding-left .25s ease-in-out',
}));
const TypographyHeaderText = styled(Typography)(({ theme }) => ({
fontSize: '0.75rem',
lineHeight: 'normal',
letterSpacing: '0.21px',
textTransform: 'uppercase',
color: theme.palette.text.disabled,
fontWeight: theme.typography.fontWeightMedium,
}));
const VerticalNavSectionTitle = props => {
// ** Props
const { item } = props;
// ** Hook
const theme = useTheme();
return (
<ListSubheader
className="nav-section-title"
sx={{
px: 0,
py: 1.75,
color: theme.palette.text.disabled,
'& .MuiDivider-root:before, & .MuiDivider-root:after, & hr': {
borderColor: `rgba(${theme.palette.customColors.main}, 0.12)`,
},
}}
>
<Divider
textAlign="left"
sx={{
m: 0,
width: '100%',
lineHeight: 'normal',
textTransform: 'uppercase',
'&:before, &:after': { top: 7, transform: 'none' },
'& .MuiDivider-wrapper': { px: 2.5, fontSize: '0.75rem', letterSpacing: '0.21px' },
}}
>
<TypographyHeaderText noWrap>{item.sectionTitle}</TypographyHeaderText>
</Divider>
</ListSubheader>
);
};
export default VerticalNavSectionTitle;

View File

@@ -0,0 +1,131 @@
// ** React Import
import { useRef, useState } from 'react';
// ** MUI Import
import List from '@mui/material/List';
import Box from '@mui/material/Box';
import { styled, useTheme } from '@mui/material/styles';
// ** Third Party Components
import PerfectScrollbar from 'react-perfect-scrollbar';
// ** Component Imports
import Drawer from './Drawer';
import VerticalNavItems from './VerticalNavItems';
import VerticalNavHeader from './VerticalNavHeader';
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const StyledBoxForShadow = styled(Box)({
top: 50,
left: -8,
zIndex: 2,
height: 75,
display: 'none',
position: 'absolute',
pointerEvents: 'none',
width: 'calc(100% + 15px)',
'&.d-block': {
display: 'block',
},
});
const Navigation = props => {
// ** Props
const {
hidden,
afterVerticalNavMenuContent,
beforeVerticalNavMenuContent,
verticalNavMenuContent: userVerticalNavMenuContent,
} = props;
// ** States
const [groupActive, setGroupActive] = useState([]);
const [currentActiveGroup, setCurrentActiveGroup] = useState([]);
// ** Ref
const shadowRef = useRef(null);
// ** Hooks
const theme = useTheme();
// ** Fixes Navigation InfiniteScroll
const handleInfiniteScroll = ref => {
if (ref) {
// @ts-ignore
ref._getBoundingClientRect = ref.getBoundingClientRect;
ref.getBoundingClientRect = () => {
// @ts-ignore
const original = ref._getBoundingClientRect();
return { ...original, height: Math.floor(original.height) };
};
}
};
// ** Scroll Menu
const scrollMenu = container => {
container = hidden ? container.target : container;
if (shadowRef && container.scrollTop > 0) {
// @ts-ignore
if (!shadowRef.current.classList.contains('d-block')) {
// @ts-ignore
shadowRef.current.classList.add('d-block');
}
} else {
// @ts-ignore
shadowRef.current.classList.remove('d-block');
}
};
const ScrollWrapper = hidden ? Box : PerfectScrollbar;
return (
<Drawer {...props}>
<VerticalNavHeader {...props} />
<StyledBoxForShadow
ref={shadowRef}
sx={{
background: `linear-gradient(${theme.palette.background.default} 40%,${hexToRGBA(
theme.palette.background.default,
0.1,
)} 95%,${hexToRGBA(theme.palette.background.default, 0.05)})`,
}}
/>
<Box sx={{ height: '100%', position: 'relative', overflow: 'hidden' }}>
<ScrollWrapper
containerRef={ref => handleInfiniteScroll(ref)}
{...(hidden
? {
onScroll: container => scrollMenu(container),
sx: { height: '100%', overflowY: 'auto', overflowX: 'hidden' },
}
: {
options: { wheelPropagation: false },
onScrollY: container => scrollMenu(container),
})}
>
{beforeVerticalNavMenuContent ? beforeVerticalNavMenuContent(props) : null}
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
{userVerticalNavMenuContent ? (
userVerticalNavMenuContent(props)
) : (
<List className="nav-items" sx={{ transition: 'padding .25s ease', pr: 4.5 }}>
<VerticalNavItems
groupActive={groupActive}
setGroupActive={setGroupActive}
currentActiveGroup={currentActiveGroup}
setCurrentActiveGroup={setCurrentActiveGroup}
{...props}
/>
</List>
)}
</Box>
</ScrollWrapper>
</Box>
{afterVerticalNavMenuContent ? afterVerticalNavMenuContent(props) : null}
</Drawer>
);
};
export default Navigation;

View File

@@ -0,0 +1,16 @@
/**
* Check for URL queries as well for matching
* Current URL & Item Path
*
* @param item
* @param activeItem
*/
export const handleURLQueries = (router, path) => {
if (Object.keys(router.query).length && path) {
const arr = Object.keys(router.query);
return router.asPath.includes(path) && router.asPath.includes(router.query[arr[0]]) && path !== '/';
}
return false;
};

View File

@@ -0,0 +1,103 @@
// ** MUI imports
import { styled } from '@mui/material/styles';
const ApexChartWrapper = styled('div')(({ theme }) => ({
'& .apexcharts-canvas': {
"& line[stroke='transparent']": {
display: 'none',
},
'& .apexcharts-xaxis > line, & .apexcharts-yaxis > line': {
stroke: theme.palette.divider,
},
'& .apexcharts-xaxis-tick, & .apexcharts-yaxis-tick': {
stroke: theme.palette.divider,
},
'& .apexcharts-tooltip': {
boxShadow: theme.shadows[3],
borderColor: theme.palette.divider,
background: theme.palette.background.paper,
'& .apexcharts-tooltip-title': {
fontWeight: 600,
borderColor: theme.palette.divider,
background: theme.palette.background.paper,
},
'&.apexcharts-theme-dark': {
'& .apexcharts-tooltip-text-label, & .apexcharts-tooltip-text-value': {
color: theme.palette.common.white,
},
},
'& .bar-chart': {
padding: theme.spacing(2, 2.5),
},
},
'& .apexcharts-xaxistooltip': {
borderColor: theme.palette.divider,
background: theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.background.default,
'& .apexcharts-xaxistooltip-text': {
color: theme.palette.text.primary,
},
'&:after': {
borderBottomColor: theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.background.default,
},
'&:before': {
borderBottomColor: theme.palette.divider,
},
},
'& .apexcharts-yaxistooltip': {
borderColor: theme.palette.divider,
background: theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.background.default,
'& .apexcharts-yaxistooltip-text': {
color: theme.palette.text.primary,
},
'&:after': {
borderLeftColor: theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.background.default,
},
'&:before': {
borderLeftColor: theme.palette.divider,
},
},
'& .apexcharts-text, & .apexcharts-tooltip-text, & .apexcharts-datalabel-label, & .apexcharts-datalabel': {
filter: 'none',
fontWeight: 400,
fill: theme.palette.text.primary,
fontFamily: `${theme.typography.fontFamily} !important`,
},
'& .apexcharts-pie-label': {
filter: 'none',
fill: theme.palette.common.white,
},
'& .apexcharts-pie': {
'& .apexcharts-datalabel-label, .apexcharts-datalabel-value': {
fontSize: '1.5rem',
},
},
'& .apexcharts-marker': {
boxShadow: 'none',
},
'& .apexcharts-legend-series': {
margin: `${theme.spacing(0.75, 2)} !important`,
'& .apexcharts-legend-text': {
marginLeft: theme.spacing(0.75),
color: `${theme.palette.text.primary} !important`,
},
},
'& .apexcharts-xcrosshairs, & .apexcharts-ycrosshairs, & .apexcharts-gridline': {
stroke: theme.palette.divider,
},
'& .apexcharts-heatmap-rect': {
stroke: theme.palette.mode === 'light' ? theme.palette.background.paper : theme.palette.background.default,
},
'& .apexcharts-radialbar > g > g:first-of-type .apexcharts-radialbar-area': {
stroke: theme.palette.background.default,
},
'& .apexcharts-radar-series polygon': {
stroke: theme.palette.divider,
fill: theme.palette.background.paper,
},
'& .apexcharts-radar-series line': {
stroke: theme.palette.divider,
},
},
}));
export default ApexChartWrapper;

View File

@@ -0,0 +1,358 @@
// ** MUI imports
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const DatePickerWrapper = styled(Box)(({ theme }) => {
return {
'& .react-datepicker-popper': {
zIndex: 5,
},
'& .react-datepicker-wrapper': {
width: '100%',
},
'& .react-datepicker': {
border: 'none',
boxShadow: theme.shadows[7],
padding: theme.spacing(2, 0),
color: theme.palette.text.primary,
borderRadius: theme.shape.borderRadius,
fontFamily: theme.typography.fontFamily,
backgroundColor: theme.palette.background.paper,
'& .react-datepicker__header': {
padding: 0,
border: 'none',
fontWeight: 'normal',
backgroundColor: theme.palette.background.paper,
'& .react-datepicker__day-name': {
margin: 0,
},
},
'& .react-datepicker-year-header': {
lineHeight: 2.1,
marginBottom: '0.5rem',
color: theme.palette.text.primary,
},
'& .react-datepicker__triangle': {
display: 'none',
},
'& > .react-datepicker__navigation': {
top: theme.spacing(3),
'&.react-datepicker__navigation--previous': {
border: 'none',
backgroundImage: `${"url('data:image/svg+xml,%3Csvg xmlns=\\'http://www.w3.org/2000/svg\\' style=\\'width:24px;height:24px\\' viewBox=\\'0 0 24 24\\'%3E%3Cpath fill=\\'currentColor\\' d=\\'M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z\\' /%3E%3C/svg%3E')"
.replace('currentColor', theme.palette.text.secondary)
.replace('#', '%23')}`,
height: '24px',
width: '24px',
'& .react-datepicker__navigation-icon': {
display: 'none',
},
},
'&.react-datepicker__navigation--next': {
border: 'none',
backgroundImage: `${"url('data:image/svg+xml,%3Csvg xmlns=\\'http://www.w3.org/2000/svg\\' style=\\'width:24px;height:24px\\' viewBox=\\'0 0 24 24\\'%3E%3Cpath fill=\\'currentColor\\' d=\\'M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z\\' /%3E%3C/svg%3E')"
.replace('currentColor', theme.palette.text.secondary)
.replace('#', '%23')}`,
height: '24px',
width: '24px',
'& .react-datepicker__navigation-icon': {
display: 'none',
},
},
'&.react-datepicker__navigation--next--with-time': {
right: '122px',
},
'&:focus, &:active': {
outline: 0,
},
},
'& .react-datepicker__current-month': {
lineHeight: 2.1,
fontSize: '1rem',
fontWeight: 'normal',
letterSpacing: '0.15px',
marginBottom: theme.spacing(2),
color: theme.palette.text.primary,
},
'& .react-datepicker__day-name': {
lineHeight: 1.5,
width: '2.25rem',
fontSize: '0.75rem',
letterSpacing: '0.4px',
color: theme.palette.text.secondary,
},
'& .react-datepicker__day': {
margin: 0,
width: '2.25rem',
lineHeight: 2.75,
height: '2.25rem',
borderRadius: '50%',
color: theme.palette.text.primary,
'&.react-datepicker__day--selected, &.react-datepicker__day--keyboard-selected': {
color: theme.palette.common.white,
backgroundColor: `${theme.palette.primary.main} !important`,
},
'&.react-datepicker__day--in-range, &.react-datepicker__day--in-selecting-range': {
borderRadius: 0,
color: theme.palette.primary.main,
backgroundColor: `${hexToRGBA(theme.palette.primary.main, 0.06)} !important`,
'&:empty': {
backgroundColor: 'transparent !important',
},
},
'&.react-datepicker__day--selected.react-datepicker__day--in-selecting-range.react-datepicker__day--selecting-range-start, &.react-datepicker__day--selected.react-datepicker__day--range-start.react-datepicker__day--in-range, &.react-datepicker__day--range-start':
{
borderTopLeftRadius: '50%',
borderBottomLeftRadius: '50%',
color: theme.palette.common.white,
backgroundColor: `${theme.palette.primary.main} !important`,
},
'&.react-datepicker__day--range-end': {
borderTopRightRadius: '50%',
borderBottomRightRadius: '50%',
color: theme.palette.common.white,
backgroundColor: `${theme.palette.primary.main} !important`,
},
'&:focus, &:active': {
outline: 0,
},
'&.react-datepicker__day--outside-month': {
height: 'auto',
},
'&.react-datepicker__day--outside-month, &.react-datepicker__day--disabled:not(.react-datepicker__day--selected)':
{
color: theme.palette.text.disabled,
'&:hover': {
backgroundColor: 'transparent',
},
},
'&.react-datepicker__day--highlighted, &.react-datepicker__day--highlighted:hover': {
color: theme.palette.success.main,
backgroundColor: hexToRGBA(theme.palette.success.main, 0.12),
},
'&.react-datepicker__day--today': {
fontWeight: 'normal',
},
},
'& .react-datepicker__header__dropdown': {
'& .react-datepicker__month-dropdown-container:not(:last-child)': {
marginRight: theme.spacing(8),
},
'& .react-datepicker__month-dropdown-container, & .react-datepicker__year-dropdown-container': {
marginBottom: theme.spacing(4),
},
'& .react-datepicker__month-read-view--selected-month, & .react-datepicker__year-read-view--selected-year': {
fontSize: '0.875rem',
marginRight: theme.spacing(1),
color: theme.palette.text.primary,
},
'& .react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow, & .react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow':
{
borderTopColor: theme.palette.text.secondary,
borderRightColor: theme.palette.text.secondary,
},
'& .react-datepicker__month-read-view--down-arrow, & .react-datepicker__year-read-view--down-arrow': {
top: 4,
borderTopColor: theme.palette.text.disabled,
borderRightColor: theme.palette.text.disabled,
},
'& .react-datepicker__month-dropdown, & .react-datepicker__year-dropdown': {
paddingTop: theme.spacing(1.5),
paddingBottom: theme.spacing(1.5),
borderColor: theme.palette.divider,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.paper,
boxShadow: theme.palette.mode === 'light' ? theme.shadows[8] : theme.shadows[9],
},
'& .react-datepicker__month-option, & .react-datepicker__year-option': {
paddingTop: theme.spacing(0.5),
paddingBottom: theme.spacing(0.5),
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
},
'& .react-datepicker__month-option.react-datepicker__month-option--selected_month': {
backgroundColor: hexToRGBA(theme.palette.primary.main, 0.08),
'&:hover': {
backgroundColor: hexToRGBA(theme.palette.primary.main, 0.12),
},
'& .react-datepicker__month-option--selected': {
display: 'none',
},
},
'& .react-datepicker__year-option.react-datepicker__year-option--selected_year': {
backgroundColor: hexToRGBA(theme.palette.primary.main, 0.08),
'&:hover': {
backgroundColor: hexToRGBA(theme.palette.primary.main, 0.12),
},
'& .react-datepicker__year-option--selected': {
display: 'none',
},
},
'& .react-datepicker__year-option': {
// TODO: Remove some of the following styles for arrow in Year dropdown when react-datepicker give arrows in Year dropdown
'& .react-datepicker__navigation--years-upcoming': {
width: 9,
height: 9,
borderStyle: 'solid',
borderWidth: '3px 3px 0 0',
transform: 'rotate(-45deg)',
borderTopColor: theme.palette.text.disabled,
borderRightColor: theme.palette.text.disabled,
margin: `${theme.spacing(2.75)} auto ${theme.spacing(0)}`,
},
'&:hover .react-datepicker__navigation--years-upcoming': {
borderTopColor: theme.palette.text.secondary,
borderRightColor: theme.palette.text.secondary,
},
'& .react-datepicker__navigation--years-previous': {
width: 9,
height: 9,
borderStyle: 'solid',
borderWidth: '0 0 3px 3px',
transform: 'rotate(-45deg)',
borderLeftColor: theme.palette.text.disabled,
borderBottomColor: theme.palette.text.disabled,
margin: `${theme.spacing(0)} auto ${theme.spacing(2.75)}`,
},
'&:hover .react-datepicker__navigation--years-previous': {
borderLeftColor: theme.palette.text.secondary,
borderBottomColor: theme.palette.text.secondary,
},
},
},
'& .react-datepicker__month': {
marginTop: theme.spacing(3),
},
[theme.breakpoints.down('sm')]: {
'& .react-datepicker__month': {
marginLeft: 0,
marginRight: 0,
marginBottom: 0,
},
},
'& .react-datepicker__month, & .react-datepicker__year': {
'& .react-datepicker__month-text, & .react-datepicker__year-text, & .react-datepicker__quarter-text': {
height: '2rem',
alignItems: 'center',
display: 'inline-flex',
justifyContent: 'center',
'&:hover': {
borderRadius: theme.shape.borderRadius,
},
'&:focus, &:active': {
outline: 0,
},
},
'& .react-datepicker__quarter--selected, & .react-datepicker__year-text--selected, & .react-datepicker__month--selected, & .react-datepicker__quarter-text--keyboard-selected, & .react-datepicker__month-text--keyboard-selected, & .react-datepicker__year-text--keyboard-selected':
{
color: theme.palette.common.white,
borderRadius: theme.shape.borderRadius,
backgroundColor: `${theme.palette.primary.main} !important`,
},
'& .react-datepicker__week-number': {
fontWeight: 600,
color: theme.palette.text.primary,
},
},
'& .react-datepicker__year-wrapper': {
maxWidth: 205,
justifyContent: 'center',
},
'& .react-datepicker__input-time-container': {
display: 'flex',
alignItems: 'center',
},
'& .react-datepicker__today-button': {
borderRadius: '1rem',
margin: '0 1rem 0.3rem',
color: theme.palette.common.white,
backgroundColor: theme.palette.primary.main,
},
// ** Time Picker
'& .react-datepicker__time-container': {
borderLeftColor: theme.palette.divider,
},
'&.react-datepicker--time-only, & .react-datepicker__time-container': {
width: '7rem',
padding: theme.spacing(1.2, 0),
'& .react-datepicker-time__header': {
marginBottom: theme.spacing(3),
color: theme.palette.text.primary,
fontSize: theme.typography.body2.fontSize,
},
'& .react-datepicker__time': {
background: theme.palette.background.paper,
'& .react-datepicker__time-box .react-datepicker__time-list-item--disabled': {
color: theme.palette.text.disabled,
},
},
'& .react-datepicker__time-list-item': {
lineHeight: 1.75,
height: 'auto !important',
marginLeft: theme.spacing(3.2),
marginRight: theme.spacing(1.2),
color: theme.palette.text.primary,
borderRadius: theme.shape.borderRadius,
'&:focus, &:active': {
outline: 0,
},
'&:hover': {
backgroundColor: `${theme.palette.action.hover} !important`,
},
'&.react-datepicker__time-list-item--selected': {
color: `${theme.palette.common.white} !important`,
backgroundColor: `${theme.palette.primary.main} !important`,
},
},
'& .react-datepicker__time-box': {
width: '100%',
},
'& .react-datepicker__time-list': {
'&::-webkit-scrollbar': {
width: 8,
},
/* Track */
'&::-webkit-scrollbar-track': {
background: theme.palette.background.paper,
},
/* Handle */
'&::-webkit-scrollbar-thumb': {
background: '#aaa',
borderRadius: '10px',
},
/* Handle on hover */
'&::-webkit-scrollbar-thumb:hover': {
background: '#999',
},
},
},
'&.react-datepicker--time-only .react-datepicker__time-container': {
width: 'calc(7rem - 2px)',
},
'& .react-datepicker__day:hover, & .react-datepicker__month-text:hover, & .react-datepicker__quarter-text:hover, & .react-datepicker__year-text:hover':
{
backgroundColor: theme.palette.action.hover,
},
},
'& .react-datepicker__close-icon': {
paddingRight: theme.spacing(4),
'&:after': {
width: 'unset',
height: 'unset',
fontSize: '1.5rem',
color: theme.palette.text.primary,
backgroundColor: 'transparent !important',
},
},
};
});
export default DatePickerWrapper;

View File

@@ -0,0 +1,49 @@
// ** MUI Imports
import CssBaseline from '@mui/material/CssBaseline';
import GlobalStyles from '@mui/material/GlobalStyles';
import { ThemeProvider, createTheme, responsiveFontSizes } from '@mui/material/styles';
// ** Theme Config
import themeConfig from 'src/configs/themeConfig';
// ** Theme Override Imports
import overrides from './overrides';
import typography from './typography';
// ** Theme
import themeOptions from './ThemeOptions';
// ** Global Styles
import GlobalStyling from './globalStyles';
const ThemeComponent = props => {
// ** Props
const { settings, children } = props;
// ** Merged ThemeOptions of Core and User
const coreThemeConfig = themeOptions(settings);
// ** Pass ThemeOptions to CreateTheme Function to create partial theme without component overrides
let theme = createTheme(coreThemeConfig);
// ** Continue theme creation and pass merged component overrides to CreateTheme function
theme = createTheme(theme, {
components: { ...overrides(theme) },
typography: { ...typography(theme) },
});
// ** Set responsive font sizes to true
if (themeConfig.responsiveFontSizes) {
theme = responsiveFontSizes(theme);
}
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles styles={() => GlobalStyling(theme)} />
{children}
</ThemeProvider>
);
};
export default ThemeComponent;

View File

@@ -0,0 +1,54 @@
// ** MUI Theme Provider
import { deepmerge } from '@mui/utils';
// ** Theme Override Imports
import palette from './palette';
import spacing from './spacing';
import shadows from './shadows';
import breakpoints from './breakpoints';
const themeOptions = settings => {
// ** Vars
const { mode, themeColor } = settings;
const themeConfig = {
palette: palette(mode, themeColor),
typography: {
fontFamily: [
'Inter',
'sans-serif',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
},
shadows: shadows(mode),
...spacing,
breakpoints: breakpoints(),
shape: {
borderRadius: 6,
},
mixins: {
toolbar: {
minHeight: 64,
},
},
};
return deepmerge(themeConfig, {
palette: {
primary: {
...themeConfig.palette[themeColor],
},
},
});
};
export default themeOptions;

View File

@@ -0,0 +1,11 @@
const breakpoints = () => ({
values: {
xs: 0,
sm: 600,
md: 900,
lg: 1200,
xl: 1536,
},
});
export default breakpoints;

View File

@@ -0,0 +1,43 @@
const GlobalStyles = theme => {
return {
'.ps__rail-y': {
zIndex: 1,
right: '0 !important',
left: 'auto !important',
'&:hover, &:focus, &.ps--clicking': {
backgroundColor: theme.palette.mode === 'light' ? '#E4E5EB !important' : '#423D5D !important',
},
'& .ps__thumb-y': {
right: '3px !important',
left: 'auto !important',
backgroundColor: theme.palette.mode === 'light' ? '#C2C4D1 !important' : '#504B6D !important',
},
'.layout-vertical-nav &': {
'& .ps__thumb-y': {
width: 4,
backgroundColor: theme.palette.mode === 'light' ? '#C2C4D1 !important' : '#504B6D !important',
},
'&:hover, &:focus, &.ps--clicking': {
backgroundColor: 'transparent !important',
'& .ps__thumb-y': {
width: 6,
},
},
},
},
'#nprogress': {
pointerEvents: 'none',
'& .bar': {
left: 0,
top: 0,
height: 3,
width: '100%',
zIndex: 2000,
position: 'fixed',
backgroundColor: theme.palette.primary.main,
},
},
};
};
export default GlobalStyles;

View File

@@ -0,0 +1,46 @@
const Accordion = theme => {
return {
MuiAccordion: {
styleOverrides: {
root: {
'&.Mui-disabled': {
backgroundColor: `rgba(${theme.palette.customColors.main}, 0.12)`,
},
'&.Mui-expanded': {
boxShadow: theme.shadows[3],
},
},
},
},
MuiAccordionSummary: {
styleOverrides: {
root: {
padding: `0 ${theme.spacing(5)}`,
'& + .MuiCollapse-root': {
'& .MuiAccordionDetails-root:first-child': {
paddingTop: 0,
},
},
},
content: {
margin: `${theme.spacing(2.5)} 0`,
},
expandIconWrapper: {
color: theme.palette.text.secondary,
},
},
},
MuiAccordionDetails: {
styleOverrides: {
root: {
padding: theme.spacing(5),
'& + .MuiAccordionDetails-root': {
paddingTop: 0,
},
},
},
},
};
};
export default Accordion;

View File

@@ -0,0 +1,110 @@
import { lighten, darken } from '@mui/material/styles';
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const Alert = theme => {
const getColor = theme.palette.mode === 'light' ? darken : lighten;
return {
MuiAlert: {
styleOverrides: {
root: {
borderRadius: 5,
'& .MuiAlertTitle-root': {
marginBottom: theme.spacing(1.6),
},
'& a': {
color: 'inherit',
fontWeight: 500,
},
},
standardSuccess: {
color: getColor(theme.palette.success.main, 0.12),
backgroundColor: hexToRGBA(theme.palette.success.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.success.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.success.main, 0.12),
},
},
standardInfo: {
color: getColor(theme.palette.info.main, 0.12),
backgroundColor: hexToRGBA(theme.palette.info.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.info.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.info.main, 0.12),
},
},
standardWarning: {
color: getColor(theme.palette.warning.main, 0.12),
backgroundColor: hexToRGBA(theme.palette.warning.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.warning.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.warning.main, 0.12),
},
},
standardError: {
color: getColor(theme.palette.error.main, 0.12),
backgroundColor: hexToRGBA(theme.palette.error.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.error.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.error.main, 0.12),
},
},
outlinedSuccess: {
borderColor: theme.palette.success.main,
color: getColor(theme.palette.success.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.success.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.success.main, 0.12),
},
},
outlinedInfo: {
borderColor: theme.palette.info.main,
color: getColor(theme.palette.info.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.info.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.info.main, 0.12),
},
},
outlinedWarning: {
borderColor: theme.palette.warning.main,
color: getColor(theme.palette.warning.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.warning.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.warning.main, 0.12),
},
},
outlinedError: {
borderColor: theme.palette.error.main,
color: getColor(theme.palette.error.main, 0.12),
'& .MuiAlertTitle-root': {
color: getColor(theme.palette.error.main, 0.12),
},
'& .MuiAlert-icon': {
color: getColor(theme.palette.error.main, 0.12),
},
},
filled: {
fontWeight: 400,
},
},
},
};
};
export default Alert;

View File

@@ -0,0 +1,27 @@
const Avatar = theme => {
return {
MuiAvatar: {
styleOverrides: {
colorDefault: {
color: theme.palette.text.secondary,
backgroundColor: theme.palette.mode === 'light' ? theme.palette.grey[200] : theme.palette.grey[700],
},
rounded: {
borderRadius: 5,
},
},
},
MuiAvatarGroup: {
styleOverrides: {
root: {
justifyContent: 'flex-end',
'.MuiCard-root & .MuiAvatar-root': {
borderColor: theme.palette.background.paper,
},
},
},
},
};
};
export default Avatar;

View File

@@ -0,0 +1,22 @@
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const Backdrop = theme => {
return {
MuiBackdrop: {
styleOverrides: {
root: {
backgroundColor:
theme.palette.mode === 'light'
? `rgba(${theme.palette.customColors.main}, 0.7)`
: hexToRGBA(theme.palette.background.default, 0.7),
},
invisible: {
backgroundColor: 'transparent',
},
},
},
};
};
export default Backdrop;

View File

@@ -0,0 +1,50 @@
// ** Theme Config Imports
import themeConfig from 'src/configs/themeConfig';
const Button = theme => {
return {
MuiButton: {
styleOverrides: {
root: {
fontWeight: 500,
borderRadius: 5,
lineHeight: 1.71,
letterSpacing: '0.3px',
padding: `${theme.spacing(1.875, 3)}`,
},
contained: {
boxShadow: theme.shadows[3],
padding: `${theme.spacing(1.875, 5.5)}`,
},
outlined: {
padding: `${theme.spacing(1.625, 5.25)}`,
},
sizeSmall: {
padding: `${theme.spacing(1, 2.25)}`,
'&.MuiButton-contained': {
padding: `${theme.spacing(1, 3.5)}`,
},
'&.MuiButton-outlined': {
padding: `${theme.spacing(0.75, 3.25)}`,
},
},
sizeLarge: {
padding: `${theme.spacing(2.125, 5.5)}`,
'&.MuiButton-contained': {
padding: `${theme.spacing(2.125, 6.5)}`,
},
'&.MuiButton-outlined': {
padding: `${theme.spacing(1.875, 6.25)}`,
},
},
},
},
MuiButtonBase: {
defaultProps: {
disableRipple: themeConfig.disableRipple,
},
},
};
};
export default Button;

View File

@@ -0,0 +1,83 @@
const Card = theme => {
return {
MuiCard: {
styleOverrides: {
root: {
boxShadow: theme.shadows[6],
'& .card-more-options': {
marginTop: theme.spacing(-1),
marginRight: theme.spacing(-3),
},
},
},
},
MuiCardHeader: {
styleOverrides: {
root: {
padding: theme.spacing(5),
'& + .MuiCardContent-root, & + .MuiCollapse-root .MuiCardContent-root': {
paddingTop: 0,
},
'& .MuiCardHeader-subheader': {
fontSize: '0.875rem',
},
},
title: {
lineHeight: 1,
fontWeight: 500,
fontSize: '1.25rem',
letterSpacing: '0.0125em',
},
action: {
marginTop: 0,
marginRight: 0,
},
},
},
MuiCardContent: {
styleOverrides: {
root: {
padding: theme.spacing(5),
'& + .MuiCardContent-root': {
paddingTop: 0,
},
'&:last-of-type': {
paddingBottom: theme.spacing(5),
},
'& + .MuiCardActions-root': {
paddingTop: 0,
},
},
},
},
MuiCardActions: {
styleOverrides: {
root: {
padding: theme.spacing(5),
'&.card-action-dense': {
padding: theme.spacing(0, 2.5, 2.5),
'.MuiCard-root .MuiCardMedia-root + &': {
paddingTop: theme.spacing(2.5),
},
'.MuiCard-root &:first-of-type': {
paddingTop: theme.spacing(5),
paddingBottom: theme.spacing(5),
'& + .MuiCardContent-root': {
paddingTop: 0,
},
'& + .MuiCardHeader-root': {
paddingTop: 0,
},
},
},
'& .MuiButton-text': {
paddingLeft: theme.spacing(2.5),
paddingRight: theme.spacing(2.5),
},
},
},
},
};
};
export default Card;

View File

@@ -0,0 +1,19 @@
const Chip = theme => {
return {
MuiChip: {
styleOverrides: {
outlined: {
'&.MuiChip-colorDefault': {
borderColor: `rgba(${theme.palette.customColors.main}, 0.22)`,
},
},
deleteIcon: {
width: 18,
height: 18,
},
},
},
};
};
export default Chip;

View File

@@ -0,0 +1,61 @@
const DateTimePicker = theme => {
return {
MuiCalendarPicker: {
styleOverrides: {
root: {
'& [role="presentation"]': {
fontWeight: 400,
'& .PrivatePickersFadeTransitionGroup-root + .PrivatePickersFadeTransitionGroup-root > div': {
marginRight: 0,
},
'& .MuiIconButton-sizeSmall': {
padding: theme.spacing(0.5),
},
'& + div .MuiIconButton-root:not(.Mui-disabled)': {
color: theme.palette.text.secondary,
},
},
'& .PrivatePickersSlideTransition-root': {
minHeight: 240,
},
},
},
},
MuiPickersDay: {
styleOverrides: {
root: {
fontSize: '0.875rem',
},
},
},
MuiClockPicker: {
styleOverrides: {
arrowSwitcher: {
'& .MuiIconButton-root:not(.Mui-disabled)': {
color: theme.palette.text.secondary,
},
'& + div': {
'& > div': {
backgroundColor:
theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.background.default,
'& ~ .MuiIconButton-root span.MuiTypography-caption': {
color: 'inherit',
},
},
},
},
},
},
MuiMonthPicker: {
styleOverrides: {
root: {
'& > .MuiTypography-root.Mui-selected': {
fontSize: '1rem',
},
},
},
},
};
};
export default DateTimePicker;

View File

@@ -0,0 +1,104 @@
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const Dialog = theme => {
return {
MuiDialog: {
styleOverrides: {
paper: {
boxShadow: theme.shadows[6],
'&:not(.MuiDialog-paperFullScreen)': {
'@media (max-width:599px)': {
margin: theme.spacing(4),
width: `calc(100% - ${theme.spacing(8)})`,
maxWidth: `calc(100% - ${theme.spacing(8)}) !important`,
},
},
'& > .MuiList-root': {
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
},
},
},
},
MuiDialogTitle: {
styleOverrides: {
root: {
padding: theme.spacing(5),
},
},
},
MuiDialogContent: {
styleOverrides: {
root: {
padding: theme.spacing(5),
'& + .MuiDialogContent-root': {
paddingTop: 0,
},
'& + .MuiDialogActions-root': {
paddingTop: 0,
},
// Styling for Mobile Date Picker starts
'& .PrivatePickersToolbar-root': {
padding: theme.spacing(4, 5),
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
'& .MuiTypography-root': {
color: theme.palette.primary.contrastText,
},
'& span.MuiTypography-overline': {
fontSize: '1rem',
lineHeight: '24px',
letterSpacing: '0.15px',
},
'& ~ div[class^="css-"] > div[class^="css-"]': {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
'& > div[class^="css-"]': {
backgroundColor:
theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.background.default,
'& ~ .MuiIconButton-root span.MuiTypography-caption': {
color: 'inherit',
},
},
},
'& .PrivateTimePickerToolbar-hourMinuteLabel': {
alignItems: 'center',
'& > .MuiButton-root span.MuiTypography-root': {
fontWeight: 300,
lineHeight: '72px',
fontSize: '3.75rem',
letterSpacing: '-0.5px',
},
'& > .MuiTypography-root': {
color: hexToRGBA(theme.palette.primary.contrastText, 0.54),
'& + .MuiButton-root > span.MuiTypography-root': {
color: hexToRGBA(theme.palette.primary.contrastText, 0.54),
},
},
},
'& .PrivateTimePickerToolbar-ampmSelection span.MuiTypography-root:not(.Mui-selected)': {
color: hexToRGBA(theme.palette.primary.contrastText, 0.54),
},
},
// Styling for Mobile Date Picker ends
},
},
},
MuiDialogActions: {
styleOverrides: {
root: {
padding: theme.spacing(5),
'&.dialog-actions-dense': {
padding: theme.spacing(2.5),
paddingTop: 0,
},
},
},
},
};
};
export default Dialog;

View File

@@ -0,0 +1,13 @@
const Divider = theme => {
return {
MuiDivider: {
styleOverrides: {
root: {
margin: `${theme.spacing(2)} 0`,
},
},
},
};
};
export default Divider;

View File

@@ -0,0 +1,85 @@
// ** Overrides Imports
import MuiCard from './card';
import MuiChip from './chip';
import MuiLink from './link';
import MuiList from './list';
import MuiMenu from './menu';
import MuiTabs from './tabs';
import MuiInput from './input';
import MuiPaper from './paper';
import MuiTable from './table';
import MuiAlerts from './alerts';
import MuiButton from './button';
import MuiDialog from './dialog';
import MuiRating from './rating';
import MuiSelect from './select';
import MuiAvatar from './avatars';
import MuiDivider from './divider';
import MuiPopover from './popover';
import MuiTooltip from './tooltip';
import MuiBackdrop from './backdrop';
import MuiSnackbar from './snackbar';
import MuiSwitches from './switches';
import MuiTimeline from './timeline';
import MuiAccordion from './accordion';
import MuiPagination from './pagination';
import MuiTypography from './typography';
import MuiToggleButton from './toggleButton';
import MuiDateTimePicker from './dateTimePicker';
const Overrides = theme => {
const chip = MuiChip(theme);
const list = MuiList(theme);
const menu = MuiMenu(theme);
const tabs = MuiTabs(theme);
const cards = MuiCard(theme);
const input = MuiInput(theme);
const tables = MuiTable(theme);
const alerts = MuiAlerts(theme);
const button = MuiButton(theme);
const rating = MuiRating(theme);
const avatars = MuiAvatar(theme);
const divider = MuiDivider(theme);
const dialog = MuiDialog(theme);
const popover = MuiPopover(theme);
const tooltip = MuiTooltip(theme);
const backdrop = MuiBackdrop(theme);
const snackbar = MuiSnackbar(theme);
const switches = MuiSwitches(theme);
const timeline = MuiTimeline(theme);
const accordion = MuiAccordion(theme);
const pagination = MuiPagination(theme);
const dateTimePicker = MuiDateTimePicker(theme);
return Object.assign(
chip,
list,
menu,
tabs,
cards,
input,
alerts,
button,
dialog,
rating,
tables,
avatars,
divider,
MuiLink,
popover,
tooltip,
backdrop,
MuiPaper,
snackbar,
switches,
timeline,
accordion,
MuiSelect,
pagination,
MuiTypography,
dateTimePicker,
MuiToggleButton,
);
};
export default Overrides;

View File

@@ -0,0 +1,62 @@
const input = theme => {
return {
MuiInputLabel: {
styleOverrides: {
root: {
color: theme.palette.text.secondary,
},
},
},
MuiInput: {
styleOverrides: {
root: {
'&:before': {
borderBottom: `1px solid rgba(${theme.palette.customColors.main}, 0.22)`,
},
'&:hover:not(.Mui-disabled):before': {
borderBottom: `1px solid rgba(${theme.palette.customColors.main}, 0.32)`,
},
'&.Mui-disabled:before': {
borderBottom: `1px solid ${theme.palette.text.disabled}`,
},
},
},
},
MuiFilledInput: {
styleOverrides: {
root: {
backgroundColor: `rgba(${theme.palette.customColors.main}, 0.04)`,
'&:hover:not(.Mui-disabled)': {
backgroundColor: `rgba(${theme.palette.customColors.main}, 0.08)`,
},
'&:before': {
borderBottom: `1px solid rgba(${theme.palette.customColors.main}, 0.22)`,
},
'&:hover:not(.Mui-disabled):before': {
borderBottom: `1px solid rgba(${theme.palette.customColors.main}, 0.32)`,
},
},
},
},
MuiOutlinedInput: {
styleOverrides: {
root: {
'&:hover:not(.Mui-focused) .MuiOutlinedInput-notchedOutline': {
borderColor: `rgba(${theme.palette.customColors.main}, 0.32)`,
},
'&:hover.Mui-error .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.error.main,
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: `rgba(${theme.palette.customColors.main}, 0.22)`,
},
'&.Mui-disabled .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.text.disabled,
},
},
},
},
};
};
export default input;

View File

@@ -0,0 +1,9 @@
export default {
MuiLink: {
styleOverrides: {
root: {
textDecoration: 'none',
},
},
},
};

View File

@@ -0,0 +1,41 @@
const List = theme => {
return {
MuiListItemIcon: {
styleOverrides: {
root: {
minWidth: 0,
marginRight: theme.spacing(2.25),
color: theme.palette.text.secondary,
},
},
},
MuiListItemAvatar: {
styleOverrides: {
root: {
minWidth: 0,
marginRight: theme.spacing(4),
},
},
},
MuiListItemText: {
styleOverrides: {
dense: {
'& .MuiListItemText-primary': {
color: theme.palette.text.primary,
},
},
},
},
MuiListSubheader: {
styleOverrides: {
root: {
fontWeight: 600,
textTransform: 'uppercase',
color: theme.palette.text.primary,
},
},
},
};
};
export default List;

View File

@@ -0,0 +1,16 @@
const Menu = theme => {
return {
MuiMenu: {
styleOverrides: {
root: {
'& .MuiMenu-paper': {
borderRadius: 5,
boxShadow: theme.palette.mode === 'light' ? theme.shadows[8] : theme.shadows[9],
},
},
},
},
};
};
export default Menu;

View File

@@ -0,0 +1,38 @@
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const Pagination = theme => {
return {
MuiPaginationItem: {
styleOverrides: {
root: {
'&.Mui-selected:not(.Mui-disabled):not(.MuiPaginationItem-textPrimary):not(.MuiPaginationItem-textSecondary):hover':
{
backgroundColor: `rgba(${theme.palette.customColors.main}, 0.12)`,
},
},
outlined: {
borderColor: `rgba(${theme.palette.customColors.main}, 0.22)`,
},
outlinedPrimary: {
'&.Mui-selected': {
backgroundColor: hexToRGBA(theme.palette.primary.main, 0.12),
'&:hover': {
backgroundColor: `${hexToRGBA(theme.palette.primary.main, 0.2)} !important`,
},
},
},
outlinedSecondary: {
'&.Mui-selected': {
backgroundColor: hexToRGBA(theme.palette.secondary.main, 0.12),
'&:hover': {
backgroundColor: `${hexToRGBA(theme.palette.secondary.main, 0.2)} !important`,
},
},
},
},
},
};
};
export default Pagination;

View File

@@ -0,0 +1,9 @@
export default {
MuiPaper: {
styleOverrides: {
root: {
backgroundImage: 'none',
},
},
},
};

View File

@@ -0,0 +1,15 @@
const Popover = theme => {
return {
MuiPopover: {
styleOverrides: {
root: {
'& .MuiPopover-paper': {
boxShadow: theme.shadows[6],
},
},
},
},
};
};
export default Popover;

View File

@@ -0,0 +1,13 @@
const Rating = theme => {
return {
MuiRating: {
styleOverrides: {
root: {
color: theme.palette.warning.main,
},
},
},
};
};
export default Rating;

View File

@@ -0,0 +1,12 @@
export default {
MuiSelect: {
styleOverrides: {
select: {
minWidth: '6rem !important',
'&.MuiTablePagination-select': {
minWidth: '1rem !important',
},
},
},
},
};

View File

@@ -0,0 +1,13 @@
const Snackbar = theme => {
return {
MuiSnackbarContent: {
styleOverrides: {
root: {
backgroundColor: theme.palette.mode === 'light' ? theme.palette.grey[900] : theme.palette.grey[100],
},
},
},
};
};
export default Snackbar;

View File

@@ -0,0 +1,15 @@
const Switch = theme => {
return {
MuiSwitch: {
styleOverrides: {
root: {
'& .MuiSwitch-track': {
backgroundColor: `rgb(${theme.palette.customColors.main})`,
},
},
},
},
};
};
export default Switch;

View File

@@ -0,0 +1,66 @@
const Table = theme => {
return {
MuiTableContainer: {
styleOverrides: {
root: {
boxShadow: theme.shadows[0],
borderTopColor: theme.palette.divider,
},
},
},
MuiTableHead: {
styleOverrides: {
root: {
textTransform: 'uppercase',
'& .MuiTableCell-head': {
fontSize: '0.75rem',
fontWeight: 600,
letterSpacing: '0.13px',
},
},
},
},
MuiTableBody: {
styleOverrides: {
root: {
'& .MuiTableCell-body': {
letterSpacing: '0.25px',
color: theme.palette.text.secondary,
'&:not(.MuiTableCell-sizeSmall):not(.MuiTableCell-paddingCheckbox):not(.MuiTableCell-paddingNone)': {
paddingTop: theme.spacing(3.5),
paddingBottom: theme.spacing(3.5),
},
},
},
},
},
MuiTableRow: {
styleOverrides: {
root: {
'& .MuiTableCell-head:first-child, & .MuiTableCell-root:first-child ': {
paddingLeft: theme.spacing(5),
},
'& .MuiTableCell-head:last-child, & .MuiTableCell-root:last-child': {
paddingRight: theme.spacing(5),
},
},
},
},
MuiTableCell: {
styleOverrides: {
root: {
borderBottom: `1px solid ${theme.palette.divider}`,
'& .MuiButton-root': {
textTransform: 'uppercase',
color: theme.palette.text.secondary,
},
},
stickyHeader: {
backgroundColor: theme.palette.customColors.tableHeaderBg,
},
},
},
};
};
export default Table;

View File

@@ -0,0 +1,27 @@
const Tabs = theme => {
return {
MuiTabs: {
styleOverrides: {
vertical: {
minWidth: 130,
marginRight: theme.spacing(4),
borderRight: `1px solid ${theme.palette.divider}`,
'& .MuiTab-root': {
minWidth: 130,
},
},
},
},
MuiTab: {
styleOverrides: {
textColorSecondary: {
'&.Mui-selected': {
color: theme.palette.text.secondary,
},
},
},
},
};
};
export default Tabs;

View File

@@ -0,0 +1,80 @@
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const Timeline = theme => {
return {
MuiTimelineItem: {
styleOverrides: {
root: {
'&:not(:last-of-type)': {
'& .MuiTimelineContent-root': {
marginBottom: theme.spacing(4),
},
},
},
},
},
MuiTimelineConnector: {
styleOverrides: {
root: {
backgroundColor: theme.palette.divider,
},
},
},
MuiTimelineContent: {
styleOverrides: {
root: {
marginTop: theme.spacing(0.5),
},
},
},
MuiTimelineDot: {
styleOverrides: {
filledPrimary: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.primary.main, 0.12)}`,
},
filledSecondary: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.secondary.main, 0.12)}`,
},
filledSuccess: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.success.main, 0.12)}`,
},
filledError: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.error.main, 0.12)}`,
},
filledWarning: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.warning.main, 0.12)}`,
},
filledInfo: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.info.main, 0.12)}`,
},
filledGrey: {
boxShadow: `0 0 0 3px ${hexToRGBA(theme.palette.grey[400], 0.12)}`,
},
outlinedPrimary: {
'& svg': { color: theme.palette.primary.main },
},
outlinedSecondary: {
'& svg': { color: theme.palette.secondary.main },
},
outlinedSuccess: {
'& svg': { color: theme.palette.success.main },
},
outlinedError: {
'& svg': { color: theme.palette.error.main },
},
outlinedWarning: {
'& svg': { color: theme.palette.warning.main },
},
outlinedInfo: {
'& svg': { color: theme.palette.info.main },
},
outlinedGrey: {
'& svg': { color: theme.palette.grey[500] },
},
},
},
};
};
export default Timeline;

View File

@@ -0,0 +1,16 @@
export default {
MuiToggleButtonGroup: {
styleOverrides: {
root: {
borderRadius: 4,
},
},
},
MuiToggleButton: {
styleOverrides: {
root: {
borderRadius: 4,
},
},
},
};

View File

@@ -0,0 +1,25 @@
// ** Util Import
import { hexToRGBA } from 'src/@core/utils/hex-to-rgba';
const Tooltip = theme => {
return {
MuiTooltip: {
styleOverrides: {
tooltip: {
backgroundColor:
theme.palette.mode === 'light'
? hexToRGBA(theme.palette.grey[900], 0.9)
: hexToRGBA(theme.palette.grey[700], 0.9),
},
arrow: {
color:
theme.palette.mode === 'light'
? hexToRGBA(theme.palette.grey[900], 0.9)
: hexToRGBA(theme.palette.grey[700], 0.9),
},
},
},
};
};
export default Tooltip;

View File

@@ -0,0 +1,13 @@
const Typography = theme => {
return {
MuiTypography: {
styleOverrides: {
gutterBottom: {
marginBottom: theme.spacing(2),
},
},
},
};
};
export default Typography;

View File

@@ -0,0 +1,107 @@
const DefaultPalette = (mode, themeColor) => {
// ** Vars
const lightColor = '58, 53, 65';
const darkColor = '231, 227, 252';
const mainColor = mode === 'light' ? lightColor : darkColor;
const primaryGradient = () => {
if (themeColor === 'primary') {
return 'rgba(12, 45, 87,0.7)';
} else if (themeColor === 'secondary') {
return '#9C9FA4';
} else if (themeColor === 'success') {
return '#93DD5C';
} else if (themeColor === 'error') {
return '#FF8C90';
} else if (themeColor === 'warning') {
return '#FFCF5C';
} else {
return '#6ACDFF';
}
};
return {
customColors: {
main: mainColor,
primaryGradient: primaryGradient(),
tableHeaderBg: mode === 'light' ? '#F9FAFC' : '#3D3759',
},
common: {
black: '#000',
white: '#FFF',
},
mode: mode,
primary: {
light: 'rgba(12, 45, 87,0.2)',
main: 'rgba(12, 45, 87,0.7)',
dark: 'rgba(12, 45, 87,1)',
contrastText: '#FFF',
},
secondary: {
light: '#9C9FA4',
main: '#8A8D93',
dark: '#777B82',
contrastText: '#FFF',
},
success: {
light: '#6AD01F',
main: '#56CA00',
dark: '#4CB200',
contrastText: '#FFF',
},
error: {
light: '#FF6166',
main: '#FF4C51',
dark: '#E04347',
contrastText: '#FFF',
},
warning: {
light: '#FFCA64',
main: '#FFB400',
dark: '#E09E00',
contrastText: '#FFF',
},
info: {
light: '#32BAFF',
main: '#16B1FF',
dark: '#139CE0',
contrastText: '#FFF',
},
grey: {
50: '#FAFAFA',
100: '#F5F5F5',
200: '#EEEEEE',
300: '#E0E0E0',
400: '#BDBDBD',
500: '#9E9E9E',
600: '#757575',
700: '#616161',
800: '#424242',
900: '#212121',
A100: '#D5D5D5',
A200: '#AAAAAA',
A400: '#616161',
A700: '#303030',
},
text: {
primary: `rgba(${mainColor}, 0.87)`,
secondary: `rgba(${mainColor}, 0.68)`,
disabled: `rgba(${mainColor}, 0.38)`,
},
divider: `rgba(${mainColor}, 0.12)`,
background: {
paper: mode === 'light' ? '#FFF' : '#312D4B',
default: mode === 'light' ? '#F4F5FA' : '#28243D',
},
action: {
active: `rgba(${mainColor}, 0.54)`,
hover: `rgba(${mainColor}, 0.04)`,
selected: `rgba(${mainColor}, 0.08)`,
disabled: `rgba(${mainColor}, 0.3)`,
disabledBackground: `rgba(${mainColor}, 0.18)`,
focus: `rgba(${mainColor}, 0.12)`,
},
};
};
export default DefaultPalette;

View File

@@ -0,0 +1,61 @@
const Shadows = mode => {
if (mode === 'light') {
return [
'none',
'0px 2px 1px -1px rgba(58, 53, 65, 0.2), 0px 1px 1px 0px rgba(58, 53, 65, 0.14), 0px 1px 3px 0px rgba(58, 53, 65, 0.12)',
'0px 3px 1px -2px rgba(58, 53, 65, 0.2), 0px 2px 2px 0px rgba(58, 53, 65, 0.14), 0px 1px 5px 0px rgba(58, 53, 65, 0.12)',
'0px 4px 8px -4px rgba(58, 53, 65, 0.42)',
'0px 6px 18px -8px rgba(58, 53, 65, 0.56)',
'0px 3px 5px -1px rgba(58, 53, 65, 0.2), 0px 5px 8px 0px rgba(58, 53, 65, 0.14), 0px 1px 14px 0px rgba(58, 53, 65, 0.12)',
'0px 2px 10px 0px rgba(58, 53, 65, 0.1)',
'0px 4px 5px -2px rgba(58, 53, 65, 0.2), 0px 7px 10px 1px rgba(58, 53, 65, 0.14), 0px 2px 16px 1px rgba(58, 53, 65, 0.12)',
'0px 5px 5px -3px rgba(58, 53, 65, 0.2), 0px 8px 10px 1px rgba(58, 53, 65, 0.14), 0px 3px 14px 2px rgba(58, 53, 65, 0.12)',
'0px 5px 6px -3px rgba(58, 53, 65, 0.2), 0px 9px 12px 1px rgba(58, 53, 65, 0.14), 0px 3px 16px 2px rgba(58, 53, 65, 0.12)',
'0px 6px 6px -3px rgba(58, 53, 65, 0.2), 0px 10px 14px 1px rgba(58, 53, 65, 0.14), 0px 4px 18px 3px rgba(58, 53, 65, 0.12)',
'0px 6px 7px -4px rgba(58, 53, 65, 0.2), 0px 11px 15px 1px rgba(58, 53, 65, 0.14), 0px 4px 20px 3px rgba(58, 53, 65, 0.12)',
'0px 7px 8px -4px rgba(58, 53, 65, 0.2), 0px 12px 17px 2px rgba(58, 53, 65, 0.14), 0px 5px 22px 4px rgba(58, 53, 65, 0.12)',
'0px 7px 8px -4px rgba(58, 53, 65, 0.2), 0px 13px 19px 2px rgba(58, 53, 65, 0.14), 0px 5px 24px 4px rgba(58, 53, 65, 0.12)',
'0px 7px 9px -4px rgba(58, 53, 65, 0.2), 0px 14px 21px 2px rgba(58, 53, 65, 0.14), 0px 5px 26px 4px rgba(58, 53, 65, 0.12)',
'0px 8px 9px -5px rgba(58, 53, 65, 0.2), 0px 15px 22px 2px rgba(58, 53, 65, 0.14), 0px 6px 28px 5px rgba(58, 53, 65, 0.12)',
'0px 8px 10px -5px rgba(58, 53, 65, 0.2), 0px 16px 24px 2px rgba(58, 53, 65, 0.14), 0px 6px 30px 5px rgba(58, 53, 65, 0.12)',
'0px 8px 11px -5px rgba(58, 53, 65, 0.2), 0px 17px 26px 2px rgba(58, 53, 65, 0.14), 0px 6px 32px 5px rgba(58, 53, 65, 0.12)',
'0px 9px 11px -5px rgba(58, 53, 65, 0.2), 0px 18px 28px 2px rgba(58, 53, 65, 0.14), 0px 7px 34px 6px rgba(58, 53, 65, 0.12)',
'0px 9px 12px -6px rgba(58, 53, 65, 0.2), 0px 19px 29px 2px rgba(58, 53, 65, 0.14), 0px 7px 36px 6px rgba(58, 53, 65, 0.12)',
'0px 10px 13px -6px rgba(58, 53, 65, 0.2), 0px 20px 31px 3px rgba(58, 53, 65, 0.14), 0px 8px 38px 7px rgba(58, 53, 65, 0.12)',
'0px 10px 13px -6px rgba(58, 53, 65, 0.2), 0px 21px 33px 3px rgba(58, 53, 65, 0.14), 0px 8px 40px 7px rgba(58, 53, 65, 0.12)',
'0px 10px 14px -6px rgba(58, 53, 65, 0.2), 0px 22px 35px 3px rgba(58, 53, 65, 0.14), 0px 8px 42px 7px rgba(58, 53, 65, 0.12)',
'0px 11px 14px -7px rgba(58, 53, 65, 0.2), 0px 23px 36px 3px rgba(58, 53, 65, 0.14), 0px 9px 44px 8px rgba(58, 53, 65, 0.12)',
'0px 11px 15px -7px rgba(58, 53, 65, 0.2), 0px 24px 38px 3px rgba(58, 53, 65, 0.14), 0px 9px 46px 8px rgba(58, 53, 65, 0.12)',
];
} else {
return [
'none',
'0px 2px 1px -1px rgba(19, 17, 32, 0.2), 0px 1px 1px 0px rgba(19, 17, 32, 0.14), 0px 1px 3px 0px rgba(19, 17, 32, 0.12)',
'0px 3px 1px -2px rgba(19, 17, 32, 0.2), 0px 2px 2px 0px rgba(19, 17, 32, 0.14), 0px 1px 5px 0px rgba(19, 17, 32, 0.12)',
'0px 4px 8px -4px rgba(19, 17, 32, 0.42)',
'0px 6px 18px -8px rgba(19, 17, 32, 0.56)',
'0px 3px 5px -1px rgba(19, 17, 32, 0.2), 0px 5px 8px rgba(19, 17, 32, 0.14), 0px 1px 14px rgba(19, 17, 32, 0.12)',
'0px 2px 10px 0px rgba(19, 17, 32, 0.1)',
'0px 4px 5px -2px rgba(19, 17, 32, 0.2), 0px 7px 10px 1px rgba(19, 17, 32, 0.14), 0px 2px 16px 1px rgba(19, 17, 32, 0.12)',
'0px 5px 5px -3px rgba(19, 17, 32, 0.2), 0px 8px 10px 1px rgba(19, 17, 32, 0.14), 0px 3px 14px 2px rgba(19, 17, 32, 0.12)',
'0px 5px 6px -3px rgba(19, 17, 32, 0.2), 0px 9px 12px 1px rgba(19, 17, 32, 0.14), 0px 3px 16px 2px rgba(19, 17, 32, 0.12)',
'0px 6px 6px -3px rgba(19, 17, 32, 0.2), 0px 10px 14px 1px rgba(19, 17, 32, 0.14), 0px 4px 18px 3px rgba(19, 17, 32, 0.12)',
'0px 6px 7px -4px rgba(19, 17, 32, 0.2), 0px 11px 15px 1px rgba(19, 17, 32, 0.14), 0px 4px 20px 3px rgba(19, 17, 32, 0.12)',
'0px 7px 8px -4px rgba(19, 17, 32, 0.2), 0px 12px 17px 2px rgba(19, 17, 32, 0.14), 0px 5px 22px 4px rgba(19, 17, 32, 0.12)',
'0px 7px 8px -4px rgba(19, 17, 32, 0.2), 0px 13px 19px 2px rgba(19, 17, 32, 0.14), 0px 5px 24px 4px rgba(19, 17, 32, 0.12)',
'0px 7px 9px -4px rgba(19, 17, 32, 0.2), 0px 14px 21px 2px rgba(19, 17, 32, 0.14), 0px 5px 26px 4px rgba(19, 17, 32, 0.12)',
'0px 8px 9px -5px rgba(19, 17, 32, 0.2), 0px 15px 22px 2px rgba(19, 17, 32, 0.14), 0px 6px 28px 5px rgba(19, 17, 32, 0.12)',
'0px 8px 10px -5px rgba(19, 17, 32, 0.2), 0px 16px 24px 2px rgba(19, 17, 32, 0.14), 0px 6px 30px 5px rgba(19, 17, 32, 0.12)',
'0px 8px 11px -5px rgba(19, 17, 32, 0.2), 0px 17px 26px 2px rgba(19, 17, 32, 0.14), 0px 6px 32px 5px rgba(19, 17, 32, 0.12)',
'0px 9px 11px -5px rgba(19, 17, 32, 0.2), 0px 18px 28px 2px rgba(19, 17, 32, 0.14), 0px 7px 34px 6px rgba(19, 17, 32, 0.12)',
'0px 9px 12px -6px rgba(19, 17, 32, 0.2), 0px 19px 29px 2px rgba(19, 17, 32, 0.14), 0px 7px 36px 6px rgba(19, 17, 32, 0.12)',
'0px 10px 13px -6px rgba(19, 17, 32, 0.2), 0px 20px 31px 3px rgba(19, 17, 32, 0.14), 0px 8px 38px 7px rgba(19, 17, 32, 0.12)',
'0px 10px 13px -6px rgba(19, 17, 32, 0.2), 0px 21px 33px 3px rgba(19, 17, 32, 0.14), 0px 8px 40px 7px rgba(19, 17, 32, 0.12)',
'0px 10px 14px -6px rgba(19, 17, 32, 0.2), 0px 22px 35px 3px rgba(19, 17, 32, 0.14), 0px 8px 42px 7px rgba(19, 17, 32, 0.12)',
'0px 11px 14px -7px rgba(19, 17, 32, 0.2), 0px 23px 36px 3px rgba(19, 17, 32, 0.14), 0px 9px 44px 8px rgba(19, 17, 32, 0.12)',
'0px 11px 15px -7px rgba(19, 17, 32, 0.2), 0px 24px 38px 3px rgba(19, 17, 32, 0.14), 0px 9px 46px 8px rgba(19, 17, 32, 0.12)',
];
}
};
export default Shadows;

View File

@@ -0,0 +1,3 @@
export default {
spacing: factor => `${0.25 * factor}rem`,
};

View File

@@ -0,0 +1,3 @@
export default {
spacing: factor => `${0.25 * factor}rem`
}

View File

@@ -0,0 +1,64 @@
const Typography = theme => {
return {
h1: {
fontWeight: 500,
letterSpacing: '-1.5px',
color: theme.palette.text.primary,
},
h2: {
fontWeight: 500,
letterSpacing: '-0.5px',
color: theme.palette.text.primary,
},
h3: {
fontWeight: 500,
letterSpacing: 0,
color: theme.palette.text.primary,
},
h4: {
fontWeight: 500,
letterSpacing: '0.25px',
color: theme.palette.text.primary,
},
h5: {
fontWeight: 500,
letterSpacing: 0,
color: theme.palette.text.primary,
},
h6: {
letterSpacing: '0.15px',
color: theme.palette.text.primary,
},
subtitle1: {
letterSpacing: '0.15px',
color: theme.palette.text.primary,
},
subtitle2: {
letterSpacing: '0.1px',
color: theme.palette.text.secondary,
},
body1: {
letterSpacing: '0.15px',
color: theme.palette.text.primary,
},
body2: {
lineHeight: 1.5,
letterSpacing: '0.15px',
color: theme.palette.text.secondary,
},
button: {
letterSpacing: '0.3px',
color: theme.palette.text.primary,
},
caption: {
letterSpacing: '0.4px',
color: theme.palette.text.secondary,
},
overline: {
letterSpacing: '1px',
color: theme.palette.text.secondary,
},
};
};
export default Typography;

View File

@@ -0,0 +1,5 @@
import createCache from '@emotion/cache';
export const createEmotionCache = () => {
return createCache({ key: 'css' });
};

View File

@@ -0,0 +1,14 @@
/**
** Hex color to RGBA color
*/
export const hexToRGBA = (hexCode, opacity) => {
let hex = hexCode.replace('#', '');
if (hex.length === 3) {
hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`;
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
};

View File

@@ -0,0 +1,15 @@
import axios from 'axios';
import { API_PREF } from './common';
const CancelOrder = ({ custom_id }) => {
return axios
.post(`/api/order/cancel-order`, { custom_id })
.then(response => {
return response['data']['state'];
})
.catch(error => {
console.error(error);
});
};
export default CancelOrder;

View File

@@ -0,0 +1,10 @@
import axios from 'axios';
const CheckSession = ({ session }) => {
return axios
.get(`/api/auth/check_session`, { headers: { session } })
.then(res => res['data'])
.catch(err => console.error(err));
};
export default CheckSession;

View File

@@ -0,0 +1,15 @@
import axios from 'axios';
import { API_PREF } from './common';
const checkoutCart = items_to_checkout => {
return axios
.post(`/api/inventory/check_out`, items_to_checkout)
.then(response => {
return response;
})
.catch(error => {
console.error(error);
});
};
export default checkoutCart;

View File

@@ -0,0 +1,3 @@
const API_PREF = process.env.NEXT_PUBLIC_API_PREF;
export { API_PREF };

View File

@@ -0,0 +1,22 @@
import axios from 'axios';
import { API_PREF } from './common';
const fetchAllOrders = ({ session }) => {
console.log({ session });
return axios
.get(`/api/order/list`, { headers: { session } })
.then(function (response) {
let { data } = response;
let { orders } = data;
return orders;
})
.catch(function (error) {
// handle error
console.log(error);
});
};
export default fetchAllOrders;

View File

@@ -0,0 +1,21 @@
import axios from 'axios';
import { API_PREF } from './common';
const fetchCategories = () => {
return axios
.get(`/api/categories/list`)
.then(function (response) {
let { data } = response;
let { categories } = data;
return categories;
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
};
export default fetchCategories;

View File

@@ -0,0 +1,10 @@
import axios from 'axios';
const fetchCategory = async ({ cid }) => {
return axios
.get(`/api/categories/view?cid=${cid}`)
.then(res => res)
.catch(err => console.error(err));
};
export default fetchCategory;

View File

@@ -0,0 +1,18 @@
import axios from 'axios';
import { API_PREF } from './common';
const fetchCustomerOrders = ({ session }) => {
return axios
.get(`/api/order/listByCustomer`, { headers: { session } })
.then(function (response) {
let { data } = response;
let { orders } = data;
return orders;
})
.catch(function (error) {
// handle error
console.log(error);
});
};
export default fetchCustomerOrders;

View File

@@ -0,0 +1,10 @@
import axios from 'axios';
const fetchListByCategory = ({ cid }) => {
return axios
.get(`/api/products/listByCategory?cid=${cid}`)
.then(res => res)
.catch(err => console.error(err));
};
export default fetchListByCategory;

View File

@@ -0,0 +1,16 @@
import axios from 'axios';
import { API_PREF } from './common';
const fetchListByCategoryUnsold = ({ cid }) => {
return axios
.get(`/api/products/listByCategoryUnsold?cid=${cid}`)
.then(res => {
let {
data: { products },
} = res;
return products;
})
.catch(err => console.error(err));
};
export default fetchListByCategoryUnsold;

View File

@@ -0,0 +1,13 @@
import axios from 'axios';
import { API_PREF } from './common';
const fetchProduct = ({ pid }) => {
return axios
.get(`/api/products/view?pid=${pid}`)
.then(res => {
return res;
})
.catch(err => console.error(err));
};
export default fetchProduct;

View File

@@ -0,0 +1,19 @@
import axios from 'axios';
import { API_PREF } from './common';
const fetchProducts = () => {
return axios
.get(`/api/products/list`)
.then(function (response) {
let { data } = response;
let { products } = data;
return products;
})
.catch(function (error) {
// handle error
console.log(error);
});
};
export default fetchProducts;

View File

@@ -0,0 +1,18 @@
import axios from 'axios';
import { API_PREF } from './common';
const GetOrderDetails = ({ cart, currency_code }) => {
let session_raw = localStorage.getItem('session');
let { session } = JSON.parse(session_raw);
return axios
.post(`/api/order/get-order-details`, { cart, currency_code }, { headers: { session } })
.then(response => {
return response['data'];
})
.catch(error => {
console.error(error);
});
};
export default GetOrderDetails;

View File

@@ -0,0 +1,16 @@
import axios from 'axios';
import { API_PREF } from './common';
import { csrf } from 'src/lib/csrf';
const SaveOrder = ({ custom_id }) => {
return axios
.post(`/api/order/save-order`, { custom_id })
.then(response => {
return response['data']['state'];
})
.catch(error => {
console.error(error);
});
};
export default SaveOrder;

View File

@@ -0,0 +1,124 @@
// ** React Imports
import { useContext } from 'react';
// ** MUI Imports
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Grid from '@mui/material/Grid';
// ** Icons Imports
import CartPlus from 'mdi-material-ui/CartPlus';
import { useRouter } from 'next/router';
import { Stack } from '@mui/material';
import { CartContext } from 'src/contexts/cart';
import TextSnippetIcon from '@mui/icons-material/TextSnippet';
// Styled Grid component
const StyledGrid = styled(Grid)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
[theme.breakpoints.down('md')]: {
borderBottom: `1px solid ${theme.palette.divider}`,
},
[theme.breakpoints.up('md')]: {
borderRight: `1px solid ${theme.palette.divider}`,
},
}));
const CardProduct = ({ product, i }) => {
let router = useRouter();
const { addToCart } = useContext(CartContext);
if (typeof window === 'undefined') return null;
return (
<Card>
<Grid container spacing={6}>
<StyledGrid item md={5} xs={12}>
<CardContent sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div
style={{
width: '100%',
height: '100%',
minWidth: '100px',
minHeight: '100px',
backgroundImage: `url('/api/files/get?dir_prefix=${product.product_image}')`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
/>
</CardContent>
</StyledGrid>
<Grid
item
xs={12}
md={7}
sx={{
paddingTop: ['0 !important', '0 !important', '1.5rem !important'],
paddingLeft: ['1.5rem !important', '1.5rem !important', '0 !important'],
}}
>
<CardContent>
<Stack direction="column">
<Typography variant="caption" sx={{ marginBottom: 2 }}>
{product.name}
</Typography>
<Typography variant="caption" sx={{ fontWeight: 500, marginBottom: 3 }}>
Price:{' '}
<Box component="span" sx={{ fontWeight: 'bold' }}>
${parseInt(product.price).toFixed(2)}
</Box>
</Typography>
<Typography variant="caption" sx={{ fontWeight: 500, marginBottom: 3 }}>
Remains:{' '}
<Box component="span" sx={{ fontWeight: 'bold' }}>
{product.count}
</Box>
</Typography>
</Stack>
</CardContent>
<CardActions className="card-action-dense">
<Stack direction="row" spacing={'1rem'} justifyContent={'flex-end'} sx={{ width: '100%' }}>
<Box>
<Button
size="small"
variant="contained"
disabled={product.unsold_count < 1}
onClick={() => {
addToCart(product.pid, 1, parseInt(product.price));
}}
startIcon={<CartPlus />}
>
{product.unsold_count < 1 ? 'sold out' : 'Add'}
</Button>
</Box>
<Box>
<Button
size="small"
variant="contained"
onClick={() => {
router.push(`/shopfront/product/${product.pid}`);
}}
startIcon={<TextSnippetIcon />}
>
Details
</Button>
</Box>
</Stack>
</CardActions>
</Grid>
</Grid>
</Card>
);
};
export default CardProduct;

View File

@@ -0,0 +1,112 @@
// ** MUI Imports
import Card from '@mui/material/Card';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import CardContent from '@mui/material/CardContent';
// ** Icons Imports
import { Stack } from '@mui/material';
import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';
import { useRouter } from 'next/router';
import { useContext, useEffect, useState } from 'react';
import { CartContext } from 'src/contexts/cart';
import fetchProduct from 'src/api/fetchProduct';
import Loading from 'src/components/Loading';
const CardProductDetail = () => {
const router = useRouter();
const { pid } = router.query;
const [product, setProduct] = useState(null);
const { addToCart } = useContext(CartContext);
useEffect(() => {
const update = async () => {
let response = await fetchProduct({ pid });
let { data } = response;
let { product } = data;
setProduct(product);
};
if (pid) update();
}, [pid]);
if (!product)
return (
<>
<Loading />
</>
);
// help avoid pre-rendering
if (typeof window === 'undefined') return null;
return (
<Card>
<Grid container spacing={6}>
<Grid
item
sm={5}
xs={12}
sx={{ paddingTop: ['0 !important', '1.5rem !important'], paddingLeft: ['1.5rem !important', '0 !important'] }}
>
<CardContent
sx={{
height: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'action.hover',
padding: theme => `${theme.spacing(18, 5, 16)} !important`,
}}
>
<Stack direction="column" justifyContent={'center'} alignItems={'center'} spacing={'3rem'}>
<Box
style={{
backgroundImage: `url('/api/files/get?dir_prefix=${product.product_image}')`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
width: '300px',
minHeight: '300px',
borderRadius: '10px',
}}
></Box>
<Box sx={{ mb: 3.5, display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
<Typography variant="h6">HKD $</Typography>
<Typography variant="h6" sx={{ lineHeight: 1, fontWeight: 600, fontSize: '3.75rem !important' }}>
{parseInt(product.price).toFixed(2)}
</Typography>
</Box>
<Typography variant="body2" sx={{ mb: 13.75, display: 'flex', flexDirection: 'column' }}></Typography>
</Stack>
</CardContent>
</Grid>
<Grid item xs={12} sm={7}>
<CardContent sx={{ padding: theme => `${theme.spacing(3.25, 5.75, 6.25)} !important` }}>
<Typography variant="h6" sx={{ marginBottom: 3.5 }}>
{product.name}
</Typography>
<Typography variant="body2">{product.description}</Typography>
<Divider sx={{ marginTop: 6.5, marginBottom: 6.75 }} />
<Stack direction="row" justifyContent={'flex-end'}>
<Button
disabled={product.unsold_count < 1}
variant="contained"
onClick={() => addToCart(pid, 1, parseInt(product.price))}
>
<AddShoppingCartIcon />
{product.unsold_count < 1 ? 'sold out' : 'Add to cart'}
</Button>
</Stack>
</CardContent>
</Grid>
</Grid>
</Card>
);
};
export default CardProductDetail;

View File

@@ -0,0 +1,165 @@
// ** React Imports
import React, { useContext, useEffect, useState } from 'react';
// ** MUI Imports
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Grid from '@mui/material/Grid';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
// ** Icons Imports
import { Stack, TextField } from '@mui/material';
import { CartContext } from 'src/contexts/cart';
// Styled Grid component
const StyledGrid = styled(Grid)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
[theme.breakpoints.down('md')]: {
borderBottom: `1px solid ${theme.palette.divider}`,
},
[theme.breakpoints.up('md')]: {
borderRight: `1px solid ${theme.palette.divider}`,
},
}));
const CartItemDetail = ({ checking_out, products, pid, qty }) => {
let [product, setProduct] = useState(null);
const { addToCart, removeFromCart, total_price, changeCartItemQty } = useContext(CartContext);
const qtyRef = React.useRef();
const handleSelectAll = () => {
qtyRef.current.select();
};
useEffect(() => {
let result = products.filter(p => p.pid === pid);
console.log({ result, pid, products });
setProduct(result[0]);
}, [pid]);
if (!product) return <>Updating product</>;
return (
<Card sx={{ width: '500px' }}>
<Grid container spacing={6}>
<StyledGrid item md={5} xs={12}>
<CardContent sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div
style={{
width: '100%',
height: '100%',
minWidth: '100px',
minHeight: '100px',
backgroundImage: `url('/api/files/get?dir_prefix=${product.product_image}')`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
/>
</CardContent>
</StyledGrid>
<Grid
item
xs={12}
md={7}
sx={{
paddingTop: ['0 !important', '0 !important', '1.5rem !important'],
paddingLeft: ['1.5rem !important', '1.5rem !important', '0 !important'],
}}
>
<CardContent>
<Typography variant="h6" sx={{ marginBottom: 2 }}>
{product.name}
</Typography>
<Typography variant="body2" sx={{ marginBottom: 3.5 }}>
{product.description}
</Typography>
<Typography sx={{ fontWeight: 500, marginBottom: 3 }}>
Price:{' '}
<Box component="span" sx={{ fontWeight: 'bold' }}>
${parseInt(product.price).toFixed(2)}
</Box>
</Typography>
</CardContent>
<CardActions>
<Stack direction="row" justifyContent={'flex-end'} style={{ width: '100%' }}>
<Stack sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}>
<Box flexGrow={10} backgroundColor="pink"></Box>
<IconButton
disabled={checking_out}
id="long-button"
aria-label="share"
aria-haspopup="true"
onClick={() => addToCart(pid, 1, parseInt(product.price))}
aria-controls="long-menu"
>
<AddCircleOutlineIcon fontSize="small" />
</IconButton>
<Box textAlign={'center'} width={'2rem'}>
<TextField
disabled={checking_out}
inputRef={qtyRef}
variant="standard"
value={qty}
inputProps={{ style: { textAlign: 'center' } }}
onChange={e => {
try {
let final_qty = parseInt(e.target.value);
changeCartItemQty(pid, final_qty, product.price);
} catch (error) {
console.error("user wasn't entered a number");
}
}}
onBlur={e => {
try {
let final_qty = parseInt(e.target.value);
changeCartItemQty(pid, final_qty, product.price);
} catch (error) {
console.error("user wasn't entered a number");
}
}}
onClick={handleSelectAll}
/>
</Box>
<IconButton
disabled={checking_out}
id="long-button"
aria-label="share"
aria-haspopup="true"
onClick={() => removeFromCart(pid)}
aria-controls="long-menu"
>
{qty == 1 ? (
<>
<DeleteOutlineIcon fontSize="small" color={'error'} />
</>
) : (
<>
<RemoveCircleOutlineIcon fontSize="small" />
</>
)}
</IconButton>
</Stack>
</Stack>
</CardActions>
</Grid>
</Grid>
</Card>
);
};
export default CartItemDetail;

View File

@@ -0,0 +1,78 @@
import React, { useContext, useEffect, useState } from 'react';
import { PayPalButtons, usePayPalScriptReducer } from '@paypal/react-paypal-js';
import { useRouter } from 'next/router';
import { CartContext } from 'src/contexts/cart';
import SaveOrder from 'src/api/saveOrder';
import GetOrderDetails from 'src/api/getOrderDetails';
import CancelOrder from 'src/api/cancelOrder';
const Checkout = () => {
const router = useRouter();
const { cart, total_price, products, proceedCheckOutCart, emptyCart } = useContext(CartContext);
const [{ options, isPending }, dispatch] = usePayPalScriptReducer();
const currency_code = 'HKD';
const [custom_id, setCustomId] = useState('');
const share = {};
const onCreateOrder = async (data, actions) => {
const proceed = async () => {
try {
let { order_details } = await GetOrderDetails({ cart, currency_code });
share['custom_id'] = order_details['custom_id'];
return actions.order.create({ purchase_units: [{ amount: { value: total_price } }] });
} catch (error) {
console.error(error);
}
};
return await proceed();
};
const onCancelOrder = async (data, actions) => {
const cancel = async () => {
try {
await CancelOrder({ custom_id: share['custom_id'] });
alert('Your order has been cancelled');
} catch (error) {
console.error(error);
}
};
return await cancel();
};
const onApproveOrder = (data, actions) => {
return actions.order
.capture()
.then(async details => {
let { custom_id } = share;
await SaveOrder({ custom_id });
return await emptyCart();
})
.then(() => {
(async () => {
await proceedCheckOutCart();
router.replace('/shopfront/cart/Thankyou');
})();
})
.catch(err => console.error(err));
};
return (
<div className="checkout">
{isPending ? (
<p>LOADING...</p>
) : (
<>
<PayPalButtons
style={{ layout: 'vertical' }}
createOrder={(data, actions) => onCreateOrder(data, actions)}
onApprove={(data, actions) => onApproveOrder(data, actions)}
onCancel={(data, actions) => onCancelOrder(data, actions)}
/>
</>
)}
</div>
);
};
export default Checkout;

View File

@@ -0,0 +1,99 @@
// ** MUI Imports
import Paper from '@mui/material/Paper';
import Table from '@mui/material/Table';
import TableRow from '@mui/material/TableRow';
import TableHead from '@mui/material/TableHead';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import NotesIcon from '@mui/icons-material/Notes';
import crypto from 'crypto';
import { Box, Stack, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import fetchCustomerOrders from 'src/api/fetchCustomerOrders';
import Loading from './Loading';
import NoOrders from './NoOrders';
const CustomerOrderTable = () => {
let [orders, setOrders] = useState(null);
let [loading, setLoading] = useState(true);
useEffect(() => {
const fetchOrder = async () => {
try {
let session_raw = localStorage.getItem('session');
let { session } = JSON.parse(session_raw);
let fetch_orders = await fetchCustomerOrders({ session });
setOrders(fetch_orders);
setLoading(false);
} catch (error) {
console.error('error during fetching orders');
}
};
fetchOrder();
}, []);
if (loading) return <Loading />;
if (!orders || orders.length == 0) return <NoOrders />;
return (
<TableContainer component={Paper} sx={{ minWidth: '66vw' }}>
<Table sx={{ width: '100%', minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center"></TableCell>
{/* <TableCell align="center">custom_id</TableCell> */}
{/* <TableCell align="center">username</TableCell> */}
<TableCell align="center">invoice_id</TableCell>
<TableCell align="center">currency</TableCell>
<TableCell align="center">order status</TableCell>
<TableCell align="center">amount</TableCell>
<TableCell align="center">items</TableCell>
</TableRow>
</TableHead>
<TableBody>
{orders.map(row => (
<TableRow
key={row.name}
sx={{
'&:last-of-type td, &:last-of-type th': {
border: 0,
},
}}
>
<TableCell component="th" scope="row">
<NotesIcon />
</TableCell>
{/* <TableCell align="right">{row.custom_id}</TableCell> */}
{/* <TableCell align="right">{row.username}</TableCell> */}
<TableCell align="right">{row.invoice_id}</TableCell>
<TableCell align="right">{row.currency_code}</TableCell>
<TableCell align="right">{row.order_status}</TableCell>
<TableCell align="right">{parseInt(row.amount).toFixed(2)}</TableCell>
<TableCell align="right">
{JSON.parse(row.items).map(i => (
<>
<Stack direction="column" spacing={'0.2rem'} alignItems={'flex-start'}>
<Typography variant="caption" fontWeight={'bold'}>
{i.item_name}
</Typography>
<Typography variant="caption" fontWeight={'bold'}>
{'QTY :' + i.quantity}
</Typography>
<Typography variant="caption" fontWeight={'bold'}>
{row.currency_code + ' ' + parseInt(i.unit_price).toFixed(2)}
</Typography>
</Stack>
</>
))}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
export default CustomerOrderTable;

View File

@@ -0,0 +1,13 @@
import { Stack, Typography } from '@mui/material';
const Loading = () => {
return (
<>
<Stack direction="column" height="50vh" justifyContent={'center'} alignItems={'center'}>
<Typography variant="h6">loading ...</Typography>
</Stack>
</>
);
};
export default Loading;

View File

@@ -0,0 +1,15 @@
import { Paper, Stack, TableContainer, Typography } from '@mui/material';
const NoOrders = () => {
return (
<>
<TableContainer component={Paper} sx={{ minWidth: '66vw', minHeight: '66vh' }}>
<Stack direction="column" height="50vh" justifyContent={'center'} alignItems={'center'}>
<Typography variant="h6">NoOrders ...</Typography>
</Stack>
</TableContainer>
</>
);
};
export default NoOrders;

View File

@@ -0,0 +1,16 @@
import { Box, Stack, Typography } from '@mui/material';
import SwapHorizontalCircleIcon from '@mui/icons-material/SwapHorizontalCircle';
const Redirecting = () => {
return (
<>
<Box>
<Stack minHeight={'50vh'} spacing="1rem" direction="column" justifyContent="center" alignItems="center">
<SwapHorizontalCircleIcon />
<Typography variant="h6">Redirecting ...</Typography>
</Stack>
</Box>
</>
);
};
export default Redirecting;

View File

@@ -0,0 +1,16 @@
const themeConfig = {
// ** Layout Configs
templateName: 'VTKH MALL' /* App Name */,
mode: 'light' /* light | dark */,
contentWidth: 'boxed' /* full | boxed */,
// ** Routing Configs
routingLoader: true /* true | false */,
// ** Navigation (Menu) Configs
menuTextTruncate: true /* true | false */,
navigationSize: 260 /* Number in PX(Pixels) /*! Note: This is for Vertical navigation menu only */,
// ** Other Configs
responsiveFontSizes: true /* true | false */,
disableRipple: false /* true | false */,
};
export default themeConfig;

View File

@@ -0,0 +1,99 @@
import { useRouter } from 'next/router';
import React, { createContext, useEffect, useState } from 'react';
import CheckSession from 'src/api/checkSession';
import Loading from 'src/components/Loading';
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [username, setUsername] = useState(null);
const router = useRouter();
const performLogin = ({ values }) => {
return fetch('/api/auth/login', { method: 'POST', body: JSON.stringify(values) })
.then(res => res.json())
.then(res_json => {
let { status, username: username_found } = res_json;
setUsername(username_found);
if (status == 'admin login OK') {
localStorage.setItem('session', JSON.stringify(res_json));
router.replace('/admin');
} else if (status == 'customer login OK') {
localStorage.setItem('session', JSON.stringify(res_json));
router.replace('/shopfront/customer/profile');
} else {
alert('Invalid username or password');
}
})
.catch(error => {
console.error(error);
});
};
const performLogout = () => {
setUsername(null);
localStorage.clear('session');
};
const performChangePassword = ({ old_password, new_password }) => {
return fetch('/api/auth/change_password', {
method: 'POST',
body: JSON.stringify({ username, old_password, new_password }),
})
.then(res => res.json())
.then(res_json => {
let { status } = res_json;
if (status == 'password change ok') {
performLogout();
router.replace('/shopfront');
} else {
alert('password change failed');
}
})
.catch(error => {
console.error(error);
});
};
let [loading, setLoading] = useState(true);
useEffect(() => {
let session_string = localStorage.getItem('session');
if (session_string) {
let { session } = JSON.parse(session_string);
CheckSession({ session }).then(data => {
let { username: username_found } = data;
setUsername(username_found);
});
setLoading(false);
} else {
setLoading(false);
router.replace('/shopfront');
}
}, []);
const helloworld = () => {
console.log('helloworld');
};
if (loading)
return (
<>
<Loading />
</>
);
return (
<AuthContext.Provider
value={{ username, setUsername, isAuthenticated, performLogin, performLogout, performChangePassword, helloworld }}
>
{children}
</AuthContext.Provider>
);
};
export { AuthContext, AuthProvider };

View File

@@ -0,0 +1,149 @@
import { useRouter } from 'next/router';
import React, { createContext, useEffect, useState } from 'react';
import checkoutCart from 'src/api/checkoutCart';
import fetchProducts from 'src/api/fetchProducts';
const CartContext = createContext();
const CartConsumer = CartContext.Consumer;
const CartProvider = ({ children }) => {
const [cart, setCart] = useState([]);
const [total_price, setTotalPrice] = useState(0);
const [products, setProducts] = useState([]);
const changeCartItemQty = (pid, quantity, unit_price) => {
if (quantity < 1) {
alert('please enter a number greater than 0');
} else {
const productExistInCart = cart.find(item => item.pid === pid);
if (productExistInCart) {
const max_item_allowed = products.filter(p => p.pid === pid)[0].count;
const item = cart.find(item => item.pid === pid);
const allow_to_add_item = quantity <= max_item_allowed;
if (allow_to_add_item) {
const final_item = quantity;
setCart(cart.map(item => (item.pid === pid ? { ...item, quantity: final_item, unit_price } : item)));
} else {
// display alert ?
alert('sorry but the item to add is over the max item allowed');
}
} else {
console.log('newly insert in cart');
setCart([...cart, { pid, quantity, unit_price }]);
}
}
};
const addToCart = (pid, quantity, unit_price) => {
if (cart) {
const productExistInCart = cart.find(item => item.pid === pid);
if (productExistInCart) {
const max_item_allowed = products.filter(p => p.pid === pid)[0].unsold_count;
// extract item from cart
const item = cart.find(item => item.pid === pid);
const allow_to_add_item = item.quantity + quantity <= max_item_allowed;
if (allow_to_add_item) {
const final_item = item.quantity + quantity;
setCart(cart.map(item => (item.pid === pid ? { ...item, quantity: final_item, unit_price } : item)));
} else {
// display alert ?
alert('sorry but the item to add is over the max item allowed');
}
} else {
setCart([...cart, { pid, quantity, unit_price }]);
}
} else {
setCart([{ pid, quantity, unit_price }]);
}
};
const emptyCart = () => {
setCart([]);
};
const proceedCheckOutCart = async () => {
try {
let { data } = await checkoutCart(cart);
if (data.status === 'OK') {
// clear cart after done
localStorage.setItem('cart', JSON.stringify([]));
} else {
console.error({ data });
throw new Error('error during checkout');
}
} catch (error) {
console.error(error);
console.error('checkout error');
}
};
// localStorage for saving and loading across sessions
// loading, on each fresh site arriving
useEffect(() => {
setCart(JSON.parse(localStorage.getItem('cart')));
}, []);
// saving, trigger on each cart update
useEffect(() => {
if (cart?.length > 0) {
let temp = 0;
cart.forEach(c_i => {
temp = temp + c_i.unit_price * c_i.quantity;
console.log(c_i.unit_price * c_i.quantity);
});
setTotalPrice(temp);
} else {
setTotalPrice(0);
}
localStorage.setItem('cart', JSON.stringify(cart));
}, [cart]);
const removeFromCart = pid => {
const productToRemove = cart.find(item => item.pid === pid);
if (productToRemove.quantity > 1) {
setCart(cart.map(item => (item.pid === pid ? { ...item, quantity: item.quantity - 1 } : item)));
} else {
setCart(cart.filter(item => item.pid !== pid));
}
};
useEffect(() => {
const update = async () => {
let temp = await fetchProducts();
setProducts(temp);
};
update();
}, []);
const helloworld = () => {
console.log('hello cart');
};
return (
<CartContext.Provider
value={{
addToCart,
removeFromCart,
helloworld,
emptyCart,
cart,
total_price,
changeCartItemQty,
products,
proceedCheckOutCart,
}}
>
{children}
</CartContext.Provider>
);
};
export { CartProvider, CartConsumer, CartContext };

View File

@@ -0,0 +1,70 @@
// ** MUI Imports
import Box from '@mui/material/Box';
import useMediaQuery from '@mui/material/useMediaQuery';
// ** Layout Imports
// !Do not remove this Layout import
import VerticalLayout from 'src/@core/layouts/VerticalLayout';
// ** Navigation Imports
import VerticalNavItems from 'src/navigation/vertical';
// ** Component Import
import UpgradeToProButton from './components/UpgradeToProButton';
import VerticalAppBarContent from './components/vertical/AppBarContent';
// ** Hook Import
import { useSettings } from 'src/@core/hooks/useSettings';
const UserLayout = ({ children }) => {
// ** Hooks
const { settings, saveSettings } = useSettings();
/**
* The below variable will hide the current layout menu at given screen size.
* The menu will be accessible from the Hamburger icon only (Vertical Overlay Menu).
* You can change the screen size from which you want to hide the current layout menu.
* Please refer useMediaQuery() hook: https://mui.com/components/use-media-query/,
* to know more about what values can be passed to this hook.
* ! Do not change this value unless you know what you are doing. It can break the template.
*/
const hidden = useMediaQuery(theme => theme.breakpoints.down('lg'));
const UpgradeToProImg = () => {
return (
<Box sx={{ mx: 'auto' }}>
<a
target="_blank"
rel="noreferrer"
href="https://themeselection.com/products/materio-mui-react-nextjs-admin-template/"
>
<img width={230} alt="upgrade to premium" src={`/images/misc/upgrade-banner-${settings.mode}.png`} />
</a>
</Box>
);
};
return (
<VerticalLayout
hidden={hidden}
settings={settings}
saveSettings={saveSettings}
verticalNavItems={VerticalNavItems()} // Navigation Items
verticalAppBarContent={(
props, // AppBar Content
) => (
<VerticalAppBarContent
hidden={hidden}
settings={settings}
saveSettings={saveSettings}
toggleNavVisibility={props.toggleNavVisibility}
/>
)}
>
{children}
{/* <UpgradeToProButton /> */}
</VerticalLayout>
);
};
export default UserLayout;

View File

@@ -0,0 +1,111 @@
// ** React Import
import { useState } from 'react';
// ** MUI Imports
import Box from '@mui/material/Box';
import Fade from '@mui/material/Fade';
import Paper from '@mui/material/Paper';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import CardContent from '@mui/material/CardContent';
// ** Third Party Imports
import { usePopper } from 'react-popper';
const BuyNowButton = () => {
// ** States
const [open, setOpen] = useState(false);
const [popperElement, setPopperElement] = useState(null);
const [referenceElement, setReferenceElement] = useState(null);
const { styles, attributes, update } = usePopper(referenceElement, popperElement, {
placement: 'top-end',
});
const handleOpen = () => {
setOpen(true);
update ? update() : null;
};
const handleClose = () => {
setOpen(false);
};
return (
<Box
className="upgrade-to-pro-button mui-fixed"
sx={{ right: theme => theme.spacing(20), bottom: theme => theme.spacing(10), zIndex: 11, position: 'fixed' }}
>
<Button
component="a"
target="_blank"
variant="contained"
onMouseEnter={handleOpen}
onMouseLeave={handleClose}
ref={e => setReferenceElement(e)}
href="https://themeselection.com/products/materio-mui-react-nextjs-admin-template/"
sx={{
backgroundColor: '#ff3e1d',
boxShadow: '0 1px 20px 1px #ff3e1d',
'&:hover': {
boxShadow: 'none',
backgroundColor: '#e6381a',
},
}}
>
Upgrade To Pro
</Button>
<Fade in={open} timeout={700}>
<Box
style={styles.popper}
ref={setPopperElement}
{...attributes.popper}
onMouseEnter={handleOpen}
onMouseLeave={handleClose}
sx={{ pb: 4, minWidth: theme => (theme.breakpoints.down('sm') ? 400 : 300) }}
>
<Paper elevation={9} sx={{ borderRadius: 1, overflow: 'hidden' }}>
<a
target="_blank"
rel="noreferrer"
href="https://themeselection.com/products/materio-mui-react-nextjs-admin-template/"
>
<img width="100%" alt="materio-pro-banner" src="/images/misc/materio-pro-banner.png" />
</a>
<CardContent>
<Typography sx={{ mb: 4 }} variant="h6">
Materio - React Admin Template
</Typography>
<Typography sx={{ mb: 4 }} variant="body2">
Materio Admin is the most developer friendly & highly customizable Admin Dashboard Template based on MUI
and NextJS.
</Typography>
<Typography sx={{ mb: 4 }} variant="body2">
Click on below buttons to explore PRO version.
</Typography>
<Button
component="a"
sx={{ mr: 4 }}
target="_blank"
variant="contained"
href="https://demos.themeselection.com/materio-mui-react-nextjs-admin-template/landing/"
>
Demo
</Button>
<Button
component="a"
target="_blank"
variant="outlined"
href="https://themeselection.com/products/materio-mui-react-nextjs-admin-template/"
>
Download
</Button>
</CardContent>
</Paper>
</Box>
</Fade>
</Box>
);
};
export default BuyNowButton;

View File

@@ -0,0 +1,15 @@
const UserIcon = props => {
// ** Props
const { icon, iconProps } = props;
const IconTag = icon;
let styles;
/* styles = {
color: 'red',
fontSize: '2rem'
} */
// @ts-ignore
return <IconTag {...iconProps} style={{ ...styles }} />;
};
export default UserIcon;

View File

@@ -0,0 +1,44 @@
// ** MUI Imports
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import IconButton from '@mui/material/IconButton';
import useMediaQuery from '@mui/material/useMediaQuery';
import InputAdornment from '@mui/material/InputAdornment';
// ** Icons Imports
import Menu from 'mdi-material-ui/Menu';
import Magnify from 'mdi-material-ui/Magnify';
// ** Components
import ModeToggler from 'src/@core/layouts/components/shared-components/ModeToggler';
import UserDropdown from 'src/@core/layouts/components/shared-components/UserDropdown';
import NotificationDropdown from 'src/@core/layouts/components/shared-components/NotificationDropdown';
const AppBarContent = props => {
// ** Props
const { hidden, settings, saveSettings, toggleNavVisibility } = props;
// ** Hook
const hiddenSm = useMediaQuery(theme => theme.breakpoints.down('sm'));
return (
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box className="actions-left" sx={{ mr: 2, display: 'flex', alignItems: 'center' }}>
{hidden ? (
<IconButton
color="inherit"
onClick={toggleNavVisibility}
sx={{ ml: -2.75, ...(hiddenSm ? {} : { mr: 3.5 }) }}
>
<Menu />
</IconButton>
) : null}
</Box>
<Box className="actions-right" sx={{ display: 'flex', alignItems: 'center' }}>
<UserDropdown />
</Box>
</Box>
);
};
export default AppBarContent;

View File

@@ -0,0 +1,27 @@
// ** Icon imports
import Login from 'mdi-material-ui/Login';
import Table from 'mdi-material-ui/Table';
import CubeOutline from 'mdi-material-ui/CubeOutline';
import HomeOutline from 'mdi-material-ui/HomeOutline';
import FormatLetterCase from 'mdi-material-ui/FormatLetterCase';
import AccountCogOutline from 'mdi-material-ui/AccountCogOutline';
import CreditCardOutline from 'mdi-material-ui/CreditCardOutline';
import AccountPlusOutline from 'mdi-material-ui/AccountPlusOutline';
import AlertCircleOutline from 'mdi-material-ui/AlertCircleOutline';
import GoogleCirclesExtended from 'mdi-material-ui/GoogleCirclesExtended';
const navigation = () => {
return [
{ title: 'Shop front', icon: HomeOutline, path: '/' },
{ sectionTitle: 'Testing' },
{ title: 'Categories', icon: Table, path: '/admin/categories' },
{ title: 'Products', icon: Table, path: '/admin/products' },
{ title: 'Inventory', icon: Table, path: '/admin/inventory' },
{ title: 'Order', icon: Table, path: '/admin/order' },
{ title: 'Documentation', icon: Table, path: '/admin/documentation' },
{ title: 'Logout', icon: Table, path: '/admin/logout' },
];
};
export default navigation;

View File

@@ -0,0 +1,61 @@
// ** Next Import
import Link from 'next/link';
// ** MUI Components
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import FooterIllustrations from 'src/views/pages/misc/FooterIllustrations';
// ** Styled Components
const BoxWrapper = styled(Box)(({ theme }) => ({
[theme.breakpoints.down('md')]: {
width: '90vw',
},
}));
const Img = styled('img')(({ theme }) => ({
marginBottom: theme.spacing(10),
[theme.breakpoints.down('lg')]: {
height: 450,
marginTop: theme.spacing(10),
},
[theme.breakpoints.down('md')]: {
height: 400,
},
[theme.breakpoints.up('lg')]: {
marginTop: theme.spacing(13),
},
}));
const Error401 = () => {
return (
<Box className="content-center">
<Box sx={{ p: 5, display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<BoxWrapper>
<Typography variant="h1">401</Typography>
<Typography variant="h5" sx={{ mb: 1, fontSize: '1.5rem !important' }}>
You are not authorized! 🔐
</Typography>
<Typography variant="body2">You don&prime;t have permission to access this page. Go Home!</Typography>
</BoxWrapper>
<Img height="487" alt="error-illustration" src="/images/pages/401.png" />
<Link passHref href="/">
<Button component="a" variant="contained" sx={{ px: 5.5 }}>
Back to Home
</Button>
</Link>
</Box>
<FooterIllustrations />
</Box>
);
};
Error401.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default Error401;

View File

@@ -0,0 +1,70 @@
// ** Next Import
import Link from 'next/link';
// ** MUI Components
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import FooterIllustrations from 'src/views/pages/misc/FooterIllustrations';
// ** Styled Components
const BoxWrapper = styled(Box)(({ theme }) => ({
[theme.breakpoints.down('md')]: {
width: '90vw',
},
}));
const Img = styled('img')(({ theme }) => ({
marginBottom: theme.spacing(10),
[theme.breakpoints.down('lg')]: {
height: 450,
marginTop: theme.spacing(10),
},
[theme.breakpoints.down('md')]: {
height: 400,
},
[theme.breakpoints.up('lg')]: {
marginTop: theme.spacing(13),
},
}));
const TreeIllustration = styled('img')(({ theme }) => ({
left: 0,
bottom: '5rem',
position: 'absolute',
[theme.breakpoints.down('lg')]: {
bottom: 0,
},
}));
const Error404 = () => {
return (
<Box className="content-center">
<Box sx={{ p: 5, display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<BoxWrapper>
<Typography variant="h1">404</Typography>
<Typography variant="h5" sx={{ mb: 1, fontSize: '1.5rem !important' }}>
Page Not Found
</Typography>
<Typography variant="body2">We couldn&prime;t find the page you are looking for.</Typography>
</BoxWrapper>
<Img height="487" alt="error-illustration" src="/images/pages/404.png" />
<Link passHref href="/">
<Button component="a" variant="contained" sx={{ px: 5.5 }}>
Back to Home
</Button>
</Link>
</Box>
<FooterIllustrations image={<TreeIllustration alt="tree" src="/images/pages/tree.png" />} />
</Box>
);
};
Error404.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default Error404;

View File

@@ -0,0 +1,70 @@
// ** Next Import
import Link from 'next/link';
// ** MUI Components
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import FooterIllustrations from 'src/views/pages/misc/FooterIllustrations';
// ** Styled Components
const BoxWrapper = styled(Box)(({ theme }) => ({
[theme.breakpoints.down('md')]: {
width: '90vw',
},
}));
const Img = styled('img')(({ theme }) => ({
marginBottom: theme.spacing(10),
[theme.breakpoints.down('lg')]: {
height: 450,
marginTop: theme.spacing(10),
},
[theme.breakpoints.down('md')]: {
height: 400,
},
[theme.breakpoints.up('lg')]: {
marginTop: theme.spacing(13),
},
}));
const TreeIllustration = styled('img')(({ theme }) => ({
left: 0,
bottom: '5rem',
position: 'absolute',
[theme.breakpoints.down('lg')]: {
bottom: 0,
},
}));
const Error500 = () => {
return (
<Box className="content-center">
<Box sx={{ p: 5, display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<BoxWrapper>
<Typography variant="h1">500</Typography>
<Typography variant="h5" sx={{ mb: 1, fontSize: '1.5rem !important' }}>
Internal server error 👨🏻💻
</Typography>
<Typography variant="body2">Oops, something went wrong!</Typography>
</BoxWrapper>
<Img height="487" alt="error-illustration" src="/images/pages/500.png" />
<Link passHref href="/">
<Button component="a" variant="contained" sx={{ px: 5.5 }}>
Back to Home
</Button>
</Link>
</Box>
<FooterIllustrations image={<TreeIllustration alt="tree" src="/images/pages/tree-3.png" />} />
</Box>
);
};
Error500.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default Error500;

View File

@@ -0,0 +1,78 @@
// ** Next Imports
import Head from 'next/head';
import { Router } from 'next/router';
// ** Loader Import
import NProgress from 'nprogress';
// ** Emotion Imports
import { CacheProvider } from '@emotion/react';
// ** Config Imports
import themeConfig from 'src/configs/themeConfig';
// ** Component Imports
import UserLayout from 'src/layouts/UserLayout';
import ThemeComponent from 'src/@core/theme/ThemeComponent';
// ** Contexts
import { SettingsConsumer, SettingsProvider } from 'src/@core/context/settingsContext';
// ** Utils Imports
import { createEmotionCache } from 'src/@core/utils/create-emotion-cache';
// ** React Perfect Scrollbar Style
import 'react-perfect-scrollbar/dist/css/styles.css';
// ** Global css styles
import '../../styles/globals.css';
import { AuthProvider } from 'src/contexts/auth';
const clientSideEmotionCache = createEmotionCache();
// ** Pace Loader
if (themeConfig.routingLoader) {
Router.events.on('routeChangeStart', () => {
NProgress.start();
});
Router.events.on('routeChangeError', () => {
NProgress.done();
});
Router.events.on('routeChangeComplete', () => {
NProgress.done();
});
}
// ** Configure JSS & ClassName
const App = props => {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
// Variables
const getLayout = Component.getLayout ?? (page => <UserLayout>{page}</UserLayout>);
return (
<CacheProvider value={emotionCache}>
<Head>
<title>{`${themeConfig.templateName} - Material Design React Admin Template`}</title>
<meta
name="description"
content={`${themeConfig.templateName} Material Design React Admin Dashboard Template is the most developer friendly & highly customizable Admin Dashboard Template based on MUI v5.`}
/>
<meta name="keywords" content="Material Design, MUI, Admin Template, React Admin Template" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<AuthProvider>
<SettingsProvider>
<SettingsConsumer>
{({ settings }) => {
return <ThemeComponent settings={settings}>{getLayout(<Component {...pageProps} />)}</ThemeComponent>;
}}
</SettingsConsumer>
</SettingsProvider>
</AuthProvider>
</CacheProvider>
);
};
export default App;

View File

@@ -0,0 +1,68 @@
// ** React Import
import { Children } from 'react';
// ** Next Import
import Document, { Html, Head, Main, NextScript } from 'next/document';
// ** Emotion Imports
import createEmotionServer from '@emotion/server/create-instance';
// ** Utils Imports
import { createEmotionCache } from 'src/@core/utils/create-emotion-cache';
class CustomDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
/>
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png" />
<link rel="shortcut icon" href="/images/favicon.png" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
CustomDocument.getInitialProps = async ctx => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props =>
(
<App
{...props} // @ts-ignore
emotionCache={cache}
/>
),
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(style => {
return (
<style
key={style.key}
dangerouslySetInnerHTML={{ __html: style.css }}
data-emotion={`${style.key} ${style.ids.join(' ')}`}
/>
);
});
return {
...initialProps,
styles: [...Children.toArray(initialProps.styles), ...emotionStyleTags],
};
};
export default CustomDocument;

Some files were not shown because too many files have changed in this diff Show More