Add UI and login screen
|
@ -23,6 +23,18 @@ jobs:
|
||||||
name: set Go path
|
name: set Go path
|
||||||
command: echo 'export PATH=$PATH:/usr/local/go/bin' >> $BASH_ENV
|
command: echo 'export PATH=$PATH:/usr/local/go/bin' >> $BASH_ENV
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: install Node 1/3
|
||||||
|
command: curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: install Node 2/3
|
||||||
|
command: sudo bash ./nodesource_setup.sh
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: install Node 3/3
|
||||||
|
command: sudo apt-get install -y nodejs
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Install GuPM
|
name: Install GuPM
|
||||||
command: curl -fsSL https://azukaar.github.io/GuPM/install.sh | bash
|
command: curl -fsSL https://azukaar.github.io/GuPM/install.sh | bash
|
||||||
|
@ -36,6 +48,11 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
command: ~/.gupm/gupm/g make
|
command: ~/.gupm/gupm/g make
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Build UI
|
||||||
|
command: g vite build
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Build Linux (ARM)
|
name: Build Linux (ARM)
|
||||||
command: ~/.gupm/gupm/g ci/build linux arm64
|
command: ~/.gupm/gupm/g ci/build linux arm64
|
||||||
|
|
2
.gitignore
vendored
|
@ -8,8 +8,10 @@ localcert.crt
|
||||||
localcert.key
|
localcert.key
|
||||||
.vite
|
.vite
|
||||||
dev.json
|
dev.json
|
||||||
|
static
|
||||||
.bin
|
.bin
|
||||||
client/dist
|
client/dist
|
||||||
|
client/.vite
|
||||||
config_dev.json
|
config_dev.json
|
||||||
tests
|
tests
|
||||||
todo.txt
|
todo.txt
|
||||||
|
|
25
client/TEMPLATE LICENSE
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Built from original template from CodedThemes.
|
||||||
|
The template was distributed with the licence.
|
||||||
|
This licence does not cover the changes made to the template:
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 CodedThemes
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
13
client/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Cosmos</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
18
client/src/App.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// project import
|
||||||
|
import Routes from './routes';
|
||||||
|
import ThemeCustomization from './themes';
|
||||||
|
import ScrollTop from './components/ScrollTop';
|
||||||
|
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== //
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeCustomization>
|
||||||
|
<ScrollTop>
|
||||||
|
<Routes />
|
||||||
|
</ScrollTop>
|
||||||
|
</ThemeCustomization>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
9
client/src/App.test.jsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
render(<App />);
|
||||||
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
26
client/src/api/authentication.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
function login(values) {
|
||||||
|
return fetch('/cosmos/api/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(values)
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
function me() {
|
||||||
|
return fetch('/cosmos/api/me/', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
login,
|
||||||
|
me
|
||||||
|
};
|
4
client/src/api/index.jsx
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import * as auth from './authentication.jsx';
|
||||||
|
export {
|
||||||
|
auth
|
||||||
|
};
|
18
client/src/assets/images/auth/AuthBackground.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
|
||||||
|
import logo from '../../../../../logo.png';
|
||||||
|
|
||||||
|
// ==============================|| AUTH BLUR BACK SVG ||============================== //
|
||||||
|
|
||||||
|
const AuthBackground = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'fixed', float: 'left', height: 'calc(100vh - 50px)', overflow: 'hidden', filter: 'blur(25px)', zIndex: -1, top: 100, left: -500 }}>
|
||||||
|
<img src={logo} style={{ display:'inline'}} alt="Cosmos" width="1100" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthBackground;
|
1
client/src/assets/images/icons/discord(1).svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>
|
After Width: | Height: | Size: 925 B |
1
client/src/assets/images/icons/discord.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>
|
After Width: | Height: | Size: 925 B |
3
client/src/assets/images/icons/facebook.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="9" height="16" viewBox="0 0 9 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.04102 0.125034C3.96989 0.125034 2.29102 1.80391 2.29102 3.87503V5.75003H0.0410156V8.75003H2.29102V15.875H5.29102V8.75003H7.91602L8.29102 5.75003H5.29102V4.25003C5.29102 3.42166 5.96264 2.75003 6.79102 2.75003H8.29102V0.245784C7.57514 0.171909 6.76139 0.123534 6.04102 0.125034Z" fill="#4267B2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 409 B |
6
client/src/assets/images/icons/google.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.6871 6.53113H15.083V6.5H8.33301V9.5H12.5716C11.9533 11.2464 10.2916 12.5 8.33301 12.5C5.84788 12.5 3.83301 10.4851 3.83301 8C3.83301 5.51487 5.84788 3.5 8.33301 3.5C9.48013 3.5 10.5238 3.93275 11.3184 4.63962L13.4398 2.51825C12.1003 1.26987 10.3085 0.5 8.33301 0.5C4.19113 0.5 0.833008 3.85812 0.833008 8C0.833008 12.1419 4.19113 15.5 8.33301 15.5C12.4749 15.5 15.833 12.1419 15.833 8C15.833 7.49713 15.7813 7.00625 15.6871 6.53113Z" fill="#FFC107"/>
|
||||||
|
<path d="M1.69824 4.50913L4.16237 6.31625C4.82912 4.6655 6.44387 3.5 8.33349 3.5C9.48062 3.5 10.5242 3.93275 11.3189 4.63963L13.4402 2.51825C12.1007 1.26988 10.309 0.5 8.33349 0.5C5.45274 0.5 2.95449 2.12638 1.69824 4.50913Z" fill="#FF3D00"/>
|
||||||
|
<path d="M8.33312 15.5C10.2704 15.5 12.0306 14.7586 13.3615 13.553L11.0402 11.5888C10.2872 12.1591 9.35125 12.5 8.33312 12.5C6.38237 12.5 4.726 11.2561 4.102 9.52026L1.65625 11.4046C2.8975 13.8335 5.41825 15.5 8.33312 15.5Z" fill="#4CAF50"/>
|
||||||
|
<path d="M15.6871 6.53113H15.083V6.5H8.33301V9.5H12.5716C12.2746 10.3389 11.735 11.0622 11.039 11.5891C11.0394 11.5887 11.0398 11.5887 11.0401 11.5884L13.3614 13.5526C13.1971 13.7019 15.833 11.75 15.833 8C15.833 7.49713 15.7813 7.00625 15.6871 6.53113Z" fill="#1976D2"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
client/src/assets/images/icons/twitter.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="13" viewBox="0 0 16 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.5 1.42863C14.9488 1.67278 14.3559 1.83568 13.7306 1.91276C14.3663 1.53529 14.8555 0.933256 15.085 0.222065C14.4901 0.570786 13.831 0.827014 13.1298 0.962003C12.5698 0.368303 11.7711 0 10.8862 0C9.18636 0 7.80856 1.36572 7.80856 3.04975C7.80856 3.28806 7.83647 3.52012 7.88897 3.74552C5.33168 3.6172 3.06354 2.40147 1.54616 0.55662C1.27952 1.00742 1.12953 1.53529 1.12953 2.09233C1.12953 3.15099 1.67157 4.08299 2.49817 4.63211C1.99363 4.6167 1.51866 4.47629 1.10287 4.25131C1.10287 4.26048 1.10287 4.27423 1.10287 4.28714C1.10287 5.7666 2.16403 6.99858 3.57058 7.27898C3.31352 7.34939 3.04187 7.38855 2.76189 7.38855C2.56316 7.38855 2.36943 7.36605 2.18194 7.33231C2.57358 8.54137 3.70973 9.42505 5.05587 9.4513C4.00262 10.2679 2.67607 10.757 1.23369 10.757C0.984543 10.757 0.740813 10.7429 0.5 10.7137C1.8628 11.5765 3.481 12.0823 5.21794 12.0823C10.8779 12.0823 13.9743 7.43438 13.9743 3.40222C13.9743 3.27014 13.9701 3.13849 13.9639 3.0085C14.568 2.58187 15.0888 2.04358 15.5 1.42863Z" fill="#03A9F4"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
client/src/assets/images/users/avatar-1.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
client/src/assets/images/users/avatar-2.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
client/src/assets/images/users/avatar-3.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
client/src/assets/images/users/avatar-4.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
client/src/assets/images/users/avatar-group.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
4
client/src/assets/third-party/apex-chart.css
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.apexcharts-legend-series .apexcharts-legend-marker {
|
||||||
|
left: -4px !important;
|
||||||
|
top: 2px !important;
|
||||||
|
}
|
29
client/src/components/@extended/AnimateButton.jsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
// ==============================|| ANIMATION BUTTON ||============================== //
|
||||||
|
|
||||||
|
export default function AnimateButton({ children, type }) {
|
||||||
|
switch (type) {
|
||||||
|
case 'rotate': // only available in paid version
|
||||||
|
case 'slide': // only available in paid version
|
||||||
|
case 'scale': // only available in paid version
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<motion.div whileHover={{ scale: 1 }} whileTap={{ scale: 0.9 }}>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimateButton.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
type: PropTypes.oneOf(['slide', 'scale', 'rotate'])
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimateButton.defaultProps = {
|
||||||
|
type: 'scale'
|
||||||
|
};
|
106
client/src/components/@extended/Breadcrumbs.jsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||||
|
import { Grid, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import MainCard from '../MainCard';
|
||||||
|
|
||||||
|
// ==============================|| BREADCRUMBS ||============================== //
|
||||||
|
|
||||||
|
const Breadcrumbs = ({ navigation, title, ...others }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const [main, setMain] = useState();
|
||||||
|
const [item, setItem] = useState();
|
||||||
|
|
||||||
|
// set active item state
|
||||||
|
const getCollapse = (menu) => {
|
||||||
|
if (menu.children) {
|
||||||
|
menu.children.filter((collapse) => {
|
||||||
|
if (collapse.type && collapse.type === 'collapse') {
|
||||||
|
getCollapse(collapse);
|
||||||
|
} else if (collapse.type && collapse.type === 'item') {
|
||||||
|
if (location.pathname === collapse.url) {
|
||||||
|
setMain(menu);
|
||||||
|
setItem(collapse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigation?.items?.map((menu) => {
|
||||||
|
if (menu.type && menu.type === 'group') {
|
||||||
|
getCollapse(menu);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// only used for component demo breadcrumbs
|
||||||
|
if (location.pathname === '/breadcrumbs') {
|
||||||
|
location.pathname = '/dashboard/analytics';
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainContent;
|
||||||
|
let itemContent;
|
||||||
|
let breadcrumbContent = <Typography />;
|
||||||
|
let itemTitle = '';
|
||||||
|
|
||||||
|
// collapse item
|
||||||
|
if (main && main.type === 'collapse') {
|
||||||
|
mainContent = (
|
||||||
|
<Typography component={Link} to={document.location.pathname} variant="h6" sx={{ textDecoration: 'none' }} color="textSecondary">
|
||||||
|
{main.title}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// items
|
||||||
|
if (item && item.type === 'item') {
|
||||||
|
itemTitle = item.title;
|
||||||
|
itemContent = (
|
||||||
|
<Typography variant="subtitle1" color="textPrimary">
|
||||||
|
{itemTitle}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
// main
|
||||||
|
if (item.breadcrumbs !== false) {
|
||||||
|
breadcrumbContent = (
|
||||||
|
<MainCard border={false} sx={{ mb: 3, bgcolor: 'transparent' }} {...others} content={false}>
|
||||||
|
<Grid container direction="column" justifyContent="flex-start" alignItems="flex-start" spacing={1}>
|
||||||
|
<Grid item>
|
||||||
|
<MuiBreadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography component={Link} to="/" color="textSecondary" variant="h6" sx={{ textDecoration: 'none' }}>
|
||||||
|
Home
|
||||||
|
</Typography>
|
||||||
|
{mainContent}
|
||||||
|
{itemContent}
|
||||||
|
</MuiBreadcrumbs>
|
||||||
|
</Grid>
|
||||||
|
{title && (
|
||||||
|
<Grid item sx={{ mt: 2 }}>
|
||||||
|
<Typography variant="h5">{item.title}</Typography>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return breadcrumbContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
Breadcrumbs.propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
title: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Breadcrumbs;
|
48
client/src/components/@extended/Dot.jsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
|
||||||
|
const Dot = ({ color, size }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
let main;
|
||||||
|
switch (color) {
|
||||||
|
case 'secondary':
|
||||||
|
main = theme.palette.secondary.main;
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
main = theme.palette.error.main;
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
main = theme.palette.warning.main;
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
main = theme.palette.info.main;
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
main = theme.palette.success.main;
|
||||||
|
break;
|
||||||
|
case 'primary':
|
||||||
|
default:
|
||||||
|
main = theme.palette.primary.main;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: size || 8,
|
||||||
|
height: size || 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
bgcolor: main
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Dot.propTypes = {
|
||||||
|
color: PropTypes.string,
|
||||||
|
size: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dot;
|
62
client/src/components/@extended/Transitions.jsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Fade, Box, Grow } from '@mui/material';
|
||||||
|
|
||||||
|
// ==============================|| TRANSITIONS ||============================== //
|
||||||
|
|
||||||
|
const Transitions = forwardRef(({ children, position, type, ...others }, ref) => {
|
||||||
|
let positionSX = {
|
||||||
|
transformOrigin: '0 0 0'
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'top-right':
|
||||||
|
case 'top':
|
||||||
|
case 'bottom-left':
|
||||||
|
case 'bottom-right':
|
||||||
|
case 'bottom':
|
||||||
|
case 'top-left':
|
||||||
|
default:
|
||||||
|
positionSX = {
|
||||||
|
transformOrigin: '0 0 0'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={ref}>
|
||||||
|
{type === 'grow' && (
|
||||||
|
<Grow {...others}>
|
||||||
|
<Box sx={positionSX}>{children}</Box>
|
||||||
|
</Grow>
|
||||||
|
)}
|
||||||
|
{type === 'fade' && (
|
||||||
|
<Fade
|
||||||
|
{...others}
|
||||||
|
timeout={{
|
||||||
|
appear: 0,
|
||||||
|
enter: 300,
|
||||||
|
exit: 150
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={positionSX}>{children}</Box>
|
||||||
|
</Fade>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Transitions.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
type: PropTypes.oneOf(['grow', 'fade', 'collapse', 'slide', 'zoom']),
|
||||||
|
position: PropTypes.oneOf(['top-left', 'top-right', 'top', 'bottom-left', 'bottom-right', 'bottom'])
|
||||||
|
};
|
||||||
|
|
||||||
|
Transitions.defaultProps = {
|
||||||
|
type: 'grow',
|
||||||
|
position: 'top-left'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Transitions;
|
15
client/src/components/Loadable.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Loader from './Loader';
|
||||||
|
|
||||||
|
// ==============================|| LOADABLE - LAZY LOADING ||============================== //
|
||||||
|
|
||||||
|
const Loadable = (Component) => (props) =>
|
||||||
|
(
|
||||||
|
<Suspense fallback={<Loader />}>
|
||||||
|
<Component {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Loadable;
|
25
client/src/components/Loader.jsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import LinearProgress from '@mui/material/LinearProgress';
|
||||||
|
|
||||||
|
// loader style
|
||||||
|
const LoaderWrapper = styled('div')(({ theme }) => ({
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 2001,
|
||||||
|
width: '100%',
|
||||||
|
'& > * + *': {
|
||||||
|
marginTop: theme.spacing(2)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==============================|| Loader ||============================== //
|
||||||
|
|
||||||
|
const Loader = () => (
|
||||||
|
<LoaderWrapper>
|
||||||
|
<LinearProgress color="primary" />
|
||||||
|
</LoaderWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Loader;
|
26
client/src/components/Logo/Logo.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { fontWeight } from '@mui/system';
|
||||||
|
|
||||||
|
import logo from '../../../../logo.png';
|
||||||
|
|
||||||
|
// ==============================|| LOGO SVG ||============================== //
|
||||||
|
|
||||||
|
const Logo = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
/**
|
||||||
|
* if you want to use image instead of svg uncomment following, and comment out <svg> element.
|
||||||
|
*
|
||||||
|
* <img src={logo} alt="Mantis" width="100" />
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
<>
|
||||||
|
<img src={logo} alt="Cosmos" width="50" />
|
||||||
|
<span style={{fontWeight: 'bold', fontSize: '170%', paddingLeft:'10px'}}> Cosmos</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Logo;
|
24
client/src/components/Logo/index.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { ButtonBase } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Logo from './Logo';
|
||||||
|
import config from '../../config';
|
||||||
|
|
||||||
|
// ==============================|| MAIN LOGO ||============================== //
|
||||||
|
|
||||||
|
const LogoSection = ({ sx, to }) => (
|
||||||
|
<ButtonBase disableRipple component={Link} to={!to ? config.defaultPath : to} sx={sx}>
|
||||||
|
<Logo />
|
||||||
|
</ButtonBase>
|
||||||
|
);
|
||||||
|
|
||||||
|
LogoSection.propTypes = {
|
||||||
|
sx: PropTypes.object,
|
||||||
|
to: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogoSection;
|
109
client/src/components/MainCard.jsx
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Highlighter from './third-party/Highlighter';
|
||||||
|
|
||||||
|
// header style
|
||||||
|
const headerSX = {
|
||||||
|
p: 2.5,
|
||||||
|
'& .MuiCardHeader-action': { m: '0px auto', alignSelf: 'center' }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| CUSTOM - MAIN CARD ||============================== //
|
||||||
|
|
||||||
|
const MainCard = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
border = true,
|
||||||
|
boxShadow,
|
||||||
|
children,
|
||||||
|
content = true,
|
||||||
|
contentSX = {},
|
||||||
|
darkTitle,
|
||||||
|
divider = true,
|
||||||
|
elevation,
|
||||||
|
secondary,
|
||||||
|
shadow,
|
||||||
|
sx = {},
|
||||||
|
title,
|
||||||
|
codeHighlight,
|
||||||
|
...others
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
elevation={elevation || 0}
|
||||||
|
ref={ref}
|
||||||
|
{...others}
|
||||||
|
sx={{
|
||||||
|
...sx,
|
||||||
|
border: border ? '1px solid' : 'none',
|
||||||
|
borderRadius: 2,
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? theme.palette.divider : theme.palette.grey.A800,
|
||||||
|
boxShadow: boxShadow && (!border || theme.palette.mode === 'dark') ? shadow || theme.customShadows.z1 : 'inherit',
|
||||||
|
':hover': {
|
||||||
|
boxShadow: boxShadow ? shadow || theme.customShadows.z1 : 'inherit'
|
||||||
|
},
|
||||||
|
'& pre': {
|
||||||
|
m: 0,
|
||||||
|
p: '16px !important',
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: '0.75rem'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* card header and action */}
|
||||||
|
{!darkTitle && title && (
|
||||||
|
<CardHeader sx={headerSX} titleTypographyProps={{ variant: 'subtitle1' }} title={title} action={secondary} />
|
||||||
|
)}
|
||||||
|
{darkTitle && title && (
|
||||||
|
<CardHeader sx={headerSX} title={<Typography variant="h3">{title}</Typography>} action={secondary} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* content & header divider */}
|
||||||
|
{title && divider && <Divider />}
|
||||||
|
|
||||||
|
{/* card content */}
|
||||||
|
{content && <CardContent sx={contentSX}>{children}</CardContent>}
|
||||||
|
{!content && children}
|
||||||
|
|
||||||
|
{/* card footer - clipboard & highlighter */}
|
||||||
|
{codeHighlight && (
|
||||||
|
<>
|
||||||
|
<Divider sx={{ borderStyle: 'dashed' }} />
|
||||||
|
<Highlighter codeHighlight={codeHighlight} main>
|
||||||
|
{children}
|
||||||
|
</Highlighter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
MainCard.propTypes = {
|
||||||
|
border: PropTypes.bool,
|
||||||
|
boxShadow: PropTypes.bool,
|
||||||
|
contentSX: PropTypes.object,
|
||||||
|
darkTitle: PropTypes.bool,
|
||||||
|
divider: PropTypes.bool,
|
||||||
|
elevation: PropTypes.number,
|
||||||
|
secondary: PropTypes.node,
|
||||||
|
shadow: PropTypes.string,
|
||||||
|
sx: PropTypes.object,
|
||||||
|
title: PropTypes.string,
|
||||||
|
codeHighlight: PropTypes.bool,
|
||||||
|
content: PropTypes.bool,
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainCard;
|
26
client/src/components/ScrollTop.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
// ==============================|| NAVIGATION - SCROLL TO TOP ||============================== //
|
||||||
|
|
||||||
|
const ScrollTop = ({ children }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return children || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScrollTop.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollTop;
|
22
client/src/components/cards/AuthFooter.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// material-ui
|
||||||
|
import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material';
|
||||||
|
|
||||||
|
// ==============================|| FOOTER - AUTHENTICATION ||============================== //
|
||||||
|
|
||||||
|
const AuthFooter = () => {
|
||||||
|
const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Stack
|
||||||
|
direction={matchDownSM ? 'column' : 'row'}
|
||||||
|
justifyContent={matchDownSM ? 'center' : 'space-between'}
|
||||||
|
spacing={2}
|
||||||
|
textAlign={matchDownSM ? 'center' : 'inherit'}
|
||||||
|
>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthFooter;
|
70
client/src/components/cards/statistics/AnalyticEcommerce.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, Chip, Grid, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../../components/MainCard';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { RiseOutlined, FallOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| STATISTICS - ECOMMERCE CARD ||============================== //
|
||||||
|
|
||||||
|
const AnalyticEcommerce = ({ color, title, count, percentage, isLoss, extra }) => (
|
||||||
|
<MainCard contentSX={{ p: 2.25 }}>
|
||||||
|
<Stack spacing={0.5}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Grid container alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h4" color="inherit">
|
||||||
|
{count}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
{percentage && (
|
||||||
|
<Grid item>
|
||||||
|
<Chip
|
||||||
|
variant="combined"
|
||||||
|
color={color}
|
||||||
|
icon={
|
||||||
|
<>
|
||||||
|
{!isLoss && <RiseOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />}
|
||||||
|
{isLoss && <FallOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
label={`${percentage}%`}
|
||||||
|
sx={{ ml: 1.25, pl: 1 }}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
<Box sx={{ pt: 2.25 }}>
|
||||||
|
<Typography variant="caption" color="textSecondary">
|
||||||
|
You made an extra{' '}
|
||||||
|
<Typography component="span" variant="caption" sx={{ color: `${color || 'primary'}.main` }}>
|
||||||
|
{extra}
|
||||||
|
</Typography>{' '}
|
||||||
|
this year
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
AnalyticEcommerce.propTypes = {
|
||||||
|
color: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
count: PropTypes.string,
|
||||||
|
percentage: PropTypes.number,
|
||||||
|
isLoss: PropTypes.bool,
|
||||||
|
extra: PropTypes.oneOfType([PropTypes.node, PropTypes.string])
|
||||||
|
};
|
||||||
|
|
||||||
|
AnalyticEcommerce.defaultProps = {
|
||||||
|
color: 'primary'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalyticEcommerce;
|
65
client/src/components/third-party/Highlighter.jsx
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, CardActions, Collapse, Divider, IconButton, Tooltip } from '@mui/material';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
|
import reactElementToJSXString from 'react-element-to-jsx-string';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import SyntaxHighlight from '../../utils/SyntaxHighlight';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { CodeOutlined, CopyOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| CLIPBOARD & HIGHLIGHTER ||============================== //
|
||||||
|
|
||||||
|
const Highlighter = ({ children }) => {
|
||||||
|
const [highlight, setHighlight] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'relative' }}>
|
||||||
|
<CardActions sx={{ justifyContent: 'flex-end', p: 1, mb: highlight ? 1 : 0 }}>
|
||||||
|
<Box sx={{ display: 'flex', position: 'inherit', right: 0, top: 6 }}>
|
||||||
|
<CopyToClipboard text={reactElementToJSXString(children, { showFunctions: true, maxInlineAttributesLineLength: 100 })}>
|
||||||
|
<Tooltip title="Copy the source" placement="top-end">
|
||||||
|
<IconButton color="secondary" size="small" sx={{ fontSize: '0.875rem' }}>
|
||||||
|
<CopyOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</CopyToClipboard>
|
||||||
|
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} />
|
||||||
|
<Tooltip title="Show the source" placement="top-end">
|
||||||
|
<IconButton
|
||||||
|
sx={{ fontSize: '0.875rem' }}
|
||||||
|
size="small"
|
||||||
|
color={highlight ? 'primary' : 'secondary'}
|
||||||
|
onClick={() => setHighlight(!highlight)}
|
||||||
|
>
|
||||||
|
<CodeOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</CardActions>
|
||||||
|
<Collapse in={highlight}>
|
||||||
|
{highlight && (
|
||||||
|
<SyntaxHighlight>
|
||||||
|
{reactElementToJSXString(children, {
|
||||||
|
showFunctions: true,
|
||||||
|
showDefaultProps: false,
|
||||||
|
maxInlineAttributesLineLength: 100
|
||||||
|
})}
|
||||||
|
</SyntaxHighlight>
|
||||||
|
)}
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Highlighter.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Highlighter;
|
62
client/src/components/third-party/SimpleBar.jsx
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { alpha, styled } from '@mui/material/styles';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import SimpleBar from 'simplebar-react';
|
||||||
|
import { BrowserView, MobileView } from 'react-device-detect';
|
||||||
|
|
||||||
|
// root style
|
||||||
|
const RootStyle = styled(BrowserView)({
|
||||||
|
flexGrow: 1,
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'hidden'
|
||||||
|
});
|
||||||
|
|
||||||
|
// scroll bar wrapper
|
||||||
|
const SimpleBarStyle = styled(SimpleBar)(({ theme }) => ({
|
||||||
|
maxHeight: '100%',
|
||||||
|
'& .simplebar-scrollbar': {
|
||||||
|
'&:before': {
|
||||||
|
backgroundColor: alpha(theme.palette.grey[500], 0.48)
|
||||||
|
},
|
||||||
|
'&.simplebar-visible:before': {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .simplebar-track.simplebar-vertical': {
|
||||||
|
width: 10
|
||||||
|
},
|
||||||
|
'& .simplebar-track.simplebar-horizontal .simplebar-scrollbar': {
|
||||||
|
height: 6
|
||||||
|
},
|
||||||
|
'& .simplebar-mask': {
|
||||||
|
zIndex: 'inherit'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==============================|| SIMPLE SCROLL BAR ||============================== //
|
||||||
|
|
||||||
|
export default function SimpleBarScroll({ children, sx, ...other }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RootStyle>
|
||||||
|
<SimpleBarStyle timeout={500} clickOnTrack={false} sx={sx} {...other}>
|
||||||
|
{children}
|
||||||
|
</SimpleBarStyle>
|
||||||
|
</RootStyle>
|
||||||
|
<MobileView>
|
||||||
|
<Box sx={{ overflowX: 'auto', ...sx }} {...other}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</MobileView>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleBarScroll.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
sx: PropTypes.object
|
||||||
|
};
|
19
client/src/config.jsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// ==============================|| THEME CONFIG ||============================== //
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
defaultPath: '/dashboard/default',
|
||||||
|
fontFamily: `'Public Sans', sans-serif`,
|
||||||
|
i18n: 'en',
|
||||||
|
miniDrawer: false,
|
||||||
|
container: true,
|
||||||
|
mode: 'light',
|
||||||
|
presetColor: 'default',
|
||||||
|
themeDirection: 'ltr'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
export const drawerWidth = 260;
|
||||||
|
|
||||||
|
export const twitterColor = '#1DA1F2';
|
||||||
|
export const facebookColor = '#3b5998';
|
||||||
|
export const linkedInColor = '#0e76a8';
|
36
client/src/index.jsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { StrictMode } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
// scroll bar
|
||||||
|
import 'simplebar/src/simplebar.css';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
|
|
||||||
|
// apex-chart
|
||||||
|
import './assets/third-party/apex-chart.css';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import App from './App';
|
||||||
|
import { store } from './store';
|
||||||
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
// ==============================|| MAIN - REACT DOM RENDER ||============================== //
|
||||||
|
|
||||||
|
const container = document.getElementById('root');
|
||||||
|
const root = createRoot(container); // createRoot(container!) if you use TypeScript
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<BrowserRouter basename="/">
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</ReduxProvider>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
|
reportWebVitals();
|
13
client/src/isLoggedIn.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
import * as API from './api';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const isLoggedIn = () => useEffect(() => {
|
||||||
|
API.auth.me().then((data) => {
|
||||||
|
if(data.status != 'OK') {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isLoggedIn;
|
|
@ -0,0 +1,31 @@
|
||||||
|
// material-ui
|
||||||
|
import { Button, CardMedia, Link, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../../../components/MainCard';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import avatar from '../../../../assets/images/users/avatar-group.png';
|
||||||
|
import AnimateButton from '../../../../components/@extended/AnimateButton';
|
||||||
|
|
||||||
|
// ==============================|| DRAWER CONTENT - NAVIGATION CARD ||============================== //
|
||||||
|
|
||||||
|
const NavCard = () => (
|
||||||
|
<MainCard sx={{ bgcolor: 'grey.50', m: 3 }}>
|
||||||
|
<Stack alignItems="center" spacing={2.5}>
|
||||||
|
<Stack alignItems="center">
|
||||||
|
<Typography variant="h5">Cosmos Pro</Typography>
|
||||||
|
<Typography variant="h6" color="secondary">
|
||||||
|
Checkout pro features
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<AnimateButton>
|
||||||
|
<Button component={Link} target="_blank" href="https://mantisdashboard.io" variant="contained" color="success" size="small">
|
||||||
|
Pro
|
||||||
|
</Button>
|
||||||
|
</AnimateButton>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default NavCard;
|
|
@ -0,0 +1,59 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, List, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import NavItem from './NavItem';
|
||||||
|
|
||||||
|
// ==============================|| NAVIGATION - LIST GROUP ||============================== //
|
||||||
|
|
||||||
|
const NavGroup = ({ item }) => {
|
||||||
|
const menu = useSelector((state) => state.menu);
|
||||||
|
const { drawerOpen } = menu;
|
||||||
|
|
||||||
|
const navCollapse = item.children?.map((menuItem) => {
|
||||||
|
switch (menuItem.type) {
|
||||||
|
case 'collapse':
|
||||||
|
return (
|
||||||
|
<Typography key={menuItem.id} variant="caption" color="error" sx={{ p: 2.5 }}>
|
||||||
|
collapse - only available in paid version
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
case 'item':
|
||||||
|
return <NavItem key={menuItem.id} item={menuItem} level={1} />;
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Typography key={menuItem.id} variant="h6" color="error" align="center">
|
||||||
|
Fix - Group Collapse or Items
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
subheader={
|
||||||
|
item.title &&
|
||||||
|
drawerOpen && (
|
||||||
|
<Box sx={{ pl: 3, mb: 1.5 }}>
|
||||||
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
|
{item.title}
|
||||||
|
</Typography>
|
||||||
|
{/* only available in paid version */}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{ mb: drawerOpen ? 1.5 : 0, py: 0, zIndex: 0 }}
|
||||||
|
>
|
||||||
|
{navCollapse}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NavGroup.propTypes = {
|
||||||
|
item: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavGroup;
|
|
@ -0,0 +1,146 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { forwardRef, useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import { activeItem } from '../../../../../store/reducers/menu';
|
||||||
|
|
||||||
|
// ==============================|| NAVIGATION - LIST ITEM ||============================== //
|
||||||
|
|
||||||
|
const NavItem = ({ item, level }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const menu = useSelector((state) => state.menu);
|
||||||
|
const { drawerOpen, openItem } = menu;
|
||||||
|
|
||||||
|
let itemTarget = '_self';
|
||||||
|
if (item.target) {
|
||||||
|
itemTarget = '_blank';
|
||||||
|
}
|
||||||
|
|
||||||
|
let listItemProps = { component: forwardRef((props, ref) => <Link ref={ref} {...props} to={item.url} target={itemTarget} />) };
|
||||||
|
if (item?.external) {
|
||||||
|
listItemProps = { component: 'a', href: item.url, target: itemTarget };
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemHandler = (id) => {
|
||||||
|
dispatch(activeItem({ openItem: [id] }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const Icon = item.icon;
|
||||||
|
const itemIcon = item.icon ? <Icon style={{ fontSize: drawerOpen ? '1rem' : '1.25rem' }} /> : false;
|
||||||
|
|
||||||
|
const isSelected = openItem.findIndex((id) => id === item.id) > -1;
|
||||||
|
|
||||||
|
// active menu item on page load
|
||||||
|
useEffect(() => {
|
||||||
|
const currentIndex = document.location.pathname
|
||||||
|
.toString()
|
||||||
|
.split('/')
|
||||||
|
.findIndex((id) => id === item.id);
|
||||||
|
if (currentIndex > -1) {
|
||||||
|
dispatch(activeItem({ openItem: [item.id] }));
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const textColor = 'text.primary';
|
||||||
|
const iconSelectedColor = 'primary.main';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItemButton
|
||||||
|
{...listItemProps}
|
||||||
|
disabled={item.disabled}
|
||||||
|
onClick={() => itemHandler(item.id)}
|
||||||
|
selected={isSelected}
|
||||||
|
sx={{
|
||||||
|
zIndex: 1201,
|
||||||
|
pl: drawerOpen ? `${level * 28}px` : 1.5,
|
||||||
|
py: !drawerOpen && level === 1 ? 1.25 : 1,
|
||||||
|
...(drawerOpen && {
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'primary.lighter'
|
||||||
|
},
|
||||||
|
'&.Mui-selected': {
|
||||||
|
bgcolor: 'primary.lighter',
|
||||||
|
borderRight: `2px solid ${theme.palette.primary.main}`,
|
||||||
|
color: iconSelectedColor,
|
||||||
|
'&:hover': {
|
||||||
|
color: iconSelectedColor,
|
||||||
|
bgcolor: 'primary.lighter'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...(!drawerOpen && {
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'transparent'
|
||||||
|
},
|
||||||
|
'&.Mui-selected': {
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'transparent'
|
||||||
|
},
|
||||||
|
bgcolor: 'transparent'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{itemIcon && (
|
||||||
|
<ListItemIcon
|
||||||
|
sx={{
|
||||||
|
minWidth: 28,
|
||||||
|
color: isSelected ? iconSelectedColor : textColor,
|
||||||
|
...(!drawerOpen && {
|
||||||
|
borderRadius: 1.5,
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'secondary.lighter'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...(!drawerOpen &&
|
||||||
|
isSelected && {
|
||||||
|
bgcolor: 'primary.lighter',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'primary.lighter'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{itemIcon}
|
||||||
|
</ListItemIcon>
|
||||||
|
)}
|
||||||
|
{(drawerOpen || (!drawerOpen && level !== 1)) && (
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="h6" sx={{ color: isSelected ? iconSelectedColor : textColor }}>
|
||||||
|
{item.title}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(drawerOpen || (!drawerOpen && level !== 1)) && item.chip && (
|
||||||
|
<Chip
|
||||||
|
color={item.chip.color}
|
||||||
|
variant={item.chip.variant}
|
||||||
|
size={item.chip.size}
|
||||||
|
label={item.chip.label}
|
||||||
|
avatar={item.chip.avatar && <Avatar>{item.chip.avatar}</Avatar>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NavItem.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
level: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavItem;
|
|
@ -0,0 +1,27 @@
|
||||||
|
// material-ui
|
||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import NavGroup from './NavGroup';
|
||||||
|
import menuItem from '../../../../../menu-items';
|
||||||
|
|
||||||
|
// ==============================|| DRAWER CONTENT - NAVIGATION ||============================== //
|
||||||
|
|
||||||
|
const Navigation = () => {
|
||||||
|
const navGroups = menuItem.items.map((item) => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'group':
|
||||||
|
return <NavGroup key={item.id} item={item} />;
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Typography key={item.id} variant="h6" color="error" align="center">
|
||||||
|
Fix - Navigation Group
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Box sx={{ pt: 2 }}>{navGroups}</Box>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navigation;
|
22
client/src/layout/MainLayout/Drawer/DrawerContent/index.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// project import
|
||||||
|
import NavCard from './NavCard';
|
||||||
|
import Navigation from './Navigation';
|
||||||
|
import SimpleBar from '../../../../components/third-party/SimpleBar';
|
||||||
|
|
||||||
|
// ==============================|| DRAWER CONTENT ||============================== //
|
||||||
|
|
||||||
|
const DrawerContent = () => (
|
||||||
|
<SimpleBar
|
||||||
|
sx={{
|
||||||
|
'& .simplebar-content': {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Navigation />
|
||||||
|
{/* <NavCard /> */}
|
||||||
|
</SimpleBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DrawerContent;
|
|
@ -0,0 +1,15 @@
|
||||||
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
|
||||||
|
// ==============================|| DRAWER HEADER - STYLED ||============================== //
|
||||||
|
|
||||||
|
const DrawerHeaderStyled = styled(Box, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
||||||
|
...theme.mixins.toolbar,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: open ? 'flex-start' : 'center',
|
||||||
|
paddingLeft: theme.spacing(open ? 3 : 0)
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default DrawerHeaderStyled;
|
38
client/src/layout/MainLayout/Drawer/DrawerHeader/index.jsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Stack, Chip } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import DrawerHeaderStyled from './DrawerHeaderStyled';
|
||||||
|
import Logo from '../../../../components/Logo';
|
||||||
|
import {version} from '../../../../../../gupm.json';
|
||||||
|
|
||||||
|
// ==============================|| DRAWER HEADER ||============================== //
|
||||||
|
|
||||||
|
const DrawerHeader = ({ open }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DrawerHeaderStyled theme={theme} open={open}>
|
||||||
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
|
<Logo />
|
||||||
|
<Chip
|
||||||
|
label={version}
|
||||||
|
size="small"
|
||||||
|
sx={{ height: 16, '& .MuiChip-label': { fontSize: '0.625rem', py: 0.25 } }}
|
||||||
|
component="a"
|
||||||
|
href="/"
|
||||||
|
clickable
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</DrawerHeaderStyled>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DrawerHeader.propTypes = {
|
||||||
|
open: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrawerHeader;
|
47
client/src/layout/MainLayout/Drawer/MiniDrawerStyled.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import Drawer from '@mui/material/Drawer';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import { drawerWidth } from '../../../config';
|
||||||
|
|
||||||
|
const openedMixin = (theme) => ({
|
||||||
|
width: drawerWidth,
|
||||||
|
borderRight: `1px solid ${theme.palette.divider}`,
|
||||||
|
transition: theme.transitions.create('width', {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.enteringScreen
|
||||||
|
}),
|
||||||
|
overflowX: 'hidden',
|
||||||
|
boxShadow: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
const closedMixin = (theme) => ({
|
||||||
|
transition: theme.transitions.create('width', {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen
|
||||||
|
}),
|
||||||
|
overflowX: 'hidden',
|
||||||
|
width: 0,
|
||||||
|
borderRight: 'none',
|
||||||
|
boxShadow: theme.customShadows.z1
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==============================|| DRAWER - MINI STYLED ||============================== //
|
||||||
|
|
||||||
|
const MiniDrawerStyled = styled(Drawer, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
...(open && {
|
||||||
|
...openedMixin(theme),
|
||||||
|
'& .MuiDrawer-paper': openedMixin(theme)
|
||||||
|
}),
|
||||||
|
...(!open && {
|
||||||
|
...closedMixin(theme),
|
||||||
|
'& .MuiDrawer-paper': closedMixin(theme)
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default MiniDrawerStyled;
|
66
client/src/layout/MainLayout/Drawer/index.jsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Box, Drawer, useMediaQuery } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import DrawerHeader from './DrawerHeader';
|
||||||
|
import DrawerContent from './DrawerContent';
|
||||||
|
import MiniDrawerStyled from './MiniDrawerStyled';
|
||||||
|
import { drawerWidth } from '../../../config';
|
||||||
|
|
||||||
|
// ==============================|| MAIN LAYOUT - DRAWER ||============================== //
|
||||||
|
|
||||||
|
const MainDrawer = ({ open, handleDrawerToggle, window }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
|
||||||
|
// responsive drawer container
|
||||||
|
const container = window !== undefined ? () => window().document.body : undefined;
|
||||||
|
|
||||||
|
// header content
|
||||||
|
const drawerContent = useMemo(() => <DrawerContent />, []);
|
||||||
|
const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label="mailbox folders">
|
||||||
|
{!matchDownMD ? (
|
||||||
|
<MiniDrawerStyled variant="permanent" open={open}>
|
||||||
|
{drawerHeader}
|
||||||
|
{drawerContent}
|
||||||
|
</MiniDrawerStyled>
|
||||||
|
) : (
|
||||||
|
<Drawer
|
||||||
|
container={container}
|
||||||
|
variant="temporary"
|
||||||
|
open={open}
|
||||||
|
onClose={handleDrawerToggle}
|
||||||
|
ModalProps={{ keepMounted: true }}
|
||||||
|
sx={{
|
||||||
|
display: { xs: 'block', lg: 'none' },
|
||||||
|
'& .MuiDrawer-paper': {
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
width: drawerWidth,
|
||||||
|
borderRight: `1px solid ${theme.palette.divider}`,
|
||||||
|
backgroundImage: 'none',
|
||||||
|
boxShadow: 'inherit'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{open && drawerHeader}
|
||||||
|
{open && drawerContent}
|
||||||
|
</Drawer>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MainDrawer.propTypes = {
|
||||||
|
open: PropTypes.bool,
|
||||||
|
handleDrawerToggle: PropTypes.func,
|
||||||
|
window: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainDrawer;
|
26
client/src/layout/MainLayout/Header/AppBarStyled.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import AppBar from '@mui/material/AppBar';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import { drawerWidth } from '../../../config';
|
||||||
|
|
||||||
|
// ==============================|| HEADER - APP BAR STYLED ||============================== //
|
||||||
|
|
||||||
|
const AppBarStyled = styled(AppBar, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
||||||
|
zIndex: theme.zIndex.drawer + 1,
|
||||||
|
transition: theme.transitions.create(['width', 'margin'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen
|
||||||
|
}),
|
||||||
|
...(open && {
|
||||||
|
marginLeft: drawerWidth,
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
|
transition: theme.transitions.create(['width', 'margin'], {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.enteringScreen
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default AppBarStyled;
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { AppBar, Box, ClickAwayListener, IconButton, Paper, Popper, Toolbar } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Search from './Search';
|
||||||
|
import Profile from './Profile';
|
||||||
|
import Transitions from '../../../../components/@extended/Transitions';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { MoreOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| HEADER CONTENT - MOBILE ||============================== //
|
||||||
|
|
||||||
|
const MobileSection = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const anchorRef = useRef(null);
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
setOpen((prevOpen) => !prevOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (event) => {
|
||||||
|
if (anchorRef.current && anchorRef.current.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevOpen = useRef(open);
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevOpen.current === true && open === false) {
|
||||||
|
anchorRef.current.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
prevOpen.current = open;
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ flexShrink: 0, ml: 0.75 }}>
|
||||||
|
<IconButton
|
||||||
|
component="span"
|
||||||
|
disableRipple
|
||||||
|
sx={{
|
||||||
|
bgcolor: open ? 'grey.300' : 'grey.100'
|
||||||
|
}}
|
||||||
|
ref={anchorRef}
|
||||||
|
aria-controls={open ? 'menu-list-grow' : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleToggle}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<MoreOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Popper
|
||||||
|
placement="bottom-end"
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorRef.current}
|
||||||
|
role={undefined}
|
||||||
|
transition
|
||||||
|
disablePortal
|
||||||
|
style={{
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
popperOptions={{
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
options: {
|
||||||
|
offset: [0, 9]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ TransitionProps }) => (
|
||||||
|
<Transitions type="fade" in={open} {...TransitionProps}>
|
||||||
|
<Paper sx={{ boxShadow: theme.customShadows.z1 }}>
|
||||||
|
<ClickAwayListener onClickAway={handleClose}>
|
||||||
|
<AppBar color="inherit">
|
||||||
|
<Toolbar>
|
||||||
|
<Search />
|
||||||
|
<Profile />
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Paper>
|
||||||
|
</Transitions>
|
||||||
|
)}
|
||||||
|
</Popper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MobileSection;
|
|
@ -0,0 +1,278 @@
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
ClickAwayListener,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemText,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
Paper,
|
||||||
|
Popper,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../../../components/MainCard';
|
||||||
|
import Transitions from '../../../../components/@extended/Transitions';
|
||||||
|
// assets
|
||||||
|
import { BellOutlined, CloseOutlined, GiftOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// sx styles
|
||||||
|
const avatarSX = {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
fontSize: '1rem'
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionSX = {
|
||||||
|
mt: '6px',
|
||||||
|
ml: 1,
|
||||||
|
top: 'auto',
|
||||||
|
right: 'auto',
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
|
||||||
|
transform: 'none'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| HEADER CONTENT - NOTIFICATION ||============================== //
|
||||||
|
|
||||||
|
const Notification = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchesXs = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
const anchorRef = useRef(null);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleToggle = () => {
|
||||||
|
setOpen((prevOpen) => !prevOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (event) => {
|
||||||
|
if (anchorRef.current && anchorRef.current.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconBackColorOpen = 'grey.300';
|
||||||
|
const iconBackColor = 'grey.100';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ flexShrink: 0, ml: 0.75 }}>
|
||||||
|
<IconButton
|
||||||
|
disableRipple
|
||||||
|
color="secondary"
|
||||||
|
sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }}
|
||||||
|
aria-label="open profile"
|
||||||
|
ref={anchorRef}
|
||||||
|
aria-controls={open ? 'profile-grow' : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleToggle}
|
||||||
|
>
|
||||||
|
<Badge badgeContent={4} color="primary">
|
||||||
|
<BellOutlined />
|
||||||
|
</Badge>
|
||||||
|
</IconButton>
|
||||||
|
<Popper
|
||||||
|
placement={matchesXs ? 'bottom' : 'bottom-end'}
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorRef.current}
|
||||||
|
role={undefined}
|
||||||
|
transition
|
||||||
|
disablePortal
|
||||||
|
popperOptions={{
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
options: {
|
||||||
|
offset: [matchesXs ? -5 : 0, 9]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ TransitionProps }) => (
|
||||||
|
<Transitions type="fade" in={open} {...TransitionProps}>
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
boxShadow: theme.customShadows.z1,
|
||||||
|
width: '100%',
|
||||||
|
minWidth: 285,
|
||||||
|
maxWidth: 420,
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
maxWidth: 285
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClickAwayListener onClickAway={handleClose}>
|
||||||
|
<MainCard
|
||||||
|
title="Notification"
|
||||||
|
elevation={0}
|
||||||
|
border={false}
|
||||||
|
content={false}
|
||||||
|
secondary={
|
||||||
|
<IconButton size="small" onClick={handleToggle}>
|
||||||
|
<CloseOutlined />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
component="nav"
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
'& .MuiListItemButton-root': {
|
||||||
|
py: 0.5,
|
||||||
|
'& .MuiAvatar-root': avatarSX,
|
||||||
|
'& .MuiListItemSecondaryAction-root': { ...actionSX, position: 'relative' }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'success.main',
|
||||||
|
bgcolor: 'success.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GiftOutlined />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="h6">
|
||||||
|
It's{' '}
|
||||||
|
<Typography component="span" variant="subtitle1">
|
||||||
|
Cristina danny's
|
||||||
|
</Typography>{' '}
|
||||||
|
birthday today.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary="2 min ago"
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Typography variant="caption" noWrap>
|
||||||
|
3:00 AM
|
||||||
|
</Typography>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
<Divider />
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
bgcolor: 'primary.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MessageOutlined />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="h6">
|
||||||
|
<Typography component="span" variant="subtitle1">
|
||||||
|
Aida Burg
|
||||||
|
</Typography>{' '}
|
||||||
|
commented your post.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary="5 August"
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Typography variant="caption" noWrap>
|
||||||
|
6:00 PM
|
||||||
|
</Typography>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
<Divider />
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'error.main',
|
||||||
|
bgcolor: 'error.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingOutlined />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="h6">
|
||||||
|
Your Profile is Complete
|
||||||
|
<Typography component="span" variant="subtitle1">
|
||||||
|
60%
|
||||||
|
</Typography>{' '}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary="7 hours ago"
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Typography variant="caption" noWrap>
|
||||||
|
2:45 PM
|
||||||
|
</Typography>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
<Divider />
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
bgcolor: 'primary.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
C
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="h6">
|
||||||
|
<Typography component="span" variant="subtitle1">
|
||||||
|
Cristina Danny
|
||||||
|
</Typography>{' '}
|
||||||
|
invited to join{' '}
|
||||||
|
<Typography component="span" variant="subtitle1">
|
||||||
|
Meeting.
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary="Daily scrum meeting time"
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Typography variant="caption" noWrap>
|
||||||
|
9:10 PM
|
||||||
|
</Typography>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
<Divider />
|
||||||
|
<ListItemButton sx={{ textAlign: 'center', py: `${12}px !important` }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="h6" color="primary">
|
||||||
|
View All
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
</MainCard>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Paper>
|
||||||
|
</Transitions>
|
||||||
|
)}
|
||||||
|
</Popper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification;
|
|
@ -0,0 +1,62 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { EditOutlined, ProfileOutlined, LogoutOutlined, UserOutlined, WalletOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| HEADER PROFILE - PROFILE TAB ||============================== //
|
||||||
|
|
||||||
|
const ProfileTab = ({ handleLogout }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
const handleListItemClick = (event, index) => {
|
||||||
|
setSelectedIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List component="nav" sx={{ p: 0, '& .MuiListItemIcon-root': { minWidth: 32, color: theme.palette.grey[500] } }}>
|
||||||
|
<ListItemButton selected={selectedIndex === 0} onClick={(event) => handleListItemClick(event, 0)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<EditOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Edit Profile" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 1} onClick={(event) => handleListItemClick(event, 1)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<UserOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="View Profile" />
|
||||||
|
</ListItemButton>
|
||||||
|
|
||||||
|
<ListItemButton selected={selectedIndex === 3} onClick={(event) => handleListItemClick(event, 3)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ProfileOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Social Profile" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 4} onClick={(event) => handleListItemClick(event, 4)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<WalletOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Billing" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 2} onClick={handleLogout}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LogoutOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Logout" />
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProfileTab.propTypes = {
|
||||||
|
handleLogout: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileTab;
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { CommentOutlined, LockOutlined, QuestionCircleOutlined, UserOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| HEADER PROFILE - SETTING TAB ||============================== //
|
||||||
|
|
||||||
|
const SettingTab = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
const handleListItemClick = (event, index) => {
|
||||||
|
setSelectedIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List component="nav" sx={{ p: 0, '& .MuiListItemIcon-root': { minWidth: 32, color: theme.palette.grey[500] } }}>
|
||||||
|
<ListItemButton selected={selectedIndex === 0} onClick={(event) => handleListItemClick(event, 0)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Support" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 1} onClick={(event) => handleListItemClick(event, 1)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<UserOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Account Settings" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 2} onClick={(event) => handleListItemClick(event, 2)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LockOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Privacy Center" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 3} onClick={(event) => handleListItemClick(event, 3)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<CommentOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Feedback" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton selected={selectedIndex === 4} onClick={(event) => handleListItemClick(event, 4)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<UnorderedListOutlined />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="History" />
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingTab;
|
|
@ -0,0 +1,212 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
ButtonBase,
|
||||||
|
CardContent,
|
||||||
|
ClickAwayListener,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
Popper,
|
||||||
|
Stack,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../../../../components/MainCard';
|
||||||
|
import Transitions from '../../../../../components/@extended/Transitions';
|
||||||
|
import ProfileTab from './ProfileTab';
|
||||||
|
import SettingTab from './SettingTab';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import avatar1 from '../../../../../assets/images/users/avatar-1.png';
|
||||||
|
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// tab panel wrapper
|
||||||
|
function TabPanel({ children, value, index, ...other }) {
|
||||||
|
return (
|
||||||
|
<div role="tabpanel" hidden={value !== index} id={`profile-tabpanel-${index}`} aria-labelledby={`profile-tab-${index}`} {...other}>
|
||||||
|
{value === index && children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TabPanel.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
index: PropTypes.any.isRequired,
|
||||||
|
value: PropTypes.any.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function a11yProps(index) {
|
||||||
|
return {
|
||||||
|
id: `profile-tab-${index}`,
|
||||||
|
'aria-controls': `profile-tabpanel-${index}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================|| HEADER CONTENT - PROFILE ||============================== //
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
// logout
|
||||||
|
};
|
||||||
|
|
||||||
|
const anchorRef = useRef(null);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleToggle = () => {
|
||||||
|
setOpen((prevOpen) => !prevOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (event) => {
|
||||||
|
if (anchorRef.current && anchorRef.current.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
const handleChange = (event, newValue) => {
|
||||||
|
setValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconBackColorOpen = 'grey.300';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ flexShrink: 0, ml: 0.75 }}>
|
||||||
|
<ButtonBase
|
||||||
|
sx={{
|
||||||
|
p: 0.25,
|
||||||
|
bgcolor: open ? iconBackColorOpen : 'transparent',
|
||||||
|
borderRadius: 1,
|
||||||
|
'&:hover': { bgcolor: 'secondary.lighter' }
|
||||||
|
}}
|
||||||
|
aria-label="open profile"
|
||||||
|
ref={anchorRef}
|
||||||
|
aria-controls={open ? 'profile-grow' : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleToggle}
|
||||||
|
>
|
||||||
|
<Stack direction="row" spacing={2} alignItems="center" sx={{ p: 0.5 }}>
|
||||||
|
<Avatar alt="profile user" src={avatar1} sx={{ width: 32, height: 32 }} />
|
||||||
|
<Typography variant="subtitle1">John Doe</Typography>
|
||||||
|
</Stack>
|
||||||
|
</ButtonBase>
|
||||||
|
<Popper
|
||||||
|
placement="bottom-end"
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorRef.current}
|
||||||
|
role={undefined}
|
||||||
|
transition
|
||||||
|
disablePortal
|
||||||
|
popperOptions={{
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
options: {
|
||||||
|
offset: [0, 9]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ TransitionProps }) => (
|
||||||
|
<Transitions type="fade" in={open} {...TransitionProps}>
|
||||||
|
{open && (
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
boxShadow: theme.customShadows.z1,
|
||||||
|
width: 290,
|
||||||
|
minWidth: 240,
|
||||||
|
maxWidth: 290,
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
maxWidth: 250
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClickAwayListener onClickAway={handleClose}>
|
||||||
|
<MainCard elevation={0} border={false} content={false}>
|
||||||
|
<CardContent sx={{ px: 2.5, pt: 3 }}>
|
||||||
|
<Grid container justifyContent="space-between" alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<Stack direction="row" spacing={1.25} alignItems="center">
|
||||||
|
<Avatar alt="profile user" src={avatar1} sx={{ width: 32, height: 32 }} />
|
||||||
|
<Stack>
|
||||||
|
<Typography variant="h6">John Doe</Typography>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
UI/UX Designer
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<IconButton size="large" color="secondary" onClick={handleLogout}>
|
||||||
|
<LogoutOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
{open && (
|
||||||
|
<>
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
|
<Tabs
|
||||||
|
variant="fullWidth"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
aria-label="profile tabs"
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
textTransform: 'capitalize'
|
||||||
|
}}
|
||||||
|
icon={<UserOutlined style={{ marginBottom: 0, marginRight: '10px' }} />}
|
||||||
|
label="Profile"
|
||||||
|
{...a11yProps(0)}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
textTransform: 'capitalize'
|
||||||
|
}}
|
||||||
|
icon={<SettingOutlined style={{ marginBottom: 0, marginRight: '10px' }} />}
|
||||||
|
label="Setting"
|
||||||
|
{...a11yProps(1)}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
<TabPanel value={value} index={0} dir={theme.direction}>
|
||||||
|
<ProfileTab handleLogout={handleLogout} />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={value} index={1} dir={theme.direction}>
|
||||||
|
<SettingTab />
|
||||||
|
</TabPanel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</MainCard>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Transitions>
|
||||||
|
)}
|
||||||
|
</Popper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
30
client/src/layout/MainLayout/Header/HeaderContent/Search.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// material-ui
|
||||||
|
import { Box, FormControl, InputAdornment, OutlinedInput } from '@mui/material';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| HEADER CONTENT - SEARCH ||============================== //
|
||||||
|
|
||||||
|
const Search = () => (
|
||||||
|
<Box sx={{ width: '100%', ml: { xs: 0, md: 1 } }}>
|
||||||
|
{/* <FormControl sx={{ width: { xs: '100%', md: 224 } }}>
|
||||||
|
<OutlinedInput
|
||||||
|
size="small"
|
||||||
|
id="header-search"
|
||||||
|
startAdornment={
|
||||||
|
<InputAdornment position="start" sx={{ mr: -0.5 }}>
|
||||||
|
<SearchOutlined />
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
aria-describedby="header-search-text"
|
||||||
|
inputProps={{
|
||||||
|
'aria-label': 'weight'
|
||||||
|
}}
|
||||||
|
placeholder="Ctrl + K"
|
||||||
|
/>
|
||||||
|
</FormControl> */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Search;
|
28
client/src/layout/MainLayout/Header/HeaderContent/index.jsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// material-ui
|
||||||
|
import { Box, IconButton, Link, useMediaQuery } from '@mui/material';
|
||||||
|
import { GithubOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Search from './Search';
|
||||||
|
import Profile from './Profile';
|
||||||
|
import Notification from './Notification';
|
||||||
|
import MobileSection from './MobileSection';
|
||||||
|
|
||||||
|
// ==============================|| HEADER - CONTENT ||============================== //
|
||||||
|
|
||||||
|
const HeaderContent = () => {
|
||||||
|
const matchesXs = useMediaQuery((theme) => theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!matchesXs && <Search />}
|
||||||
|
{matchesXs && <Box sx={{ width: '100%', ml: 1 }} />}
|
||||||
|
|
||||||
|
<Notification />
|
||||||
|
{!matchesXs && <Profile />}
|
||||||
|
{matchesXs && <MobileSection />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderContent;
|
69
client/src/layout/MainLayout/Header/index.jsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { AppBar, IconButton, Toolbar, useMediaQuery } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import AppBarStyled from './AppBarStyled';
|
||||||
|
import HeaderContent from './HeaderContent';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ==============================|| MAIN LAYOUT - HEADER ||============================== //
|
||||||
|
|
||||||
|
const Header = ({ open, handleDrawerToggle }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
|
||||||
|
const iconBackColor = 'grey.100';
|
||||||
|
const iconBackColorOpen = 'grey.200';
|
||||||
|
|
||||||
|
// common header
|
||||||
|
const mainHeader = (
|
||||||
|
<Toolbar>
|
||||||
|
<IconButton
|
||||||
|
disableRipple
|
||||||
|
aria-label="open drawer"
|
||||||
|
onClick={handleDrawerToggle}
|
||||||
|
edge="start"
|
||||||
|
color="secondary"
|
||||||
|
sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor, ml: { xs: 0, lg: -2 } }}
|
||||||
|
>
|
||||||
|
{!open ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
<HeaderContent />
|
||||||
|
</Toolbar>
|
||||||
|
);
|
||||||
|
|
||||||
|
// app-bar params
|
||||||
|
const appBar = {
|
||||||
|
position: 'fixed',
|
||||||
|
color: 'inherit',
|
||||||
|
elevation: 0,
|
||||||
|
sx: {
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`
|
||||||
|
// boxShadow: theme.customShadows.z1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!matchDownMD ? (
|
||||||
|
<AppBarStyled open={open} {...appBar}>
|
||||||
|
{mainHeader}
|
||||||
|
</AppBarStyled>
|
||||||
|
) : (
|
||||||
|
<AppBar {...appBar}>{mainHeader}</AppBar>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
open: PropTypes.bool,
|
||||||
|
handleDrawerToggle: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
60
client/src/layout/MainLayout/index.jsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Box, Toolbar, useMediaQuery } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Drawer from './Drawer';
|
||||||
|
import Header from './Header';
|
||||||
|
import navigation from '../../menu-items';
|
||||||
|
import Breadcrumbs from '../../components/@extended/Breadcrumbs';
|
||||||
|
|
||||||
|
// types
|
||||||
|
import { openDrawer } from '../../store/reducers/menu';
|
||||||
|
|
||||||
|
// ==============================|| MAIN LAYOUT ||============================== //
|
||||||
|
|
||||||
|
const MainLayout = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchDownLG = useMediaQuery(theme.breakpoints.down('xl'));
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const { drawerOpen } = useSelector((state) => state.menu);
|
||||||
|
|
||||||
|
// drawer toggler
|
||||||
|
const [open, setOpen] = useState(drawerOpen);
|
||||||
|
const handleDrawerToggle = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
dispatch(openDrawer({ drawerOpen: !open }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// set media wise responsive drawer
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(!matchDownLG);
|
||||||
|
dispatch(openDrawer({ drawerOpen: !matchDownLG }));
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [matchDownLG]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open !== drawerOpen) setOpen(drawerOpen);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [drawerOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', width: '100%' }}>
|
||||||
|
<Header open={open} handleDrawerToggle={handleDrawerToggle} />
|
||||||
|
<Drawer open={open} handleDrawerToggle={handleDrawerToggle} />
|
||||||
|
<Box component="main" sx={{ width: '100%', flexGrow: 1, p: { xs: 2, sm: 3 } }}>
|
||||||
|
<Toolbar />
|
||||||
|
<Breadcrumbs navigation={navigation} title titleBottom card={false} divider={false} />
|
||||||
|
<Outlet />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLayout;
|
11
client/src/layout/MinimalLayout/index.jsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
// ==============================|| MINIMAL LAYOUT ||============================== //
|
||||||
|
|
||||||
|
const MinimalLayout = () => (
|
||||||
|
<>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MinimalLayout;
|
0
client/src/main.css
Normal file
9
client/src/main.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import './main.css'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<div>Hello</div>
|
||||||
|
</React.StrictMode>
|
||||||
|
)
|
27
client/src/menu-items/dashboard.jsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// assets
|
||||||
|
import { HomeOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// icons
|
||||||
|
const icons = {
|
||||||
|
HomeOutlined
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| MENU ITEMS - DASHBOARD ||============================== //
|
||||||
|
|
||||||
|
const dashboard = {
|
||||||
|
id: 'group-dashboard',
|
||||||
|
title: 'Navigation',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 'home',
|
||||||
|
title: 'Home',
|
||||||
|
type: 'item',
|
||||||
|
url: '/',
|
||||||
|
icon: icons.HomeOutlined,
|
||||||
|
breadcrumbs: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default dashboard;
|
12
client/src/menu-items/index.jsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// project import
|
||||||
|
import pages from './pages';
|
||||||
|
import dashboard from './dashboard';
|
||||||
|
import support from './support';
|
||||||
|
|
||||||
|
// ==============================|| MENU ITEMS ||============================== //
|
||||||
|
|
||||||
|
const menuItems = {
|
||||||
|
items: [dashboard, pages, support]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default menuItems;
|
42
client/src/menu-items/pages.jsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// assets
|
||||||
|
import { ProfileOutlined, SettingOutlined, NodeExpandOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
|
// icons
|
||||||
|
const icons = {
|
||||||
|
NodeExpandOutlined,
|
||||||
|
ProfileOutlined,
|
||||||
|
SettingOutlined
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== //
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
id: 'management',
|
||||||
|
title: 'Management',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 'proxy',
|
||||||
|
title: 'Proxy Routes',
|
||||||
|
type: 'item',
|
||||||
|
url: '/config/proxy',
|
||||||
|
icon: icons.NodeExpandOutlined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'users',
|
||||||
|
title: 'Manage Users',
|
||||||
|
type: 'item',
|
||||||
|
url: '/config/users',
|
||||||
|
icon: icons.ProfileOutlined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'config',
|
||||||
|
title: 'Configuration',
|
||||||
|
type: 'item',
|
||||||
|
url: '/config/general',
|
||||||
|
icon: icons.SettingOutlined,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pages;
|
48
client/src/menu-items/support.jsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// assets
|
||||||
|
import { GithubOutlined, QuestionOutlined } from '@ant-design/icons';
|
||||||
|
import DiscordOutlined from '../assets/images/icons/discord.svg'
|
||||||
|
|
||||||
|
// ==============================|| MENU ITEMS - SAMPLE PAGE & DOCUMENTATION ||============================== //
|
||||||
|
|
||||||
|
const DiscordOutlinedIcon = (props) => {
|
||||||
|
return (
|
||||||
|
<img src={DiscordOutlined} width="16px" alt="Discord" {...props} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const support = {
|
||||||
|
id: 'support',
|
||||||
|
title: 'Support',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 'discord',
|
||||||
|
title: 'Discord',
|
||||||
|
type: 'item',
|
||||||
|
url: 'https://discord.com/invite/PwMWwsrwHA',
|
||||||
|
icon: DiscordOutlinedIcon,
|
||||||
|
external: true,
|
||||||
|
target: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'github',
|
||||||
|
title: 'Github',
|
||||||
|
type: 'item',
|
||||||
|
url: 'https://github.com/azukaar/Cosmos-Server',
|
||||||
|
icon: GithubOutlined,
|
||||||
|
external: true,
|
||||||
|
target: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'documentation',
|
||||||
|
title: 'Documentation',
|
||||||
|
type: 'item',
|
||||||
|
url: 'https://github.com/azukaar/Cosmos-Server/wiki',
|
||||||
|
icon: QuestionOutlined,
|
||||||
|
external: true,
|
||||||
|
target: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default support;
|
35
client/src/pages/authentication/AuthCard.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
|
||||||
|
// ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== //
|
||||||
|
|
||||||
|
const AuthCard = ({ children, ...other }) => (
|
||||||
|
<MainCard
|
||||||
|
sx={{
|
||||||
|
maxWidth: { xs: 400, lg: 475 },
|
||||||
|
margin: { xs: 2.5, md: 3 },
|
||||||
|
'& > *': {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexBasis: '50%'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
content={false}
|
||||||
|
{...other}
|
||||||
|
border={false}
|
||||||
|
boxShadow
|
||||||
|
shadow={(theme) => theme.customShadows.z1}
|
||||||
|
>
|
||||||
|
<Box sx={{ p: { xs: 2, sm: 3, md: 4, xl: 5 } }}>{children}</Box>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
AuthCard.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthCard;
|
55
client/src/pages/authentication/AuthWrapper.jsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, Grid } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import AuthCard from './AuthCard';
|
||||||
|
import Logo from '../../components/Logo';
|
||||||
|
import AuthFooter from '../../components/cards/AuthFooter';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import AuthBackground from '../../assets/images/auth/AuthBackground';
|
||||||
|
|
||||||
|
// ==============================|| AUTHENTICATION - WRAPPER ||============================== //
|
||||||
|
|
||||||
|
const AuthWrapper = ({ children }) => (
|
||||||
|
<Box sx={{ minHeight: '100vh' }}>
|
||||||
|
<AuthBackground />
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="column"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
sx={{
|
||||||
|
minHeight: '100vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid item xs={12} sx={{ ml: 3, mt: 3 }}>
|
||||||
|
<Logo />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={12}
|
||||||
|
container
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
sx={{ minHeight: { xs: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' } }}
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<AuthCard>{children}</AuthCard>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sx={{ m: 3, mt: 1 }}>
|
||||||
|
<AuthFooter />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
AuthWrapper.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthWrapper;
|
30
client/src/pages/authentication/Login.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Grid, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import AuthLogin from './auth-forms/AuthLogin';
|
||||||
|
import AuthWrapper from './AuthWrapper';
|
||||||
|
|
||||||
|
// ================================|| LOGIN ||================================ //
|
||||||
|
|
||||||
|
const Login = () => (
|
||||||
|
<AuthWrapper>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||||
|
<Typography variant="h3">Login</Typography>
|
||||||
|
{/* <Typography component={Link} to="/register" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
|
||||||
|
Don't have an account?
|
||||||
|
</Typography> */}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<AuthLogin />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</AuthWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Login;
|
30
client/src/pages/authentication/Register.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Grid, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import FirebaseRegister from './auth-forms/AuthRegister';
|
||||||
|
import AuthWrapper from './AuthWrapper';
|
||||||
|
|
||||||
|
// ================================|| REGISTER ||================================ //
|
||||||
|
|
||||||
|
const Register = () => (
|
||||||
|
<AuthWrapper>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||||
|
<Typography variant="h3">Sign up</Typography>
|
||||||
|
<Typography component={Link} to="/login" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
|
||||||
|
Already have an account?
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FirebaseRegister />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</AuthWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Register;
|
226
client/src/pages/authentication/auth-forms/AuthLogin.jsx
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Divider,
|
||||||
|
FormControlLabel,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
Link,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
|
OutlinedInput,
|
||||||
|
Stack,
|
||||||
|
Typography,
|
||||||
|
Alert
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import * as API from "../../../api";
|
||||||
|
|
||||||
|
// third party
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import FirebaseSocial from './FirebaseSocial';
|
||||||
|
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ============================|| FIREBASE - LOGIN ||============================ //
|
||||||
|
|
||||||
|
const AuthLogin = () => {
|
||||||
|
const [checked, setChecked] = React.useState(false);
|
||||||
|
|
||||||
|
const [showPassword, setShowPassword] = React.useState(false);
|
||||||
|
const handleClickShowPassword = () => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDownPassword = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Add ?notlogged=1 and ?invalid=1 to check for errors
|
||||||
|
// TODO: Extract ?redirect=<URL> to redirect to a specific page after login
|
||||||
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
|
const notLogged = urlSearchParams.get('notlogged') == 1;
|
||||||
|
const invalid = urlSearchParams.get('invalid') == 1;
|
||||||
|
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
API.auth.me().then((data) => {
|
||||||
|
if(data.status == 'OK') {
|
||||||
|
window.location.href = redirectTo;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ notLogged &&<Grid container spacing={2} justifyContent="center">
|
||||||
|
<Alert severity="error">You need to be logged in to access this</Alert>
|
||||||
|
<br />
|
||||||
|
</Grid>}
|
||||||
|
|
||||||
|
{ invalid &&<Grid container spacing={2} justifyContent="center">
|
||||||
|
<Alert severity="error">You have been disconnected. Please login to continue</Alert>
|
||||||
|
<br />
|
||||||
|
</Grid>}
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
nickname: '',
|
||||||
|
password: '',
|
||||||
|
submit: null
|
||||||
|
}}
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
nickname: Yup.string().max(255).required('Nickname is required'),
|
||||||
|
password: Yup.string().max(255).required('Password is required')
|
||||||
|
})}
|
||||||
|
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||||
|
try {
|
||||||
|
API.auth.login(values).then((data) => {
|
||||||
|
if(data.status == 'error') {
|
||||||
|
setStatus({ success: false });
|
||||||
|
if(data.code == 'UL001') {
|
||||||
|
setErrors({ submit: 'Wrong nickname or password. Try again or try resetting your password' });
|
||||||
|
} else if (data.code == 'UL002') {
|
||||||
|
setErrors({ submit: 'You have not yet registered your account. You should have an invite link in your emails. If you need a new one, contact your administrator.' });
|
||||||
|
} else if(data.status == 'error') {
|
||||||
|
setErrors({ submit: 'Unexpected error. Try again later.' });
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
setStatus({ success: true });
|
||||||
|
setSubmitting(false);
|
||||||
|
window.location.href = redirectTo;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
setStatus({ success: false });
|
||||||
|
setErrors({ submit: err.message });
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||||
|
<form noValidate onSubmit={handleSubmit}>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="nickname-login">Nickname</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="nickname-login"
|
||||||
|
type="nickname"
|
||||||
|
value={values.nickname}
|
||||||
|
name="nickname"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Enter your nickname"
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.nickname && errors.nickname)}
|
||||||
|
/>
|
||||||
|
{touched.nickname && errors.nickname && (
|
||||||
|
<FormHelperText error id="standard-weight-helper-text-nickname-login">
|
||||||
|
{errors.nickname}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="password-login">Password</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.password && errors.password)}
|
||||||
|
id="-password-login"
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={values.password}
|
||||||
|
name="password"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={handleClickShowPassword}
|
||||||
|
onMouseDown={handleMouseDownPassword}
|
||||||
|
edge="end"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
/>
|
||||||
|
{touched.password && errors.password && (
|
||||||
|
<FormHelperText error id="standard-weight-helper-text-password-login">
|
||||||
|
{errors.password}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} sx={{ mt: -1 }}>
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={checked}
|
||||||
|
onChange={(event) => setChecked(event.target.checked)}
|
||||||
|
name="checked"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={<Typography variant="h6">Keep me sign in</Typography>}
|
||||||
|
/>
|
||||||
|
<Link variant="h6" component={RouterLink} to="" color="text.primary">
|
||||||
|
Forgot Password?
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
{errors.submit && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<AnimateButton>
|
||||||
|
<Button
|
||||||
|
disableElevation
|
||||||
|
disabled={isSubmitting}
|
||||||
|
fullWidth
|
||||||
|
size="large"
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</AnimateButton>
|
||||||
|
</Grid>
|
||||||
|
{/* <Grid item xs={12}>
|
||||||
|
<Divider>
|
||||||
|
<Typography variant="caption"> Login with</Typography>
|
||||||
|
</Divider>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FirebaseSocial />
|
||||||
|
</Grid> */}
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthLogin;
|
271
client/src/pages/authentication/auth-forms/AuthRegister.jsx
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
Link,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
|
OutlinedInput,
|
||||||
|
Stack,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
// third party
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import FirebaseSocial from './FirebaseSocial';
|
||||||
|
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||||
|
import { strengthColor, strengthIndicator } from '../../../utils/password-strength';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// ============================|| FIREBASE - REGISTER ||============================ //
|
||||||
|
|
||||||
|
const AuthRegister = () => {
|
||||||
|
const [level, setLevel] = useState();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const handleClickShowPassword = () => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDownPassword = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePassword = (value) => {
|
||||||
|
const temp = strengthIndicator(value);
|
||||||
|
setLevel(strengthColor(temp));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
changePassword('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
firstname: '',
|
||||||
|
lastname: '',
|
||||||
|
email: '',
|
||||||
|
company: '',
|
||||||
|
password: '',
|
||||||
|
submit: null
|
||||||
|
}}
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
firstname: Yup.string().max(255).required('First Name is required'),
|
||||||
|
lastname: Yup.string().max(255).required('Last Name is required'),
|
||||||
|
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'),
|
||||||
|
password: Yup.string().max(255).required('Password is required')
|
||||||
|
})}
|
||||||
|
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||||
|
try {
|
||||||
|
setStatus({ success: false });
|
||||||
|
setSubmitting(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setStatus({ success: false });
|
||||||
|
setErrors({ submit: err.message });
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||||
|
<form noValidate onSubmit={handleSubmit}>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="firstname-signup">First Name*</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="firstname-login"
|
||||||
|
type="firstname"
|
||||||
|
value={values.firstname}
|
||||||
|
name="firstname"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="John"
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.firstname && errors.firstname)}
|
||||||
|
/>
|
||||||
|
{touched.firstname && errors.firstname && (
|
||||||
|
<FormHelperText error id="helper-text-firstname-signup">
|
||||||
|
{errors.firstname}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="lastname-signup">Last Name*</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.lastname && errors.lastname)}
|
||||||
|
id="lastname-signup"
|
||||||
|
type="lastname"
|
||||||
|
value={values.lastname}
|
||||||
|
name="lastname"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Doe"
|
||||||
|
inputProps={{}}
|
||||||
|
/>
|
||||||
|
{touched.lastname && errors.lastname && (
|
||||||
|
<FormHelperText error id="helper-text-lastname-signup">
|
||||||
|
{errors.lastname}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="company-signup">Company</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.company && errors.company)}
|
||||||
|
id="company-signup"
|
||||||
|
value={values.company}
|
||||||
|
name="company"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Demo Inc."
|
||||||
|
inputProps={{}}
|
||||||
|
/>
|
||||||
|
{touched.company && errors.company && (
|
||||||
|
<FormHelperText error id="helper-text-company-signup">
|
||||||
|
{errors.company}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="email-signup">Email Address*</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.email && errors.email)}
|
||||||
|
id="email-login"
|
||||||
|
type="email"
|
||||||
|
value={values.email}
|
||||||
|
name="email"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="demo@company.com"
|
||||||
|
inputProps={{}}
|
||||||
|
/>
|
||||||
|
{touched.email && errors.email && (
|
||||||
|
<FormHelperText error id="helper-text-email-signup">
|
||||||
|
{errors.email}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel htmlFor="password-signup">Password</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
fullWidth
|
||||||
|
error={Boolean(touched.password && errors.password)}
|
||||||
|
id="password-signup"
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={values.password}
|
||||||
|
name="password"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleChange(e);
|
||||||
|
changePassword(e.target.value);
|
||||||
|
}}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={handleClickShowPassword}
|
||||||
|
onMouseDown={handleMouseDownPassword}
|
||||||
|
edge="end"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
placeholder="******"
|
||||||
|
inputProps={{}}
|
||||||
|
/>
|
||||||
|
{touched.password && errors.password && (
|
||||||
|
<FormHelperText error id="helper-text-password-signup">
|
||||||
|
{errors.password}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
<FormControl fullWidth sx={{ mt: 2 }}>
|
||||||
|
<Grid container spacing={2} alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<Box sx={{ bgcolor: level?.color, width: 85, height: 8, borderRadius: '7px' }} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="subtitle1" fontSize="0.75rem">
|
||||||
|
{level?.label}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
By Signing up, you agree to our
|
||||||
|
<Link variant="subtitle2" component={RouterLink} to="#">
|
||||||
|
Terms of Service
|
||||||
|
</Link>
|
||||||
|
and
|
||||||
|
<Link variant="subtitle2" component={RouterLink} to="#">
|
||||||
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
{errors.submit && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<AnimateButton>
|
||||||
|
<Button
|
||||||
|
disableElevation
|
||||||
|
disabled={isSubmitting}
|
||||||
|
fullWidth
|
||||||
|
size="large"
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Create Account
|
||||||
|
</Button>
|
||||||
|
</AnimateButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Divider>
|
||||||
|
<Typography variant="caption">Sign up with</Typography>
|
||||||
|
</Divider>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FirebaseSocial />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthRegister;
|
|
@ -0,0 +1,66 @@
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { useMediaQuery, Button, Stack } from '@mui/material';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import Google from '../../../assets/images/icons/google.svg';
|
||||||
|
import Twitter from '../../../assets/images/icons/twitter.svg';
|
||||||
|
import Facebook from '../../../assets/images/icons/facebook.svg';
|
||||||
|
|
||||||
|
// ==============================|| FIREBASE - SOCIAL BUTTON ||============================== //
|
||||||
|
|
||||||
|
const FirebaseSocial = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const matchDownSM = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
const googleHandler = async () => {
|
||||||
|
// login || singup
|
||||||
|
};
|
||||||
|
|
||||||
|
const twitterHandler = async () => {
|
||||||
|
// login || singup
|
||||||
|
};
|
||||||
|
|
||||||
|
const facebookHandler = async () => {
|
||||||
|
// login || singup
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={matchDownSM ? 1 : 2}
|
||||||
|
justifyContent={matchDownSM ? 'space-around' : 'space-between'}
|
||||||
|
sx={{ '& .MuiButton-startIcon': { mr: matchDownSM ? 0 : 1, ml: matchDownSM ? 0 : -0.5 } }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
fullWidth={!matchDownSM}
|
||||||
|
startIcon={<img src={Google} alt="Google" />}
|
||||||
|
onClick={googleHandler}
|
||||||
|
>
|
||||||
|
{!matchDownSM && 'Google'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
fullWidth={!matchDownSM}
|
||||||
|
startIcon={<img src={Twitter} alt="Twitter" />}
|
||||||
|
onClick={twitterHandler}
|
||||||
|
>
|
||||||
|
{!matchDownSM && 'Twitter'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
fullWidth={!matchDownSM}
|
||||||
|
startIcon={<img src={Facebook} alt="Facebook" />}
|
||||||
|
onClick={facebookHandler}
|
||||||
|
>
|
||||||
|
{!matchDownSM && 'Facebook'}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FirebaseSocial;
|
24
client/src/pages/components-overview/AntIcons.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import ComponentSkeleton from './ComponentSkeleton';
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
|
||||||
|
// styles
|
||||||
|
const IFrameWrapper = styled('iframe')(() => ({
|
||||||
|
height: 'calc(100vh - 210px)',
|
||||||
|
border: 'none'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ============================|| ANT ICONS ||============================ //
|
||||||
|
|
||||||
|
const AntIcons = () => (
|
||||||
|
<ComponentSkeleton>
|
||||||
|
<MainCard title="Ant Icons">
|
||||||
|
<IFrameWrapper title="Ant Icon" width="100%" src="https://ant.design/components/icon/" />
|
||||||
|
</MainCard>
|
||||||
|
</ComponentSkeleton>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AntIcons;
|
141
client/src/pages/components-overview/Color.jsx
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, Card, Grid, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
import ComponentSkeleton from './ComponentSkeleton';
|
||||||
|
|
||||||
|
// ===============================|| COLOR BOX ||=============================== //
|
||||||
|
|
||||||
|
function ColorBox({ bgcolor, title, data, dark, main }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card sx={{ '&.MuiPaper-root': { borderRadius: '0px' } }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
py: 2.5,
|
||||||
|
bgcolor,
|
||||||
|
color: dark ? 'grey.800' : '#ffffff',
|
||||||
|
border: main ? '1px dashed' : '1px solid transparent'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title && (
|
||||||
|
<Grid container justifyContent="space-around" alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
{data && (
|
||||||
|
<Stack spacing={0.75} alignItems="center">
|
||||||
|
<Typography variant="subtitle2">{data.label}</Typography>
|
||||||
|
<Typography variant="subtitle1">{data.color}</Typography>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="subtitle1" color="inherit">
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorBox.propTypes = {
|
||||||
|
bgcolor: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
data: PropTypes.object.isRequired,
|
||||||
|
dark: PropTypes.bool,
|
||||||
|
main: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===============================|| COMPONENT - COLOR ||=============================== //
|
||||||
|
|
||||||
|
const ComponentColor = () => (
|
||||||
|
<ComponentSkeleton>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<MainCard title="Primary Color" codeHighlight>
|
||||||
|
<Stack>
|
||||||
|
<ColorBox bgcolor="primary.lighter" data={{ label: 'Blue-1', color: '#e6f7ff' }} title="primary.lighter" dark />
|
||||||
|
<ColorBox bgcolor="primary.100" data={{ label: 'Blue-2', color: '#bae7ff' }} title="primary[100]" dark />
|
||||||
|
<ColorBox bgcolor="primary.200" data={{ label: 'Blue-3', color: '#91d5ff' }} title="primary[200]" dark />
|
||||||
|
<ColorBox bgcolor="primary.light" data={{ label: 'Blue-4', color: '#69c0ff' }} title="primary.light" dark />
|
||||||
|
<ColorBox bgcolor="primary.400" data={{ label: 'Blue-5', color: '#40a9ff' }} title="primary[400]" />
|
||||||
|
<ColorBox bgcolor="primary.main" data={{ label: 'Blue-6', color: '#1890ff' }} title="primary.main" main />
|
||||||
|
<ColorBox bgcolor="primary.dark" data={{ label: 'Blue-7', color: '#096dd9' }} title="primary.dark" />
|
||||||
|
<ColorBox bgcolor="primary.700" data={{ label: 'Blue-8', color: '#0050b3' }} title="primary[700]" />
|
||||||
|
<ColorBox bgcolor="primary.darker" data={{ label: 'Blue-9', color: '#003a8c' }} title="primary.darker" />
|
||||||
|
<ColorBox bgcolor="primary.900" data={{ label: 'Blue-10', color: '#002766' }} title="primary.900" />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<MainCard title="Secondary Color" codeHighlight>
|
||||||
|
<Stack>
|
||||||
|
<ColorBox bgcolor="secondary.lighter" data={{ label: 'Grey-1', color: '#fafafa' }} title="secondary.lighter" dark />
|
||||||
|
<ColorBox bgcolor="secondary.100" data={{ label: 'Grey-2', color: '#f5f5f5' }} title="secondary[100]" dark />
|
||||||
|
<ColorBox bgcolor="secondary.200" data={{ label: 'Grey-3', color: '#f0f0f0' }} title="secondary[200]" dark />
|
||||||
|
<ColorBox bgcolor="secondary.light" data={{ label: 'Grey-4', color: '#d9d9d9' }} title="secondary.light" dark />
|
||||||
|
<ColorBox bgcolor="secondary.400" data={{ label: 'Grey-5', color: '#bfbfbf' }} title="secondary[400]" dark />
|
||||||
|
<ColorBox bgcolor="secondary.main" data={{ label: 'Grey-6', color: '#8c8c8c' }} title="secondary.main" main />
|
||||||
|
<ColorBox bgcolor="secondary.600" data={{ label: 'Grey-7', color: '#595959' }} title="secondary.600" />
|
||||||
|
<ColorBox bgcolor="secondary.dark" data={{ label: 'Grey-8', color: '#262626' }} title="secondary.dark" />
|
||||||
|
<ColorBox bgcolor="secondary.800" data={{ label: 'Grey-9', color: '#141414' }} title="secondary[800]" />
|
||||||
|
<ColorBox bgcolor="secondary.darker" data={{ label: 'Grey-10', color: '#000000' }} title="secondary.darker" />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<MainCard title="Other Color" codeHighlight>
|
||||||
|
<Stack>
|
||||||
|
<ColorBox bgcolor="secondary.A100" data={{ label: 'Grey-A1', color: '#ffffff' }} title="secondary.A100" dark />
|
||||||
|
<ColorBox bgcolor="secondary.A200" data={{ label: 'Grey-A2', color: '#434343' }} title="secondary.A200" />
|
||||||
|
<ColorBox bgcolor="secondary.A300" data={{ label: 'Grey-A3', color: '#1f1f1f' }} title="secondary.A300" />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<MainCard title="Success Color" codeHighlight>
|
||||||
|
<Stack>
|
||||||
|
<ColorBox bgcolor="success.lighter" data={{ label: 'Green-1', color: '#f6ffed' }} title="success.lighter" dark />
|
||||||
|
<ColorBox bgcolor="success.light" data={{ label: 'Green-4', color: '#95de64' }} title="success.light" dark />
|
||||||
|
<ColorBox bgcolor="success.main" data={{ label: 'Green-6', color: '#52c41a' }} title="success.main" main />
|
||||||
|
<ColorBox bgcolor="success.dark" data={{ label: 'Green-8', color: '#237804' }} title="success.dark" />
|
||||||
|
<ColorBox bgcolor="success.darker" data={{ label: 'Green-10', color: '#092b00' }} title="success.darker" />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<MainCard title="Error Color" codeHighlight>
|
||||||
|
<Stack>
|
||||||
|
<ColorBox bgcolor="error.lighter" data={{ label: 'Red-1', color: '#fff1f0' }} title="error.lighter" dark />
|
||||||
|
<ColorBox bgcolor="error.light" data={{ label: 'Red-4', color: '#ff7875' }} title="error.light" dark />
|
||||||
|
<ColorBox bgcolor="error.main" data={{ label: 'Red-6', color: '#f5222d' }} title="error.main" main />
|
||||||
|
<ColorBox bgcolor="error.dark" data={{ label: 'Red-8', color: '#a8071a' }} title="error.dark" />
|
||||||
|
<ColorBox bgcolor="error.darker" data={{ label: 'Red-10', color: '#5c0011' }} title="error.darker" />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
|
<MainCard title="Warning Color" codeHighlight>
|
||||||
|
<Stack>
|
||||||
|
<ColorBox bgcolor="warning.lighter" data={{ label: 'Gold-1', color: '#fffbe6' }} title="warning.lighter" dark />
|
||||||
|
<ColorBox bgcolor="warning.light" data={{ label: 'Gold-4', color: '#ffd666' }} title="warning.light" dark />
|
||||||
|
<ColorBox bgcolor="warning.main" data={{ label: 'Gold-6', color: '#faad14' }} title="warning.main" main />
|
||||||
|
<ColorBox bgcolor="warning.dark" data={{ label: 'Gold-8', color: '#ad6800' }} title="warning.dark" />
|
||||||
|
<ColorBox bgcolor="warning.darker" data={{ label: 'Gold-10', color: '#613400' }} title="warning.darker" />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ComponentSkeleton>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ComponentColor;
|
59
client/src/pages/components-overview/ComponentSkeleton.jsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Grid, Skeleton, Stack } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
|
||||||
|
// ===============================|| COMPONENT - SKELETON ||=============================== //
|
||||||
|
|
||||||
|
const ComponentSkeleton = ({ children }) => {
|
||||||
|
const [isLoading, setLoading] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const skeletonCard = (
|
||||||
|
<MainCard
|
||||||
|
title={<Skeleton sx={{ width: { xs: 120, md: 180 } }} />}
|
||||||
|
secondary={<Skeleton animation="wave" variant="circular" width={24} height={24} />}
|
||||||
|
>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Skeleton />
|
||||||
|
<Skeleton sx={{ height: 64 }} animation="wave" variant="rectangular" />
|
||||||
|
<Skeleton />
|
||||||
|
<Skeleton />
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoading && (
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
{skeletonCard}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
{skeletonCard}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
{skeletonCard}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
{skeletonCard}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{!isLoading && children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ComponentSkeleton.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ComponentSkeleton;
|
152
client/src/pages/components-overview/Shadow.jsx
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Grid, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
import ComponentSkeleton from './ComponentSkeleton';
|
||||||
|
|
||||||
|
// ===============================|| SHADOW BOX ||=============================== //
|
||||||
|
|
||||||
|
function ShadowBox({ shadow }) {
|
||||||
|
return (
|
||||||
|
<MainCard border={false} sx={{ boxShadow: shadow }}>
|
||||||
|
<Stack spacing={1} justifyContent="center" alignItems="center">
|
||||||
|
<Typography variant="h6">boxShadow</Typography>
|
||||||
|
<Typography variant="subtitle1">{shadow}</Typography>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowBox.propTypes = {
|
||||||
|
shadow: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===============================|| CUSTOM - SHADOW BOX ||=============================== //
|
||||||
|
|
||||||
|
function CustomShadowBox({ shadow, label, color, bgcolor }) {
|
||||||
|
return (
|
||||||
|
<MainCard border={false} sx={{ bgcolor: bgcolor || 'inherit', boxShadow: shadow }}>
|
||||||
|
<Stack spacing={1} justifyContent="center" alignItems="center">
|
||||||
|
<Typography variant="subtitle1" color={color}>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomShadowBox.propTypes = {
|
||||||
|
shadow: PropTypes.string.isRequired,
|
||||||
|
color: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
bgcolor: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================|| COMPONENT - SHADOW ||============================ //
|
||||||
|
|
||||||
|
const ComponentShadow = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ComponentSkeleton>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<MainCard title="Basic Shadow" codeHighlight>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="0" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="1" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="2" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="3" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="4" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="5" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="6" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="7" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="8" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="9" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="10" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="11" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="12" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="13" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="14" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="15" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="16" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="17" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="18" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="19" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="20" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="21" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="22" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="23" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<ShadowBox shadow="24" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<MainCard title="Custom Shadow" codeHighlight>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
|
<CustomShadowBox shadow={theme.customShadows.z1} label="z1" color="inherit" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ComponentSkeleton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ComponentShadow;
|
263
client/src/pages/components-overview/Typography.jsx
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
// material-ui
|
||||||
|
import { Breadcrumbs, Divider, Grid, Link, Stack, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import ComponentSkeleton from './ComponentSkeleton';
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
|
||||||
|
// ==============================|| COMPONENTS - TYPOGRAPHY ||============================== //
|
||||||
|
|
||||||
|
const ComponentTypography = () => (
|
||||||
|
<ComponentSkeleton>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} lg={6}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<MainCard title="Basic" codeHighlight>
|
||||||
|
<Stack spacing={0.75} sx={{ mt: -1.5 }}>
|
||||||
|
<Typography variant="h1">Inter</Typography>
|
||||||
|
<Typography variant="h5">Font Family</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Regular</Typography>
|
||||||
|
<Typography variant="h6">Medium</Typography>
|
||||||
|
<Typography variant="h6">Bold</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Heading" codeHighlight>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Typography variant="h1">H1 Heading</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 38px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Bold</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 46px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Typography variant="h2">H2 Heading</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 30px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Bold</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 38px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Typography variant="h3">H3 Heading</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 24px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular & Bold</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 32px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Typography variant="h4">H4 Heading</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 20px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Bold</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 28px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Typography variant="h5">H5 Heading</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 16px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular & Medium & Bold</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 24px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Typography variant="h6">H6 Heading / Subheading</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 14px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 22px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Body 1" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="body1" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 14px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 22px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Body 2" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 12px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 20px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Subtitle 1" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 14px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Medium</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 22px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Subtitle 2" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 12px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Medium</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 20px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Caption" codeHighlight>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Typography variant="caption">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 12px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 20px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} lg={6}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<MainCard title="Alignment" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" textAlign="center" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" textAlign="right">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Gutter Bottom" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="body1" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 12px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 20px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Overline" codeHighlight>
|
||||||
|
<Stack spacing={1.5}>
|
||||||
|
<Typography variant="overline">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 12px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 20px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Link" codeHighlight>
|
||||||
|
<Stack spacing={1.5}>
|
||||||
|
<Link href="#">www.mantis.com</Link>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 12px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 20px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Colors" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="h6" color="textPrimary" gutterBottom>
|
||||||
|
This is textPrimary text color.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="textSecondary" gutterBottom>
|
||||||
|
This is textSecondary text color.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="primary" gutterBottom>
|
||||||
|
This is primary text color.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="secondary" gutterBottom>
|
||||||
|
This is secondary text color.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="success" gutterBottom>
|
||||||
|
This is success text color.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" sx={{ color: 'warning.main' }} gutterBottom>
|
||||||
|
This is warning text color.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="error" gutterBottom>
|
||||||
|
This is error text color.
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Paragraph" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="body1" gutterBottom>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
|
||||||
|
incididunt ut labore et dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 14px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Regular</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 22px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard title="Font Style" codeHighlight>
|
||||||
|
<>
|
||||||
|
<Typography variant="body1" gutterBottom sx={{ fontStyle: 'italic' }}>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" gutterBottom sx={{ fontStyle: 'italic' }}>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua.
|
||||||
|
</Typography>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<Typography variant="h6">Size: 14px</Typography>
|
||||||
|
<Typography variant="h6">Weight: Italic Regular & Italic Bold</Typography>
|
||||||
|
<Typography variant="h6">Line Height: 22px</Typography>
|
||||||
|
</Breadcrumbs>
|
||||||
|
</>
|
||||||
|
</MainCard>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ComponentSkeleton>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ComponentTypography;
|
121
client/src/pages/dashboard/IncomeAreaChart.jsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
|
// chart options
|
||||||
|
const areaChartOptions = {
|
||||||
|
chart: {
|
||||||
|
height: 450,
|
||||||
|
type: 'area',
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
strokeDashArray: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| INCOME AREA CHART ||============================== //
|
||||||
|
|
||||||
|
const IncomeAreaChart = ({ slot }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { primary, secondary } = theme.palette.text;
|
||||||
|
const line = theme.palette.divider;
|
||||||
|
|
||||||
|
const [options, setOptions] = useState(areaChartOptions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOptions((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
colors: [theme.palette.primary.main, theme.palette.primary[700]],
|
||||||
|
xaxis: {
|
||||||
|
categories:
|
||||||
|
slot === 'month'
|
||||||
|
? ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||||
|
: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary,
|
||||||
|
secondary
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisBorder: {
|
||||||
|
show: true,
|
||||||
|
color: line
|
||||||
|
},
|
||||||
|
tickAmount: slot === 'month' ? 11 : 7
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [secondary]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: line
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: 'light'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [primary, secondary, line, theme, slot]);
|
||||||
|
|
||||||
|
const [series, setSeries] = useState([
|
||||||
|
{
|
||||||
|
name: 'Page Views',
|
||||||
|
data: [0, 86, 28, 115, 48, 210, 136]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sessions',
|
||||||
|
data: [0, 43, 14, 56, 24, 105, 68]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSeries([
|
||||||
|
{
|
||||||
|
name: 'Page Views',
|
||||||
|
data: slot === 'month' ? [76, 85, 101, 98, 87, 105, 91, 114, 94, 86, 115, 35] : [31, 40, 28, 51, 42, 109, 100]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sessions',
|
||||||
|
data: slot === 'month' ? [110, 60, 150, 35, 60, 36, 26, 45, 65, 52, 53, 41] : [11, 32, 45, 32, 34, 52, 41]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}, [slot]);
|
||||||
|
|
||||||
|
return <ReactApexChart options={options} series={series} type="area" height={450} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
IncomeAreaChart.propTypes = {
|
||||||
|
slot: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IncomeAreaChart;
|
85
client/src/pages/dashboard/MonthlyBarChart.jsx
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
|
// chart options
|
||||||
|
const barChartOptions = {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 365,
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
columnWidth: '45%',
|
||||||
|
borderRadius: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
||||||
|
axisBorder: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| MONTHLY BAR CHART ||============================== //
|
||||||
|
|
||||||
|
const MonthlyBarChart = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { primary, secondary } = theme.palette.text;
|
||||||
|
const info = theme.palette.info.light;
|
||||||
|
|
||||||
|
const [series] = useState([
|
||||||
|
{
|
||||||
|
data: [80, 95, 70, 42, 65, 55, 78]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [options, setOptions] = useState(barChartOptions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOptions((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
colors: [info],
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [secondary, secondary, secondary, secondary, secondary, secondary, secondary]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: 'light'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [primary, info, secondary]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="chart">
|
||||||
|
<ReactApexChart options={options} series={series} type="bar" height={365} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MonthlyBarChart;
|
224
client/src/pages/dashboard/OrdersTable.jsx
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, Link, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import NumberFormat from 'react-number-format';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Dot from '../../components/@extended/Dot';
|
||||||
|
|
||||||
|
function createData(trackingNo, name, fat, carbs, protein) {
|
||||||
|
return { trackingNo, name, fat, carbs, protein };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
createData(84564564, 'Camera Lens', 40, 2, 40570),
|
||||||
|
createData(98764564, 'Laptop', 300, 0, 180139),
|
||||||
|
createData(98756325, 'Mobile', 355, 1, 90989),
|
||||||
|
createData(98652366, 'Handset', 50, 1, 10239),
|
||||||
|
createData(13286564, 'Computer Accessories', 100, 1, 83348),
|
||||||
|
createData(86739658, 'TV', 99, 0, 410780),
|
||||||
|
createData(13256498, 'Keyboard', 125, 2, 70999),
|
||||||
|
createData(98753263, 'Mouse', 89, 2, 10570),
|
||||||
|
createData(98753275, 'Desktop', 185, 1, 98063),
|
||||||
|
createData(98753291, 'Chair', 100, 0, 14001)
|
||||||
|
];
|
||||||
|
|
||||||
|
function descendingComparator(a, b, orderBy) {
|
||||||
|
if (b[orderBy] < a[orderBy]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b[orderBy] > a[orderBy]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComparator(order, orderBy) {
|
||||||
|
return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stableSort(array, comparator) {
|
||||||
|
const stabilizedThis = array.map((el, index) => [el, index]);
|
||||||
|
stabilizedThis.sort((a, b) => {
|
||||||
|
const order = comparator(a[0], b[0]);
|
||||||
|
if (order !== 0) {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
return a[1] - b[1];
|
||||||
|
});
|
||||||
|
return stabilizedThis.map((el) => el[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================|| ORDER TABLE - HEADER CELL ||============================== //
|
||||||
|
|
||||||
|
const headCells = [
|
||||||
|
{
|
||||||
|
id: 'trackingNo',
|
||||||
|
align: 'left',
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'Tracking No.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
align: 'left',
|
||||||
|
disablePadding: true,
|
||||||
|
label: 'Product Name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fat',
|
||||||
|
align: 'right',
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'Total Order'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'carbs',
|
||||||
|
align: 'left',
|
||||||
|
disablePadding: false,
|
||||||
|
|
||||||
|
label: 'Status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'protein',
|
||||||
|
align: 'right',
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'Total Amount'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==============================|| ORDER TABLE - HEADER ||============================== //
|
||||||
|
|
||||||
|
function OrderTableHead({ order, orderBy }) {
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{headCells.map((headCell) => (
|
||||||
|
<TableCell
|
||||||
|
key={headCell.id}
|
||||||
|
align={headCell.align}
|
||||||
|
padding={headCell.disablePadding ? 'none' : 'normal'}
|
||||||
|
sortDirection={orderBy === headCell.id ? order : false}
|
||||||
|
>
|
||||||
|
{headCell.label}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderTableHead.propTypes = {
|
||||||
|
order: PropTypes.string,
|
||||||
|
orderBy: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| ORDER TABLE - STATUS ||============================== //
|
||||||
|
|
||||||
|
const OrderStatus = ({ status }) => {
|
||||||
|
let color;
|
||||||
|
let title;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 0:
|
||||||
|
color = 'warning';
|
||||||
|
title = 'Pending';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
color = 'success';
|
||||||
|
title = 'Approved';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
color = 'error';
|
||||||
|
title = 'Rejected';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = 'primary';
|
||||||
|
title = 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
|
<Dot color={color} />
|
||||||
|
<Typography>{title}</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
OrderStatus.propTypes = {
|
||||||
|
status: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| ORDER TABLE ||============================== //
|
||||||
|
|
||||||
|
export default function OrderTable() {
|
||||||
|
const [order] = useState('asc');
|
||||||
|
const [orderBy] = useState('trackingNo');
|
||||||
|
const [selected] = useState([]);
|
||||||
|
|
||||||
|
const isSelected = (trackingNo) => selected.indexOf(trackingNo) !== -1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<TableContainer
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
overflowX: 'auto',
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
maxWidth: '100%',
|
||||||
|
'& td, & th': { whiteSpace: 'nowrap' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
aria-labelledby="tableTitle"
|
||||||
|
sx={{
|
||||||
|
'& .MuiTableCell-root:first-child': {
|
||||||
|
pl: 2
|
||||||
|
},
|
||||||
|
'& .MuiTableCell-root:last-child': {
|
||||||
|
pr: 3
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OrderTableHead order={order} orderBy={orderBy} />
|
||||||
|
<TableBody>
|
||||||
|
{stableSort(rows, getComparator(order, orderBy)).map((row, index) => {
|
||||||
|
const isItemSelected = isSelected(row.trackingNo);
|
||||||
|
const labelId = `enhanced-table-checkbox-${index}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
hover
|
||||||
|
role="checkbox"
|
||||||
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
|
aria-checked={isItemSelected}
|
||||||
|
tabIndex={-1}
|
||||||
|
key={row.trackingNo}
|
||||||
|
selected={isItemSelected}
|
||||||
|
>
|
||||||
|
<TableCell component="th" id={labelId} scope="row" align="left">
|
||||||
|
<Link color="secondary" component={RouterLink} to="">
|
||||||
|
{row.trackingNo}
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="left">{row.name}</TableCell>
|
||||||
|
<TableCell align="right">{row.fat}</TableCell>
|
||||||
|
<TableCell align="left">
|
||||||
|
<OrderStatus status={row.carbs} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<NumberFormat value={row.protein} displayType="text" thousandSeparator prefix="$" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
105
client/src/pages/dashboard/ReportAreaChart.jsx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
|
// chart options
|
||||||
|
const areaChartOptions = {
|
||||||
|
chart: {
|
||||||
|
height: 340,
|
||||||
|
type: 'line',
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 1.5
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
strokeDashArray: 4
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
categories: [
|
||||||
|
'2018-05-19T00:00:00.000Z',
|
||||||
|
'2018-06-19T00:00:00.000Z',
|
||||||
|
'2018-07-19T01:30:00.000Z',
|
||||||
|
'2018-08-19T02:30:00.000Z',
|
||||||
|
'2018-09-19T03:30:00.000Z',
|
||||||
|
'2018-10-19T04:30:00.000Z',
|
||||||
|
'2018-11-19T05:30:00.000Z',
|
||||||
|
'2018-12-19T06:30:00.000Z'
|
||||||
|
],
|
||||||
|
labels: {
|
||||||
|
format: 'MMM'
|
||||||
|
},
|
||||||
|
axisBorder: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
x: {
|
||||||
|
format: 'MM'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| REPORT AREA CHART ||============================== //
|
||||||
|
|
||||||
|
const ReportAreaChart = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { primary, secondary } = theme.palette.text;
|
||||||
|
const line = theme.palette.divider;
|
||||||
|
|
||||||
|
const [options, setOptions] = useState(areaChartOptions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOptions((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
colors: [theme.palette.warning.main],
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [secondary, secondary, secondary, secondary, secondary, secondary, secondary, secondary]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: line
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: 'light'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
colors: 'grey.500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [primary, secondary, line, theme]);
|
||||||
|
|
||||||
|
const [series] = useState([
|
||||||
|
{
|
||||||
|
name: 'Series 1',
|
||||||
|
data: [58, 115, 28, 83, 63, 75, 35, 55]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <ReactApexChart options={options} series={series} type="line" height={345} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportAreaChart;
|
148
client/src/pages/dashboard/SalesColumnChart.jsx
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
|
// chart options
|
||||||
|
const columnChartOptions = {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
height: 430,
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
columnWidth: '30%',
|
||||||
|
borderRadius: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: true,
|
||||||
|
width: 8,
|
||||||
|
colors: ['transparent']
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: {
|
||||||
|
text: '$ (thousands)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 1
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
y: {
|
||||||
|
formatter(val) {
|
||||||
|
return `$ ${val} thousands`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
fontFamily: `'Public Sans', sans-serif`,
|
||||||
|
offsetX: 10,
|
||||||
|
offsetY: 10,
|
||||||
|
labels: {
|
||||||
|
useSeriesColors: false
|
||||||
|
},
|
||||||
|
markers: {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
radius: '50%',
|
||||||
|
offsexX: 2,
|
||||||
|
offsexY: 2
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 600,
|
||||||
|
options: {
|
||||||
|
yaxis: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| SALES COLUMN CHART ||============================== //
|
||||||
|
|
||||||
|
const SalesColumnChart = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { primary, secondary } = theme.palette.text;
|
||||||
|
const line = theme.palette.divider;
|
||||||
|
|
||||||
|
const warning = theme.palette.warning.main;
|
||||||
|
const primaryMain = theme.palette.primary.main;
|
||||||
|
const successDark = theme.palette.success.dark;
|
||||||
|
|
||||||
|
const [series] = useState([
|
||||||
|
{
|
||||||
|
name: 'Net Profit',
|
||||||
|
data: [180, 90, 135, 114, 120, 145]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Revenue',
|
||||||
|
data: [120, 45, 78, 150, 168, 99]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [options, setOptions] = useState(columnChartOptions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOptions((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
colors: [warning, primaryMain],
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [secondary, secondary, secondary, secondary, secondary, secondary]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [secondary]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: line
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: 'light'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'right',
|
||||||
|
labels: {
|
||||||
|
colors: 'grey.500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [primary, secondary, line, warning, primaryMain, successDark]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="chart">
|
||||||
|
<ReactApexChart options={options} series={series} type="bar" height={430} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SalesColumnChart;
|
358
client/src/pages/dashboard/index.jsx
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarGroup,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
List,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Alert
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import OrdersTable from './OrdersTable';
|
||||||
|
import IncomeAreaChart from './IncomeAreaChart';
|
||||||
|
import MonthlyBarChart from './MonthlyBarChart';
|
||||||
|
import ReportAreaChart from './ReportAreaChart';
|
||||||
|
import SalesColumnChart from './SalesColumnChart';
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
import AnalyticEcommerce from '../../components/cards/statistics/AnalyticEcommerce';
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { GiftOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
import avatar1 from '../../assets/images/users/avatar-1.png';
|
||||||
|
import avatar2 from '../../assets/images/users/avatar-2.png';
|
||||||
|
import avatar3 from '../../assets/images/users/avatar-3.png';
|
||||||
|
import avatar4 from '../../assets/images/users/avatar-4.png';
|
||||||
|
import isLoggedIn from '../../isLoggedIn';
|
||||||
|
|
||||||
|
// avatar style
|
||||||
|
const avatarSX = {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
fontSize: '1rem'
|
||||||
|
};
|
||||||
|
|
||||||
|
// action style
|
||||||
|
const actionSX = {
|
||||||
|
mt: 0.75,
|
||||||
|
ml: 1,
|
||||||
|
top: 'auto',
|
||||||
|
right: 'auto',
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
transform: 'none'
|
||||||
|
};
|
||||||
|
|
||||||
|
// sales report status
|
||||||
|
const status = [
|
||||||
|
{
|
||||||
|
value: 'today',
|
||||||
|
label: 'Today'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'month',
|
||||||
|
label: 'This Month'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'year',
|
||||||
|
label: 'This Year'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==============================|| DASHBOARD - DEFAULT ||============================== //
|
||||||
|
|
||||||
|
const DashboardDefault = () => {
|
||||||
|
const [value, setValue] = useState('today');
|
||||||
|
const [slot, setSlot] = useState('week');
|
||||||
|
|
||||||
|
isLoggedIn();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Alert severity="info">Dashboard implementation currently in progress! If you want to voice your opinion on where Cosmos is going, please join us on Discord!</Alert>
|
||||||
|
</div>
|
||||||
|
<div style={{filter: 'blur(10px)', marginTop: '30px', pointerEvents: 'none'}}>
|
||||||
|
<Grid container rowSpacing={4.5} columnSpacing={2.75}>
|
||||||
|
{/* row 1 */}
|
||||||
|
<Grid item xs={12} sx={{ mb: -2.25 }}>
|
||||||
|
<Typography variant="h5">Dashboard</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||||
|
<AnalyticEcommerce title="Total Page Views" count="4,42,236" percentage={59.3} extra="35,000" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||||
|
<AnalyticEcommerce title="Total Users" count="78,250" percentage={70.5} extra="8,900" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||||
|
<AnalyticEcommerce title="Total Order" count="18,800" percentage={27.4} isLoss color="warning" extra="1,943" />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||||
|
<AnalyticEcommerce title="Total Sales" count="$35,078" percentage={27.4} isLoss color="warning" extra="$20,395" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={8} sx={{ display: { sm: 'none', md: 'block', lg: 'none' } }} />
|
||||||
|
|
||||||
|
{/* row 2 */}
|
||||||
|
<Grid item xs={12} md={7} lg={8}>
|
||||||
|
<Grid container alignItems="center" justifyContent="space-between">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5">Unique Visitor</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Stack direction="row" alignItems="center" spacing={0}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => setSlot('month')}
|
||||||
|
color={slot === 'month' ? 'primary' : 'secondary'}
|
||||||
|
variant={slot === 'month' ? 'outlined' : 'text'}
|
||||||
|
>
|
||||||
|
Month
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => setSlot('week')}
|
||||||
|
color={slot === 'week' ? 'primary' : 'secondary'}
|
||||||
|
variant={slot === 'week' ? 'outlined' : 'text'}
|
||||||
|
>
|
||||||
|
Week
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<MainCard content={false} sx={{ mt: 1.5 }}>
|
||||||
|
<Box sx={{ pt: 1, pr: 2 }}>
|
||||||
|
<IncomeAreaChart slot={slot} />
|
||||||
|
</Box>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={5} lg={4}>
|
||||||
|
<Grid container alignItems="center" justifyContent="space-between">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5">Income Overview</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item />
|
||||||
|
</Grid>
|
||||||
|
<MainCard sx={{ mt: 2 }} content={false}>
|
||||||
|
<Box sx={{ p: 3, pb: 0 }}>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
This Week Statistics
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h3">$7,650</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<MonthlyBarChart />
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* row 3 */}
|
||||||
|
<Grid item xs={12} md={7} lg={8}>
|
||||||
|
<Grid container alignItems="center" justifyContent="space-between">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5">Recent Orders</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item />
|
||||||
|
</Grid>
|
||||||
|
<MainCard sx={{ mt: 2 }} content={false}>
|
||||||
|
<OrdersTable />
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={5} lg={4}>
|
||||||
|
<Grid container alignItems="center" justifyContent="space-between">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5">Analytics Report</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item />
|
||||||
|
</Grid>
|
||||||
|
<MainCard sx={{ mt: 2 }} content={false}>
|
||||||
|
<List sx={{ p: 0, '& .MuiListItemButton-root': { py: 2 } }}>
|
||||||
|
<ListItemButton divider>
|
||||||
|
<ListItemText primary="Company Finance Growth" />
|
||||||
|
<Typography variant="h5">+45.14%</Typography>
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton divider>
|
||||||
|
<ListItemText primary="Company Expenses Ratio" />
|
||||||
|
<Typography variant="h5">0.58%</Typography>
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemText primary="Business Risk Cases" />
|
||||||
|
<Typography variant="h5">Low</Typography>
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
<ReportAreaChart />
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* row 4 */}
|
||||||
|
<Grid item xs={12} md={7} lg={8}>
|
||||||
|
<Grid container alignItems="center" justifyContent="space-between">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5">Sales Report</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
id="standard-select-currency"
|
||||||
|
size="small"
|
||||||
|
select
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
sx={{ '& .MuiInputBase-input': { py: 0.5, fontSize: '0.875rem' } }}
|
||||||
|
>
|
||||||
|
{status.map((option) => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<MainCard sx={{ mt: 1.75 }}>
|
||||||
|
<Stack spacing={1.5} sx={{ mb: -12 }}>
|
||||||
|
<Typography variant="h6" color="secondary">
|
||||||
|
Net Profit
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h4">$1560</Typography>
|
||||||
|
</Stack>
|
||||||
|
<SalesColumnChart />
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={5} lg={4}>
|
||||||
|
<Grid container alignItems="center" justifyContent="space-between">
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5">Transaction History</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item />
|
||||||
|
</Grid>
|
||||||
|
<MainCard sx={{ mt: 2 }} content={false}>
|
||||||
|
<List
|
||||||
|
component="nav"
|
||||||
|
sx={{
|
||||||
|
px: 0,
|
||||||
|
py: 0,
|
||||||
|
'& .MuiListItemButton-root': {
|
||||||
|
py: 1.5,
|
||||||
|
'& .MuiAvatar-root': avatarSX,
|
||||||
|
'& .MuiListItemSecondaryAction-root': { ...actionSX, position: 'relative' }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemButton divider>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'success.main',
|
||||||
|
bgcolor: 'success.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GiftOutlined />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText primary={<Typography variant="subtitle1">Order #002434</Typography>} secondary="Today, 2:00 AM" />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Stack alignItems="flex-end">
|
||||||
|
<Typography variant="subtitle1" noWrap>
|
||||||
|
+ $1,430
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="secondary" noWrap>
|
||||||
|
78%
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton divider>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
bgcolor: 'primary.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MessageOutlined />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={<Typography variant="subtitle1">Order #984947</Typography>}
|
||||||
|
secondary="5 August, 1:45 PM"
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Stack alignItems="flex-end">
|
||||||
|
<Typography variant="subtitle1" noWrap>
|
||||||
|
+ $302
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="secondary" noWrap>
|
||||||
|
8%
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
color: 'error.main',
|
||||||
|
bgcolor: 'error.lighter'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingOutlined />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText primary={<Typography variant="subtitle1">Order #988784</Typography>} secondary="7 hours ago" />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Stack alignItems="flex-end">
|
||||||
|
<Typography variant="subtitle1" noWrap>
|
||||||
|
+ $682
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" color="secondary" noWrap>
|
||||||
|
16%
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
</MainCard>
|
||||||
|
<MainCard sx={{ mt: 2 }}>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Grid container justifyContent="space-between" alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<Stack>
|
||||||
|
<Typography variant="h5" noWrap>
|
||||||
|
Help & Support Chat
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" color="secondary" noWrap>
|
||||||
|
Typical replay within 5 min
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<AvatarGroup sx={{ '& .MuiAvatar-root': { width: 32, height: 32 } }}>
|
||||||
|
<Avatar alt="Remy Sharp" src={avatar1} />
|
||||||
|
<Avatar alt="Travis Howard" src={avatar2} />
|
||||||
|
<Avatar alt="Cindy Baker" src={avatar3} />
|
||||||
|
<Avatar alt="Agnes Walker" src={avatar4} />
|
||||||
|
</AvatarGroup>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Button size="small" variant="contained" sx={{ textTransform: 'capitalize' }}>
|
||||||
|
Need Help?
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</MainCard>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardDefault;
|
20
client/src/pages/extra-pages/SamplePage.jsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// material-ui
|
||||||
|
import { Typography } from '@mui/material';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
|
||||||
|
// ==============================|| SAMPLE PAGE ||============================== //
|
||||||
|
|
||||||
|
const SamplePage = () => (
|
||||||
|
<MainCard title="Sample Card">
|
||||||
|
<Typography variant="body2">
|
||||||
|
Lorem ipsum dolor sit amen, consenter nipissing eli, sed do elusion tempos incident ut laborers et doolie magna alissa. Ut enif
|
||||||
|
ad minim venice, quin nostrum exercitation illampu laborings nisi ut liquid ex ea commons construal. Duos aube grue dolor in
|
||||||
|
reprehended in voltage veil esse colum doolie eu fujian bulla parian. Exceptive sin ocean cuspidate non president, sunk in culpa
|
||||||
|
qui officiate descent molls anim id est labours.
|
||||||
|
</Typography>
|
||||||
|
</MainCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SamplePage;
|
1
client/src/react-app-env.d.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="react-scripts" />
|
13
client/src/reportWebVitals.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
const reportWebVitals = (onPerfEntry) => {
|
||||||
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
|
getCLS(onPerfEntry);
|
||||||
|
getFID(onPerfEntry);
|
||||||
|
getFCP(onPerfEntry);
|
||||||
|
getLCP(onPerfEntry);
|
||||||
|
getTTFB(onPerfEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reportWebVitals;
|
28
client/src/routes/LoginRoutes.jsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Loadable from '../components/Loadable';
|
||||||
|
import MinimalLayout from '../layout/MinimalLayout';
|
||||||
|
|
||||||
|
// render - login
|
||||||
|
const AuthLogin = Loadable(lazy(() => import('../pages/authentication/Login')));
|
||||||
|
const AuthRegister = Loadable(lazy(() => import('../pages/authentication/Register')));
|
||||||
|
|
||||||
|
// ==============================|| AUTH ROUTING ||============================== //
|
||||||
|
|
||||||
|
const LoginRoutes = {
|
||||||
|
path: '/',
|
||||||
|
element: <MinimalLayout />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
element: <AuthLogin />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'register',
|
||||||
|
element: <AuthRegister />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginRoutes;
|
61
client/src/routes/MainRoutes.jsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Loadable from '../components/Loadable';
|
||||||
|
import MainLayout from '../layout/MainLayout';
|
||||||
|
|
||||||
|
// render - dashboard
|
||||||
|
const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard')));
|
||||||
|
|
||||||
|
// render - sample page
|
||||||
|
const SamplePage = Loadable(lazy(() => import('../pages/extra-pages/SamplePage')));
|
||||||
|
|
||||||
|
// render - utilities
|
||||||
|
const Typography = Loadable(lazy(() => import('../pages/components-overview/Typography')));
|
||||||
|
const Color = Loadable(lazy(() => import('../pages/components-overview/Color')));
|
||||||
|
const Shadow = Loadable(lazy(() => import('../pages/components-overview/Shadow')));
|
||||||
|
const AntIcons = Loadable(lazy(() => import('../pages/components-overview/AntIcons')));
|
||||||
|
|
||||||
|
// ==============================|| MAIN ROUTING ||============================== //
|
||||||
|
|
||||||
|
const MainRoutes = {
|
||||||
|
path: '/',
|
||||||
|
element: <MainLayout />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <DashboardDefault />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'color',
|
||||||
|
element: <Color />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'default',
|
||||||
|
element: <DashboardDefault />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'sample-page',
|
||||||
|
element: <SamplePage />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'shadow',
|
||||||
|
element: <Shadow />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'typography',
|
||||||
|
element: <Typography />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'icons/ant',
|
||||||
|
element: <AntIcons />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainRoutes;
|
11
client/src/routes/index.jsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useRoutes } from 'react-router-dom';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import LoginRoutes from './LoginRoutes';
|
||||||
|
import MainRoutes from './MainRoutes';
|
||||||
|
|
||||||
|
// ==============================|| ROUTING RENDER ||============================== //
|
||||||
|
|
||||||
|
export default function ThemeRoutes() {
|
||||||
|
return useRoutes([MainRoutes, LoginRoutes]);
|
||||||
|
}
|
5
client/src/setupTests.jsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom';
|
15
client/src/store/index.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// third-party
|
||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import reducers from './reducers';
|
||||||
|
|
||||||
|
// ==============================|| REDUX TOOLKIT - MAIN STORE ||============================== //
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: reducers
|
||||||
|
});
|
||||||
|
|
||||||
|
const { dispatch } = store;
|
||||||
|
|
||||||
|
export { store, dispatch };
|
4
client/src/store/reducers/actions.jsx
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// action - account reducer
|
||||||
|
export const LOGIN = '@auth/LOGIN';
|
||||||
|
export const LOGOUT = '@auth/LOGOUT';
|
||||||
|
export const REGISTER = '@auth/REGISTER';
|
11
client/src/store/reducers/index.jsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// third-party
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import menu from './menu';
|
||||||
|
|
||||||
|
// ==============================|| COMBINE REDUCERS ||============================== //
|
||||||
|
|
||||||
|
const reducers = combineReducers({ menu });
|
||||||
|
|
||||||
|
export default reducers;
|
38
client/src/store/reducers/menu.jsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// types
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
const initialState = {
|
||||||
|
openItem: ['dashboard'],
|
||||||
|
openComponent: 'buttons',
|
||||||
|
drawerOpen: false,
|
||||||
|
componentDrawerOpen: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================|| SLICE - MENU ||============================== //
|
||||||
|
|
||||||
|
const menu = createSlice({
|
||||||
|
name: 'menu',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
activeItem(state, action) {
|
||||||
|
state.openItem = action.payload.openItem;
|
||||||
|
},
|
||||||
|
|
||||||
|
activeComponent(state, action) {
|
||||||
|
state.openComponent = action.payload.openComponent;
|
||||||
|
},
|
||||||
|
|
||||||
|
openDrawer(state, action) {
|
||||||
|
state.drawerOpen = action.payload.drawerOpen;
|
||||||
|
},
|
||||||
|
|
||||||
|
openComponentDrawer(state, action) {
|
||||||
|
state.componentDrawerOpen = action.payload.componentDrawerOpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default menu.reducer;
|
||||||
|
|
||||||
|
export const { activeItem, activeComponent, openDrawer, openComponentDrawer } = menu.actions;
|
64
client/src/themes/index.jsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { CssBaseline, StyledEngineProvider } from '@mui/material';
|
||||||
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import Palette from './palette';
|
||||||
|
import Typography from './typography';
|
||||||
|
import CustomShadows from './shadows';
|
||||||
|
import componentsOverride from './overrides';
|
||||||
|
|
||||||
|
// ==============================|| DEFAULT THEME - MAIN ||============================== //
|
||||||
|
|
||||||
|
export default function ThemeCustomization({ children }) {
|
||||||
|
const theme = Palette('light', 'default');
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const themeTypography = Typography(`'Public Sans', sans-serif`);
|
||||||
|
const themeCustomShadows = useMemo(() => CustomShadows(theme), [theme]);
|
||||||
|
|
||||||
|
const themeOptions = useMemo(
|
||||||
|
() => ({
|
||||||
|
breakpoints: {
|
||||||
|
values: {
|
||||||
|
xs: 0,
|
||||||
|
sm: 768,
|
||||||
|
md: 1024,
|
||||||
|
lg: 1266,
|
||||||
|
xl: 1536
|
||||||
|
}
|
||||||
|
},
|
||||||
|
direction: 'ltr',
|
||||||
|
mixins: {
|
||||||
|
toolbar: {
|
||||||
|
minHeight: 60,
|
||||||
|
paddingTop: 8,
|
||||||
|
paddingBottom: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
palette: theme.palette,
|
||||||
|
customShadows: themeCustomShadows,
|
||||||
|
typography: themeTypography
|
||||||
|
}),
|
||||||
|
[theme, themeTypography, themeCustomShadows]
|
||||||
|
);
|
||||||
|
|
||||||
|
const themes = createTheme(themeOptions);
|
||||||
|
themes.components = componentsOverride(themes);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledEngineProvider injectFirst>
|
||||||
|
<ThemeProvider theme={themes}>
|
||||||
|
<CssBaseline />
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
</StyledEngineProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeCustomization.propTypes = {
|
||||||
|
children: PropTypes.node
|
||||||
|
};
|
15
client/src/themes/overrides/Badge.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// ==============================|| OVERRIDES - BADGE ||============================== //
|
||||||
|
|
||||||
|
export default function Badge(theme) {
|
||||||
|
return {
|
||||||
|
MuiBadge: {
|
||||||
|
styleOverrides: {
|
||||||
|
standard: {
|
||||||
|
minWidth: theme.spacing(2),
|
||||||
|
height: theme.spacing(2),
|
||||||
|
padding: theme.spacing(0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
28
client/src/themes/overrides/Button.jsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// ==============================|| OVERRIDES - BUTTON ||============================== //
|
||||||
|
|
||||||
|
export default function Button(theme) {
|
||||||
|
const disabledStyle = {
|
||||||
|
'&.Mui-disabled': {
|
||||||
|
backgroundColor: theme.palette.grey[200]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
MuiButton: {
|
||||||
|
defaultProps: {
|
||||||
|
disableElevation: true
|
||||||
|
},
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
fontWeight: 400
|
||||||
|
},
|
||||||
|
contained: {
|
||||||
|
...disabledStyle
|
||||||
|
},
|
||||||
|
outlined: {
|
||||||
|
...disabledStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
16
client/src/themes/overrides/CardContent.jsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// ==============================|| OVERRIDES - CARD CONTENT ||============================== //
|
||||||
|
|
||||||
|
export default function CardContent() {
|
||||||
|
return {
|
||||||
|
MuiCardContent: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
padding: 20,
|
||||||
|
'&:last-child': {
|
||||||
|
paddingBottom: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
13
client/src/themes/overrides/Checkbox.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// ==============================|| OVERRIDES - CHECKBOX ||============================== //
|
||||||
|
|
||||||
|
export default function Checkbox(theme) {
|
||||||
|
return {
|
||||||
|
MuiCheckbox: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
color: theme.palette.secondary[300]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
40
client/src/themes/overrides/Chip.jsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// ==============================|| OVERRIDES - CHIP ||============================== //
|
||||||
|
|
||||||
|
export default function Chip(theme) {
|
||||||
|
return {
|
||||||
|
MuiChip: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
borderRadius: 4,
|
||||||
|
'&:active': {
|
||||||
|
boxShadow: 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sizeLarge: {
|
||||||
|
fontSize: '1rem',
|
||||||
|
height: 40
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
backgroundColor: theme.palette.primary.lighter,
|
||||||
|
borderColor: theme.palette.primary.light,
|
||||||
|
'&.MuiChip-lightError': {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
backgroundColor: theme.palette.error.lighter,
|
||||||
|
borderColor: theme.palette.error.light
|
||||||
|
},
|
||||||
|
'&.MuiChip-lightSuccess': {
|
||||||
|
color: theme.palette.success.main,
|
||||||
|
backgroundColor: theme.palette.success.lighter,
|
||||||
|
borderColor: theme.palette.success.light
|
||||||
|
},
|
||||||
|
'&.MuiChip-lightWarning': {
|
||||||
|
color: theme.palette.warning.main,
|
||||||
|
backgroundColor: theme.palette.warning.lighter,
|
||||||
|
borderColor: theme.palette.warning.light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
28
client/src/themes/overrides/IconButton.jsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// ==============================|| OVERRIDES - ICON BUTTON ||============================== //
|
||||||
|
|
||||||
|
export default function IconButton(theme) {
|
||||||
|
return {
|
||||||
|
MuiIconButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
sizeLarge: {
|
||||||
|
width: theme.spacing(5.5),
|
||||||
|
height: theme.spacing(5.5),
|
||||||
|
fontSize: '1.25rem'
|
||||||
|
},
|
||||||
|
sizeMedium: {
|
||||||
|
width: theme.spacing(4.5),
|
||||||
|
height: theme.spacing(4.5),
|
||||||
|
fontSize: '1rem'
|
||||||
|
},
|
||||||
|
sizeSmall: {
|
||||||
|
width: theme.spacing(3.75),
|
||||||
|
height: theme.spacing(3.75),
|
||||||
|
fontSize: '0.75rem'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|