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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,103 @@
// ** React Imports
import { useState } from 'react';
// ** MUI Imports
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';
import TabContext from '@mui/lab/TabContext';
import { styled } from '@mui/material/styles';
import MuiTab from '@mui/material/Tab';
// ** Icons Imports
import AccountOutline from 'mdi-material-ui/AccountOutline';
import LockOpenOutline from 'mdi-material-ui/LockOpenOutline';
import InformationOutline from 'mdi-material-ui/InformationOutline';
// ** Demo Tabs Imports
import TabInfo from 'src/views/account-settings/TabInfo';
import TabAccount from 'src/views/account-settings/TabAccount';
import TabSecurity from 'src/views/account-settings/TabSecurity';
// ** Third Party Styles Imports
import 'react-datepicker/dist/react-datepicker.css';
const Tab = styled(MuiTab)(({ theme }) => ({
[theme.breakpoints.down('md')]: {
minWidth: 100,
},
[theme.breakpoints.down('sm')]: {
minWidth: 67,
},
}));
const TabName = styled('span')(({ theme }) => ({
lineHeight: 1.71,
fontSize: '0.875rem',
marginLeft: theme.spacing(2.4),
[theme.breakpoints.down('md')]: {
display: 'none',
},
}));
const AccountSettings = () => {
// ** State
const [value, setValue] = useState('account');
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Card>
<TabContext value={value}>
<TabList
onChange={handleChange}
aria-label="account-settings tabs"
sx={{ borderBottom: theme => `1px solid ${theme.palette.divider}` }}
>
<Tab
value="account"
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<AccountOutline />
<TabName>Account</TabName>
</Box>
}
/>
<Tab
value="security"
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<LockOpenOutline />
<TabName>Security</TabName>
</Box>
}
/>
<Tab
value="info"
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<InformationOutline />
<TabName>Info</TabName>
</Box>
}
/>
</TabList>
<TabPanel sx={{ p: 0 }} value="account">
<TabAccount />
</TabPanel>
<TabPanel sx={{ p: 0 }} value="security">
<TabSecurity />
</TabPanel>
<TabPanel sx={{ p: 0 }} value="info">
<TabInfo />
</TabPanel>
</TabContext>
</Card>
);
};
export default AccountSettings;

View File

@@ -0,0 +1,36 @@
import axios from 'axios';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import fetchCategory from 'src/api/fetchCategory';
import Loading from 'src/components/Loading';
import { setup } from 'src/lib/csrf';
import FormEditCategory from 'src/views/form-layouts/FormEditCategory';
export default function viewCategories() {
let router = useRouter();
let [category, setCategory] = useState(null);
let { cid } = router.query;
useEffect(() => {
const update = async () => {
let response = await fetchCategory({ cid: 1 });
let { data } = response;
let { category } = data;
setCategory(category);
};
if (cid) update();
}, [cid]);
if (!cid) return <>Error</>;
if (!category) return <Loading />;
return (
<>
<FormEditCategory category={category} />
</>
);
}
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,13 @@
import axios from 'axios';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import Loading from 'src/components/Loading';
import FormViewCategory from 'src/views/form-layouts/FormViewCategory';
export default function viewCategories() {
return (
<>
<FormViewCategory />
</>
);
}

View File

@@ -0,0 +1,14 @@
import { setup } from 'src/lib/csrf';
import FormAddCategories from 'src/views/form-layouts/FormAddCategories';
export default function Add() {
return (
<>
<FormAddCategories />
</>
);
}
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,49 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Card from '@mui/material/Card';
import Typography from '@mui/material/Typography';
// ** Demo Components Imports
import TableCategories from 'src/views/tables/TableCategories';
import { Box, Button, CardContent, Stack } from '@mui/material';
import AddCircleOutline from '@mui/icons-material/AddCircleOutline';
import { setup } from 'src/lib/csrf';
const CategoryTable = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<Card>
<Box title="Categories" titleTypographyProps={{ variant: 'h6' }}>
<Stack m="2rem" direction="row" alignItems={'center'} justifyContent={'space-between'}>
<Box>
<Typography variant="h5">List Categories</Typography>
</Box>
<Box>
<Link href="./add">
<Button variant="contained">
<Stack direction="row" spacing={'0.2rem'}>
<AddCircleOutline />
<Box>Add</Box>
</Stack>
</Button>
</Link>
</Box>
</Stack>
</Box>
<CardContent>
<TableCategories />
</CardContent>
</Card>
</Grid>
</Grid>
);
};
export default CategoryTable;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,227 @@
import { Box, Link, Stack, Typography } from '@mui/material';
import Image from 'next/image';
import XSRF from './chrome_FgEZO9Esbd.png';
import PageLayoutPng from './PageLayout.png';
import BlankLayout from 'src/@core/layouts/BlankLayout';
import localStorageSavingLoadingCart from './localStorageSavingLoadingCart.png';
import UpdateShouldBeGreaterThanZero from './UpdateShouldBeGreaterThanZero.png';
import table_list from './table_list.png';
import signal_flow_of_paypal from './signal_flow_of_paypal.png';
import sql_injection_test from './sql_injection_test.png';
import TopologySvg from './topology.svg';
import SecureCookiesPng from './secure_cookies.png';
const SubTitle = ({ ...props }) => {
return (
<Typography variant="h5" style={{ fontWeight: 'bold' }}>
{props.children}:
</Typography>
);
};
function Documentation() {
return (
<>
<Box>
<Typography variant="h4" sx={{ textDecoration: 'underline' }}>
Documentation (README.md)
</Typography>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>Topology:</SubTitle>
</Box>
<Box>
<Image src={TopologySvg.src} width="400px" height="400px"></Image>
</Box>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>frameowrk used / Credits:</SubTitle>
</Box>
<Stack direction="column">
<Box>
<ul>
<li>React / Nextjs / next-csrf</li>
<li>Ui framework: Material ui</li>
<li>
Themes:{' '}
<a href="https://github.com/themeselection/materio-mui-react-nextjs-admin-template-free">
materio-mui-react-nextjs-admin-template-free
</a>
</li>
<li>request handler: axios / formik / formidable</li>
<li>ORM: sequelize</li>
<li>Misc lib: dotenv, crypto, </li>
</ul>
</Box>
</Stack>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>Topology:</SubTitle>
</Box>
<Box>
<Image src={SecureCookiesPng.src} width="1646px" height="406px"></Image>
</Box>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>SQL injection test:</SubTitle>
</Box>
<Box>
<Image src={sql_injection_test.src} width={1920 / 3} height={993 / 3}></Image>
</Box>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>Paypal purchase flow:</SubTitle>
<p>according to tutorial 7.pdf</p>
</Box>
<Box>
<Image src={signal_flow_of_paypal.src} width="400px" height="400px"></Image>
</Box>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>SQL injection</SubTitle>
</Box>
<Box>list of tables</Box>
<Box>
<Image src={table_list.src} width="400px" height="400px"></Image>
</Box>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>Shopping Cart</SubTitle>
</Box>
<Box>
<Box>
<Box>
<Typography variant="h6">User will be alerted if the number enter is zero</Typography>
</Box>
</Box>
<Image src={UpdateShouldBeGreaterThanZero.src} width="400px" height="400px"></Image>
</Box>
<Box>
<SubTitle>saving loading shopping cart across sessions</SubTitle>
</Box>
<Box>
<Image src={localStorageSavingLoadingCart.src} width="400px" height="400px"></Image>
<Box>
<Box>
<Typography variant="h6">Saving:</Typography>
<Box>saving to localStorage occur on each cart update</Box>
</Box>
<Box mt={'1rem'}>
<Typography variant="h6">loading:</Typography>
<Box>loading from localStorage occur on each fresh site arriving</Box>
</Box>
</Box>
<p>keyword: localStorage</p>
</Box>
</Box>
<Box mt="2rem">
<Box>
<SubTitle>Page Layout</SubTitle>
</Box>
<Box>
<p>Page Layout controlled by Nextjs Theme provider/toolset by default.</p>
<Image src={PageLayoutPng.src} width="686px" height="128px"></Image>
<p>keyword: nextjs</p>
</Box>
<Box>
<SubTitle>NextJS</SubTitle>
</Box>
<Box>
<p>
As we're using nextjs, the role of Nginx and express ending processing http hosting were replaced by nextjs
server
</p>
<p>keyword: nextjs</p>
</Box>
<Box>
<SubTitle>XSRF</SubTitle>
</Box>
<Box>
<p>XSRF is protected by "XSRF-TOKEN" in cookies, on every request.</p>
<p>
As cookies will pass along on every request(i.e. GET, POST, PUT, DELETE). Equivalently the end point is XSRF
protected.
</p>
<p>keyword: next-xsrf</p>
</Box>
<div
style={{
backgroundImage: `url('${XSRF.src}')`,
width: '100%',
height: '600px',
backgroundPosition: 'left',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
borderRadius: '10px',
}}
></div>
</Box>
<Box></Box>
<Box></Box>
<Box></Box>
<Box mt="5rem">
<SubTitle>Citation:</SubTitle>
<Stack direction="column" gap="1rem" mt="2rem">
<Stack>
<Box>The basic setup of admin panel is following the given sample code from template provider</Box>
</Stack>
</Stack>
</Box>
<Box mt="5rem">
<SubTitle>References</SubTitle>
<Stack direction="column" gap="1rem" mt="2rem">
<Stack>
<Box>Theme</Box>
<Box>
<Link href="https://themeselection.com/item/materio-free-mui-nextjs-admin-template/">
https://themeselection.com/item/materio-free-mui-nextjs-admin-template/
</Link>
</Box>
</Stack>
<Stack>
<Box>next</Box>
<Box>
<Link href="https://nextjs.org/">https://nextjs.org/</Link>
</Box>
</Stack>
<Stack>
<Box>next-csrf</Box>
<Box>
<Link href="https://www.npmjs.com/package/next-csrf">https://www.npmjs.com/package/next-csrf</Link>
</Box>
</Stack>
</Stack>
</Box>
</>
);
}
export default Documentation;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,60 @@
// ** Demo Components Imports
import { Stack, Typography } from '@mui/material';
import { setup } from 'src/lib/csrf';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import CheckSession from 'src/api/checkSession';
const Dashboard = () => {
const route = useRouter();
useEffect(() => {
const check = async () => {
try {
let { session } = JSON.parse(localStorage.getItem('session')) || '';
// fetch(`/api/auth/check_session?session=${session}`)
// .then(res => res.json())
// .then(res_json => {
// let { status } = res_json;
// if (status != 'OK') {
// localStorage.clear('session');
// router.replace('/admin/login');
// }
// })
// .catch(error => {
// console.error(error);
// });
let data = await CheckSession({ session });
if (data['status'] != 'OK') {
return route.replace('/admin/login');
} else {
// assume 'status' is ok here
if (data['role'] == 'admin') {
return route.replace('/admin');
} else {
return route.replace('/shopfront/customer/profile');
}
}
} catch (error) {}
};
return check();
}, []);
return (
<Stack direction="row" justifyContent={'center'} alignItems={'center'} minHeight={'60vh'}>
<Typography variant="h5">hello admin !</Typography>
</Stack>
);
};
export default Dashboard;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,35 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Card from '@mui/material/Card';
import Typography from '@mui/material/Typography';
import TableInventory from 'src/views/tables/TableInventory';
import { Box, CardContent, Stack } from '@mui/material';
import { setup } from 'src/lib/csrf';
const InventoryTable = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<Card>
<Box title="Products" titleTypographyProps={{ variant: 'h6' }}>
<Stack m="2rem" direction="row" alignItems={'center'} justifyContent={'space-between'}>
<Box>
<Typography variant="h5">Update inventory</Typography>
</Box>
</Stack>
</Box>
<CardContent>
<TableInventory />
</CardContent>
</Card>
</Grid>
</Grid>
);
};
export default InventoryTable;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,241 @@
// ** React Imports
import { useContext, useState } from 'react';
// ** Next Imports
import Link from 'next/link';
import { useRouter } from 'next/router';
// ** MUI Components
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Checkbox from '@mui/material/Checkbox';
import TextField from '@mui/material/TextField';
import InputLabel from '@mui/material/InputLabel';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import CardContent from '@mui/material/CardContent';
import FormControl from '@mui/material/FormControl';
import OutlinedInput from '@mui/material/OutlinedInput';
import { styled, useTheme } from '@mui/material/styles';
import MuiCard from '@mui/material/Card';
import InputAdornment from '@mui/material/InputAdornment';
import MuiFormControlLabel from '@mui/material/FormControlLabel';
// ** Icons Imports
import Google from 'mdi-material-ui/Google';
import Github from 'mdi-material-ui/Github';
import Twitter from 'mdi-material-ui/Twitter';
import Facebook from 'mdi-material-ui/Facebook';
import EyeOutline from 'mdi-material-ui/EyeOutline';
import EyeOffOutline from 'mdi-material-ui/EyeOffOutline';
// ** Configs
import themeConfig from 'src/configs/themeConfig';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import FooterIllustrationsV1 from 'src/views/pages/auth/FooterIllustration';
import { Formik } from 'formik';
import { setup } from 'src/lib/csrf';
import { AuthContext } from 'src/contexts/auth';
// ** Styled Components
const Card = styled(MuiCard)(({ theme }) => ({
[theme.breakpoints.up('sm')]: { width: '28rem' },
}));
const LinkStyled = styled('a')(({ theme }) => ({
fontSize: '0.875rem',
textDecoration: 'none',
color: theme.palette.primary.main,
}));
const FormControlLabel = styled(MuiFormControlLabel)(({ theme }) => ({
'& .MuiFormControlLabel-label': {
fontSize: '0.875rem',
color: theme.palette.text.secondary,
},
}));
const LoginPage = () => {
const [show_password, setShowPassword] = useState(false);
const { performLogin } = useContext(AuthContext);
// ** State
const [values, setValues] = useState({
password: '',
showPassword: false,
});
// ** Hook
const theme = useTheme();
const router = useRouter();
const handleChange = prop => event => {
setValues({ ...values, [prop]: event.target.value });
};
const handleClickShowPassword = () => {
setValues({ ...values, showPassword: !values.showPassword });
setShowPassword(!show_password);
};
const handleMouseDownPassword = event => {
event.preventDefault();
};
// let initialValues = { username: 'admin@vtkhmall.com', password: 'nimda' };
let initialValues = { username: '', password: '' };
let onSubmit = async (values, { setSubmitting }) => {
setSubmitting(true);
await performLogin({ values });
};
return (
<Box className="content-center">
<Card sx={{ zIndex: 1 }}>
<CardContent sx={{ padding: theme => `${theme.spacing(12, 9, 7)} !important` }}>
<Box sx={{ mb: 8, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg
width={35}
height={29}
version="1.1"
viewBox="0 0 30 23"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Artboard" transform="translate(-95.000000, -51.000000)">
<g id="logo" transform="translate(95.000000, 50.000000)">
<path
id="Combined-Shape"
fill={theme.palette.primary.main}
d="M30,21.3918362 C30,21.7535219 29.9019196,22.1084381 29.7162004,22.4188007 C29.1490236,23.366632 27.9208668,23.6752135 26.9730355,23.1080366 L26.9730355,23.1080366 L23.714971,21.1584295 C23.1114106,20.7972624 22.7419355,20.1455972 22.7419355,19.4422291 L22.7419355,19.4422291 L22.741,12.7425689 L15,17.1774194 L7.258,12.7425689 L7.25806452,19.4422291 C7.25806452,20.1455972 6.88858935,20.7972624 6.28502902,21.1584295 L3.0269645,23.1080366 C2.07913318,23.6752135 0.850976404,23.366632 0.283799571,22.4188007 C0.0980803893,22.1084381 2.0190442e-15,21.7535219 0,21.3918362 L0,3.58469444 L0.00548573643,3.43543209 L0.00548573643,3.43543209 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 L15,9.19354839 L26.9548759,1.86636639 C27.2693965,1.67359571 27.6311047,1.5715689 28,1.5715689 C29.1045695,1.5715689 30,2.4669994 30,3.5715689 L30,3.5715689 Z"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.7505183 7.25806452 16.8305646"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.6445567 7.25806452 15.1370162"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.7417372 30 16.9537453"
transform="translate(26.370968, 12.771227) scale(-1, 1) translate(-26.370968, -12.771227) "
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.6409734 30 15.2601969"
transform="translate(26.370968, 11.924453) scale(-1, 1) translate(-26.370968, -11.924453) "
/>
<path
id="Rectangle"
fillOpacity="0.15"
fill={theme.palette.common.white}
d="M3.04512412,1.86636639 L15,9.19354839 L15,9.19354839 L15,17.1774194 L0,8.58649679 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 Z"
/>
<path
id="Rectangle"
fillOpacity="0.35"
fill={theme.palette.common.white}
transform="translate(22.500000, 8.588710) scale(-1, 1) translate(-22.500000, -8.588710) "
d="M18.0451241,1.86636639 L30,9.19354839 L30,9.19354839 L30,17.1774194 L15,8.58649679 L15,3.5715689 C15,2.4669994 15.8954305,1.5715689 17,1.5715689 C17.3688953,1.5715689 17.7306035,1.67359571 18.0451241,1.86636639 Z"
/>
</g>
</g>
</g>
</svg>
<Typography
variant="h6"
sx={{
ml: 3,
lineHeight: 1,
fontWeight: 600,
textTransform: 'uppercase',
fontSize: '1.5rem !important',
}}
>
{themeConfig.templateName}
</Typography>
</Box>
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue }) => (
<form noValidate autoComplete="off" onSubmit={e => e.preventDefault()}>
<TextField
autoFocus
fullWidth
id="username"
label="Email"
sx={{ marginBottom: 4 }}
value={values.username}
onChange={handleChange('username')}
/>
<FormControl fullWidth>
<InputLabel htmlFor="auth-login-password">Password</InputLabel>
<OutlinedInput
label="Password"
value={values.password}
id="auth-login-password"
onChange={handleChange('password')}
type={show_password ? 'text' : 'password'}
endAdornment={
<InputAdornment position="end">
<IconButton
edge="end"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
aria-label="toggle password visibility"
>
{show_password ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
</FormControl>
<Box
sx={{
mb: 4,
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
justifyContent: 'space-between',
}}
></Box>
<Button onClick={handleSubmit} fullWidth size="large" variant="contained" sx={{ marginBottom: 7 }}>
Login
</Button>
</form>
)}
</Formik>
<Link href="/">
<Button fullWidth size="large" variant="outlined" sx={{ marginBottom: 7 }} onClick={() => router.push('/')}>
{'<'} back to shopfront
</Button>
</Link>
</CardContent>
</Card>
</Box>
);
};
LoginPage.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default LoginPage;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,63 @@
// ** React Imports
import { useContext, useEffect } from 'react';
// ** Next Imports
import { useRouter } from 'next/router';
// ** MUI Components
import Box from '@mui/material/Box';
import { styled } from '@mui/material/styles';
import MuiCard from '@mui/material/Card';
import MuiFormControlLabel from '@mui/material/FormControlLabel';
// ** Icons Imports
// ** Configs
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import { Stack } from '@mui/material';
import { AuthContext } from 'src/contexts/auth';
// ** Styled Components
const Card = styled(MuiCard)(({ theme }) => ({
[theme.breakpoints.up('sm')]: { width: '28rem' },
}));
const LinkStyled = styled('a')(({ theme }) => ({
fontSize: '0.875rem',
textDecoration: 'none',
color: theme.palette.primary.main,
}));
const FormControlLabel = styled(MuiFormControlLabel)(({ theme }) => ({
'& .MuiFormControlLabel-label': {
fontSize: '0.875rem',
color: theme.palette.text.secondary,
},
}));
const LogoutPage = () => {
const router = useRouter();
const { performLogout } = useContext(AuthContext);
useEffect(() => {
performLogout();
router.push('/');
}, []);
return (
<Box className="content-center">
<Stack direction="row" justifyContent="center" alignItems="center">
logging you out
</Stack>
</Box>
);
};
LogoutPage.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default LogoutPage;

View File

@@ -0,0 +1,35 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Card from '@mui/material/Card';
import Typography from '@mui/material/Typography';
import TableOrder from 'src/views/tables/TableOrder';
import { Box, CardContent, Stack } from '@mui/material';
import { setup } from 'src/lib/csrf';
const OrderTable = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<Card>
<Box title="Products" titleTypographyProps={{ variant: 'h6' }}>
<Stack m="2rem" direction="row" alignItems={'center'} justifyContent={'space-between'}>
<Box>
<Typography variant="h5">Update order</Typography>
</Box>
</Stack>
</Box>
<CardContent>
<TableOrder />
</CardContent>
</Card>
</Grid>
</Grid>
);
};
export default OrderTable;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,41 @@
import axios from 'axios';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import fetchProduct from 'src/api/fetchProduct';
import Loading from 'src/components/Loading';
import { setup } from 'src/lib/csrf';
import FormEditProduct from 'src/views/form-layouts/FormEditProduct';
export default function viewProducts() {
let router = useRouter();
let [product, setProduct] = useState(null);
let { pid } = router.query;
useEffect(() => {
const update = async () => {
let res = await fetchProduct({ pid });
let { data } = res;
let { product } = data;
setProduct(product);
};
if (pid) update();
}, [pid]);
if (!product)
return (
<>
<Loading />
</>
);
return (
<>
<FormEditProduct product={product} />
</>
);
}
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,35 @@
import axios from 'axios';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import fetchProduct from 'src/api/fetchProduct';
import Loading from 'src/components/Loading';
import FormViewProduct from 'src/views/form-layouts/FormViewProduct';
export default function viewCategories() {
let router = useRouter();
let [product, setProduct] = useState(null);
let { pid } = router.query;
useEffect(() => {
const update = async () => {
let res = await fetchProduct({ pid });
let { data } = res;
let { product } = data;
setProduct(product);
};
if (pid) update();
}, [pid]);
if (!product)
return (
<>
<Loading />
</>
);
return (
<>
<FormViewProduct product={product} />
</>
);
}

View File

@@ -0,0 +1,14 @@
import { setup } from 'src/lib/csrf';
import FormAddProducts from 'src/views/form-layouts/FormAddProducts';
export default function Add() {
return (
<>
<FormAddProducts />
</>
);
}
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,49 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Card from '@mui/material/Card';
import Typography from '@mui/material/Typography';
// ** Demo Components Imports
import TableProducts from 'src/views/tables/TableProducts';
import { Box, Button, CardContent, Stack } from '@mui/material';
import AddCircleOutline from '@mui/icons-material/AddCircleOutline';
import { setup } from 'src/lib/csrf';
const ProductTable = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<Card>
<Box title="Products" titleTypographyProps={{ variant: 'h6' }}>
<Stack m="2rem" direction="row" alignItems={'center'} justifyContent={'space-between'}>
<Box>
<Typography variant="h5">List Products</Typography>
</Box>
<Box>
<Link href="./add">
<Button variant="contained">
<Stack direction="row" spacing={'0.2rem'}>
<AddCircleOutline />
<Box>Add</Box>
</Stack>
</Button>
</Link>
</Box>
</Stack>
</Box>
<CardContent>
<TableProducts />
</CardContent>
</Card>
</Grid>
</Grid>
);
};
export default ProductTable;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});

View File

@@ -0,0 +1,64 @@
const bcrypt = require('bcrypt');
import { csrf } from 'src/lib/csrf';
import { Auth } from './model';
function sessionGenerate() {
return 'obvoiusly_this_is_a_complicated_session_key_' + Math.floor(Math.random() * 999);
}
async function hashPassword(plainTextPassword) {
const saltRounds = 10;
return await bcrypt.hash(plainTextPassword, saltRounds);
}
function compareAsync(param1, param2) {
return new Promise(function (resolve, reject) {
bcrypt.compare(param1, param2, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
async function ChangePassword(req) {
return new Promise(async (res, rej) => {
let { username, old_password, new_password } = JSON.parse(req.body);
let new_password_hashed = await hashPassword(new_password);
console.log({ username, old_password, new_password });
Auth.findOne({ where: { username } })
.then(user_found => compareAsync(old_password, user_found.password))
.then(compare_result => {
console.log({ compare_result });
if (compare_result) {
return Auth.update({ password: new_password_hashed }, { where: { username }, limit: 1 });
} else {
throw new Error('wrong password');
}
})
.then(() => {
res({ status: 'password change ok' });
})
.catch(err => {
console.log('Auth.findOne err:', err);
rej({ status: 'password change error' });
});
});
}
async function handler(req, res) {
try {
let result = await ChangePassword(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,32 @@
import { csrf } from 'src/lib/csrf';
import { Auth } from './model';
async function checkSession(req) {
try {
let { session } = req.headers;
console.log({ session });
const user_session_found = await Auth.findOne({ where: { session } });
if (user_session_found) {
return { status: 'OK', username: user_session_found['dataValues'].username, role: user_session_found.role };
}
return { status: 'ERROR SESSION NOT FOUND' };
} catch (error) {
console.error(error);
return { status: 'ERROR SESSION NOT FOUND' };
}
}
async function handler(req, res) {
try {
let result = await checkSession(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'checkSession error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,22 @@
import { csrf } from 'src/lib/csrf';
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,62 @@
const bcrypt = require('bcrypt');
import { csrf } from 'src/lib/csrf';
import { Auth } from './model';
function sessionGenerate() {
return 'obvoiusly_this_is_a_complicated_session_key_' + Math.floor(Math.random() * 999);
}
function compareAsync(param1, param2) {
return new Promise(function (resolve, reject) {
bcrypt.compare(param1, param2, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
async function login(req) {
return new Promise(async (res, rej) => {
let { username: incoming_username, password: incoming_password } = JSON.parse(req.body);
Auth.findOne({ where: { username: incoming_username } })
.then(user_found => compareAsync(incoming_password, user_found.password))
.then(async cmp_result => {
if (cmp_result) {
let session = sessionGenerate();
let user_found = await Auth.update({ session }, { where: { username: incoming_username } });
if (user_found.role == 'admin') {
console.log('admin login OK');
return res({ status: 'admin login OK', session, username: incoming_username });
}
console.log('customer login OK');
return res({ status: 'customer login OK', session, username: incoming_username });
} else {
rej({ status: 'error' });
}
})
.catch(err => {
console.log('Auth.findOne err:', err);
rej({ status: 'error' });
});
});
}
async function handler(req, res) {
try {
let result = await login(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,23 @@
const { Sequelize, DataTypes } = require('sequelize');
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env;
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
port: DB_PORT,
dialect: 'mysql',
});
const Auth = sequelize.define(
'Auths',
{
uid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
username: { type: DataTypes.STRING, allowNull: false },
password: { type: DataTypes.STRING, allowNull: false },
session: { type: DataTypes.STRING, allowNull: false, defaultValue: '' },
role: { type: DataTypes.STRING, allowNull: false },
},
{ timestamps: false },
);
export { Auth, sequelize };

View File

@@ -0,0 +1,50 @@
import { Category, sequelize } from './model';
import formidable from 'formidable';
var randomize = require('randomatic');
import rimraf from 'fs-extra';
import { csrf } from 'src/lib/csrf';
const path = require('path');
const fs = require('fs');
async function add(req) {
try {
const form = formidable({});
await sequelize.authenticate();
let fields;
let files;
[fields, files] = await form.parse(req);
let name = fields['name'][0];
let description = fields['description'][0];
let category = await Category.create({
name,
description,
});
return { status: 'OK', category };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await add(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'add error' });
}
}
export const config = {
api: {
bodyParser: false,
},
};
export default csrf(handler);

View File

@@ -0,0 +1,29 @@
import { csrf } from 'src/lib/csrf';
import { Category, sequelize } from './model';
async function destroyCategory(req) {
try {
await sequelize.authenticate();
let { cid } = req.query;
let category = await Category.findOne({ where: { cid } });
await category.destroy();
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await destroyCategory(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,18 @@
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}

View File

@@ -0,0 +1,24 @@
import { Category, sequelize } from './model';
async function list(req) {
try {
await sequelize.authenticate();
const categories = await Category.findAll();
return { status: 'OK', categories };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,21 @@
const { Sequelize, DataTypes } = require('sequelize');
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env;
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
port: DB_PORT,
dialect: 'mysql',
});
const Category = sequelize.define(
'Categories',
{
cid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
name: { type: DataTypes.STRING, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false },
},
{ timestamps: false },
);
export { Category, sequelize };

View File

@@ -0,0 +1,34 @@
import { csrf } from 'src/lib/csrf';
import { Category, sequelize } from './model';
async function update(req) {
try {
await sequelize.authenticate();
let { name, description, cid } = req.body;
let category = await Category.update(
{
name,
description,
},
{ where: { cid } },
);
return { status: 'OK', category };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await update(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'update error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,27 @@
import { Category, sequelize } from './model';
async function view(req) {
try {
let { cid } = req.query;
console.log(cid);
await sequelize.authenticate();
const category = await Category.findOne({ where: { cid } });
return { status: 'OK', category };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await view(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,31 @@
const fs = require('fs');
const path = require('path');
var mime = require('mime-types');
async function get(req) {
try {
let { dir_prefix } = req.query;
let zip_file_path = `./tmp/${dir_prefix}/image.jpg`;
let file_content = await fs.readFileSync(zip_file_path);
let filename = path.basename(zip_file_path);
let file_mime = mime.lookup(zip_file_path);
return [file_mime, file_content, filename];
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let [mime, buffer, filename] = await get(req);
res.setHeader('Content-Type', mime);
// res.setHeader('Content-Disposition', `attachment; filename=${filename}`);
return res.status(200).send(buffer);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}

View File

@@ -0,0 +1,22 @@
import { csrf } from 'src/lib/csrf';
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default csrf(handler);

BIN
tsc1877/task1/project/admin/src/pages/api/files/image.jpg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,29 @@
import { csrf } from 'src/lib/csrf';
import { Item, sequelize } from './model';
async function check_in(req) {
try {
await sequelize.authenticate();
let { count, pid, description } = req.body;
for (let i = 0; i < count; i++) {
await Item.create({ pid, description });
}
return { status: 'OK', count, pid };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await check_in(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'add error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,65 @@
import { Item, sequelize } from './model';
async function check_out(req) {
let t;
try {
let cart_items = req.body;
// // create sequelize transaction
t = await sequelize.transaction();
await sequelize.authenticate();
for (let i = 0; i < cart_items.length; i++) {
let cart_item = cart_items[i];
let { pid, quantity } = cart_item;
let item_remaining = Item.count({ where: { pid, sold: false } }, { transaction: t });
if (quantity > item_remaining) {
throw new Error('error due to ordered quantity is greater than item remaining');
} else {
console.log('remaining check passed');
}
}
// item remaining check passed
// proceed to mark item sold
for (let i = 0; i < cart_items.length; i++) {
let cart_item = cart_items[i];
let { pid, quantity } = cart_item;
for (let j = 1; j <= quantity; j++) {
let item;
item = await Item.findOne({ where: { pid, sold: false } }, { transaction: t });
await Item.update({ sold: true }, { where: { item_id: item.item_id } }, { transaction: t });
console.log(item.item_id);
}
}
await t.commit();
// return { status: 'OK', message: 'checkout done' };
} catch (error) {
await t?.rollback();
console.error(error);
return { status: 'ERROR', message: 'cart checkout error' };
}
}
export default async function handler(req, res) {
try {
let result = await check_out(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'add error' });
}
}
export const config = {
api: {
bodyParser: true,
},
};

View File

@@ -0,0 +1,27 @@
import { Item, sequelize } from './model';
async function count(req) {
try {
let { pid } = req.query;
console.log(pid);
await sequelize.authenticate();
const item = await Item.findOne({ where: { pid } });
return { status: 'OK', item };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await count(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,18 @@
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}

View File

@@ -0,0 +1,22 @@
const { Sequelize, DataTypes } = require('sequelize');
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env;
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
port: DB_PORT,
dialect: 'mysql',
});
const Item = sequelize.define(
'Items',
{
item_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
pid: { type: DataTypes.INTEGER },
description: { type: DataTypes.STRING, allowNull: false },
sold: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
},
{ timestamps: false },
);
export { Item, sequelize };

View File

@@ -0,0 +1,28 @@
import { csrf } from 'src/lib/csrf';
import { Order, sequelize } from './model';
async function CancelOrder(req) {
try {
await sequelize.authenticate();
let { custom_id } = req.body;
let order = await Order.update({ order_status: 'CANCELLED' }, { where: { custom_id } });
return { status: 'OK', order };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await CancelOrder(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'update error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,96 @@
import { Order, sequelize } from './model';
import formidable from 'formidable';
var randomize = require('randomatic');
import rimraf from 'fs-extra';
const path = require('path');
const fs = require('fs');
import crypto from 'crypto';
import { Auth } from '../auth/model';
import { Product } from '../products/model';
import { csrf } from 'src/lib/csrf';
function genSalt() {
return crypto.randomBytes(16).toString('hex');
}
function createMD5HashWithSalt(inputString, salt) {
// Prepend the salt to the inputString
const saltedInput = salt + inputString;
// Proceed with the MD5 hash
const hash = crypto.createHash('md5').update(saltedInput).digest('hex');
// Optionally, you might want to return both the hash and the salt
// for storage, so you can verify the input against the hash later.
return { salt, hash };
}
async function GetOrderDetails(req) {
try {
let { cart, currency_code } = req.body;
let { session } = req.headers;
await sequelize.authenticate();
let user_found = await Auth.findOne({ where: { session } });
let items = [];
for (let i = 0; i < cart.length; i++) {
let cart_item = cart[i];
let product_found = await Product.findOne({ where: { pid: cart_item.pid } });
items.push({
item_name: product_found['dataValues'].name,
currency_code: currency_code,
unit_price: product_found['dataValues'].price,
quantity: cart_item.quantity,
});
}
//get total price from cart
let total_price = cart.reduce((acc, ci) => acc + ci.quantity * ci.unit_price, 0);
let amount = cart.reduce((acc, ci) => acc + ci.quantity * ci.unit_price, 0);
let salt = genSalt();
let req_digest = createMD5HashWithSalt(
{
order_status: 'ORDER_SUBMITTED',
invoice_id: crypto.randomUUID(),
items: JSON.stringify(items),
username: user_found.username,
total_price: total_price,
currency_code: currency_code,
amount: amount,
},
salt,
)['hash'];
await Order.create({
order_status: 'ORDER_SUBMITTED',
invoice_id: crypto.randomUUID(),
items: JSON.stringify(items),
username: user_found.username,
custom_id: req_digest,
total_price,
currency_code,
amount: amount,
salt,
});
return { status: 'OK', order_details: { custom_id: req_digest } };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await GetOrderDetails(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'add error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,18 @@
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}

View File

@@ -0,0 +1,36 @@
import { Order, sequelize } from './model';
import { Auth } from '../auth/model';
async function list(req) {
try {
let session = req.headers['session'];
console.log(session);
const checkIfAdmin = user => user['dataValues'].role == 'admin';
await sequelize.authenticate();
const user = await Auth.findOne({ where: { session } });
console.log(user);
if (checkIfAdmin(user)) {
const orders = await Order.findAll();
return { status: 'OK', orders };
} else {
return { status: 'ERROR', msg: 'Permission denied', orders: [] };
}
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,37 @@
import { Order, sequelize } from './model';
import { Auth } from '../auth/model';
async function listByCustomer(req) {
try {
let session = req.headers['session'];
console.log(session);
await sequelize.authenticate();
let user = await Auth.findOne({ where: { session } });
if (user) {
const { username } = user['dataValues'];
console.log({ username });
const orders = await Order.findAll({ where: { username } });
return { status: 'OK', orders };
} else {
return { status: 'OK', orders: [] };
}
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await listByCustomer(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,28 @@
const { Sequelize, DataTypes } = require('sequelize');
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env;
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
port: DB_PORT,
dialect: 'mysql',
});
const Order = sequelize.define(
'Orders',
{
order_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
invoice_id: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
custom_id: { type: DataTypes.STRING, allowNull: false },
amount: { type: DataTypes.STRING, allowNull: false },
total_price: { type: DataTypes.DECIMAL, allowNull: false },
currency_code: { type: DataTypes.STRING, allowNull: false },
items: { type: DataTypes.STRING, allowNull: false },
order_status: { type: DataTypes.STRING, allowNull: false, defaultValue: 'NOT_PAID' },
salt: { type: DataTypes.STRING, allowNull: false },
},
{ timestamps: true },
);
export { Order, sequelize };

View File

@@ -0,0 +1,28 @@
import { csrf } from 'src/lib/csrf';
import { Order, sequelize } from './model';
async function SaveOrder(req) {
try {
await sequelize.authenticate();
let { custom_id } = req.body;
let order = await Order.update({ order_status: 'APPROVED' }, { where: { custom_id } });
return { status: 'OK', order };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await SaveOrder(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'update error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,27 @@
import { Category, sequelize } from './model';
async function view(req) {
try {
let { cid } = req.query;
console.log(cid);
await sequelize.authenticate();
const category = await Category.findOne({ where: { cid } });
return { status: 'OK', category };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await view(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,98 @@
import { Product, sequelize } from './model';
import formidable from 'formidable';
var randomize = require('randomatic');
import rimraf from 'fs-extra';
import { csrf } from 'src/lib/csrf';
const path = require('path');
const fs = require('fs');
async function helloworld(req) {
try {
const form = formidable({});
await sequelize.authenticate();
let fields;
let files;
[fields, files] = await form.parse(req);
let dir_prefix;
let name = fields['name'][0];
let description = fields['description'][0];
let cid = fields['cid'][0];
let price = fields['price'][0];
let clear_image = fields['clear_image'][0];
let product;
console.log('have file ?');
const haveImage = files => {
return files?.product_image?.length > 0;
};
console.log(haveImage(files));
if (haveImage(files)) {
dir_prefix = randomize('A', 5);
let product_image = files['product_image'];
for (var i = 0; i < files.product_image.length; i++) {
product_image = files.product_image[i];
let dest_path = path.join('./tmp', dir_prefix) + path.sep;
try {
rimraf.removeSync(dest_path);
} catch (error) {
console.error('error during removing directory, ignoring');
}
fs.mkdirSync(dest_path, { recursive: true });
let bs = fs.readFileSync(product_image.filepath);
fs.writeFileSync(dest_path + 'image.jpg', bs);
}
}
if (haveImage(files)) {
product = await Product.create({
name,
description,
cid,
price,
product_image: dir_prefix,
});
} else {
product = await Product.create({
name,
description,
cid,
price,
product_image: '',
});
}
return { status: 'OK', product };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export const config = {
api: {
bodyParser: false,
},
};
export default csrf(handler);

View File

@@ -0,0 +1,29 @@
import { csrf } from 'src/lib/csrf';
import { Product, sequelize } from './model';
async function destroyProduct(req) {
try {
await sequelize.authenticate();
let { pid } = req.query;
let product = await Product.findOne({ where: { pid } });
await product.destroy();
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await destroyProduct(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,18 @@
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}

View File

@@ -0,0 +1,41 @@
import { csrf } from 'src/lib/csrf';
import { Item } from '../inventory/model';
import { Product, sequelize } from './model';
async function list(req) {
try {
await sequelize.authenticate();
const products = await Product.findAll();
for (var i = 0; i < products.length; i++) {
let product_data = products[i].dataValues;
let { pid } = product_data;
let product_count = await Item.count({ where: { pid } });
products[i].dataValues['count'] = product_count;
let product_unsold_count = await Item.count({ where: { pid, sold: false } });
products[i].dataValues['unsold_count'] = product_unsold_count;
let product_sold_count = await Item.count({ where: { pid, sold: true } });
products[i].dataValues['sold_count'] = product_sold_count;
}
return { status: 'OK', products };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default csrf(handler);

View File

@@ -0,0 +1,40 @@
import { promise } from 'bcrypt/promises';
import { Item } from '../inventory/model';
import { Product, sequelize } from './model';
async function listByCategory(req) {
try {
await sequelize.authenticate();
let { cid } = req.query;
const products = await Product.findAll({ where: { cid } });
for (var i = 0; i < products.length; i++) {
let product_data = products[i].dataValues;
let { pid } = product_data;
let product_count = await Item.count({ where: { pid } });
products[i].dataValues['count'] = product_count;
let product_unsold_count = await Item.count({ where: { pid, sold: false } });
products[i].dataValues['unsold_count'] = product_unsold_count;
let product_sold_count = await Item.count({ where: { pid, sold: true } });
products[i].dataValues['sold_count'] = product_sold_count;
}
return { status: 'OK', products };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await listByCategory(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,40 @@
import { promise } from 'bcrypt/promises';
import { Item } from '../inventory/model';
import { Product, sequelize } from './model';
async function listByCategoryUnsold(req) {
try {
await sequelize.authenticate();
let { cid } = req.query;
const products = await Product.findAll({ where: { cid } });
for (var i = 0; i < products.length; i++) {
let product_data = products[i].dataValues;
let { pid } = product_data;
let product_count = await Item.count({ where: { pid, sold: false } });
products[i].dataValues['count'] = product_count;
let product_unsold_count = await Item.count({ where: { pid, sold: false } });
products[i].dataValues['unsold_count'] = product_unsold_count;
let product_sold_count = await Item.count({ where: { pid, sold: true } });
products[i].dataValues['sold_count'] = product_sold_count;
}
return { status: 'OK', products };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await listByCategoryUnsold(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,24 @@
const { Sequelize, DataTypes } = require('sequelize');
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env;
const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
host: DB_HOST,
port: DB_PORT,
dialect: 'mysql',
});
const Product = sequelize.define(
'Products',
{
pid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
cid: { type: DataTypes.INTEGER, allowNull: false },
name: { type: DataTypes.STRING, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false },
price: { type: DataTypes.DECIMAL, allowNull: false },
product_image: { type: DataTypes.STRING, allowNull: true, defaultValue: '' },
},
{ timestamps: false },
);
export { Product, sequelize };

View File

@@ -0,0 +1,25 @@
import { Product, sequelize } from './model';
async function update(req) {
try {
await sequelize.authenticate();
let { name, description, cid, pid, price } = req.body;
let category = await Product.update({ name, description, cid: cid, price }, { where: { pid } });
return { status: 'OK', category };
} catch (error) {
console.error(error);
}
}
export default async function handler(req, res) {
try {
let result = await update(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'update error' });
}
}

View File

@@ -0,0 +1,92 @@
import { Product, sequelize } from './model';
import formidable from 'formidable';
var randomize = require('randomatic');
import rimraf from 'fs-extra';
import { csrf } from 'src/lib/csrf';
const path = require('path');
const fs = require('fs');
async function update(req) {
try {
const form = formidable({});
await sequelize.authenticate();
let fields;
let files;
[fields, files] = await form.parse(req);
let dir_prefix;
let name = fields['name'][0];
let description = fields['description'][0];
let cid = fields['cid'][0];
let pid = fields['pid'][0];
let price = fields['price'][0];
let clear_image = fields['clear_image'][0];
let category;
console.log('have file ?');
const haveImage = files => {
return files?.product_image?.length > 0;
};
console.log(haveImage(files));
if (haveImage(files)) {
dir_prefix = randomize('A', 5);
let product_image = files['product_image'];
for (var i = 0; i < files.product_image.length; i++) {
product_image = files.product_image[i];
let dest_path = path.join('./tmp', dir_prefix) + path.sep;
try {
rimraf.removeSync(dest_path);
} catch (error) {
console.error('error during removing directory, ignoring');
}
fs.mkdirSync(dest_path, { recursive: true });
let bs = fs.readFileSync(product_image.filepath);
fs.writeFileSync(dest_path + 'image.jpg', bs);
}
}
if (haveImage(files)) {
category = await Product.update(
{ name, description, cid: cid, price, product_image: dir_prefix },
{ where: { pid } },
);
} else {
if (clear_image == 'true') {
category = await Product.update({ name, description, cid: cid, price, product_image: '' }, { where: { pid } });
} else {
category = await Product.update({ name, description, cid: cid, price }, { where: { pid } });
}
}
return { status: 'OK', category };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await update(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'update error' });
}
}
export const config = {
api: {
bodyParser: false,
},
};
export default csrf(handler);

View File

@@ -0,0 +1,42 @@
import { Item } from '../inventory/model';
import { Product, sequelize } from './model';
async function view(req) {
let { pid } = req.query;
await sequelize.authenticate();
const product = await Product.findOne({ where: { pid } });
if (product) {
let product_count = await Item.count({ where: { pid, sold: false } });
product.dataValues['count'] = product_count;
let product_unsold_count = await Item.count({ where: { pid, sold: false } });
product.dataValues['unsold_count'] = product_unsold_count;
let product_sold_count = await Item.count({ where: { pid, sold: true } });
product.dataValues['sold_count'] = product_sold_count;
return { status: 'OK', product, product_count };
} else {
throw new Error('PID_NOT_FOUND');
}
}
export default async function handler(req, res) {
try {
let result = await view(req);
return res.status(200).send(result);
} catch (err) {
if (err.message === 'PID_NOT_FOUND') {
// res redirect to '/'
res.redirect(302, '/');
return;
}
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,42 @@
import { Item } from '../inventory/model';
import { Product, sequelize } from './model';
async function view(req) {
let { pid } = req.query;
await sequelize.authenticate();
const product = await Product.findOne({ where: { pid } });
if (product) {
let product_count = await Item.count({ where: { pid, sold: false } });
product.dataValues['count'] = product_count;
let product_unsold_count = await Item.count({ where: { pid, sold: false } });
product.dataValues['unsold_count'] = product_unsold_count;
let product_sold_count = await Item.count({ where: { pid, sold: true } });
product.dataValues['sold_count'] = product_sold_count;
return { status: 'OK', product, product_count };
} else {
throw new Error('PID_NOT_FOUND');
}
}
export default async function handler(req, res) {
try {
let result = await view(req);
return res.status(200).send(result);
} catch (err) {
if (err.message === 'PID_NOT_FOUND') {
// res redirect to '/'
res.redirect(302, '/');
return;
}
console.log(err);
res.status(200).send({ status: 'error', message: 'list error' });
}
}

View File

@@ -0,0 +1,83 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
// ** Demo Components Imports
import CardUser from 'src/views/cards/CardUser';
import CardImgTop from 'src/views/cards/CardImgTop';
import CardMobile from 'src/views/cards/CardMobile';
import CardSupport from 'src/views/cards/CardSupport';
import CardTwitter from 'src/views/cards/CardTwitter';
import CardFacebook from 'src/views/cards/CardFacebook';
import CardLinkedIn from 'src/views/cards/CardLinkedIn';
import CardAppleWatch from 'src/views/cards/CardAppleWatch';
import CardMembership from 'src/views/cards/CardMembership';
import CardInfluencer from 'src/views/cards/CardInfluencer';
import CardNavigation from 'src/views/cards/CardNavigation';
import CardWithCollapse from 'src/views/cards/CardWithCollapse';
import CardVerticalRatings from 'src/views/cards/CardVerticalRatings';
import CardNavigationCenter from 'src/views/cards/CardNavigationCenter';
import CardHorizontalRatings from 'src/views/cards/CardHorizontalRatings';
const CardBasic = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12} sx={{ paddingBottom: 4 }}>
<Typography variant="h5">Basic Cards</Typography>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardImgTop />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardUser />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardWithCollapse />
</Grid>
<Grid item xs={12} sm={6}>
<CardMobile />
</Grid>
<Grid item xs={12} sm={6}>
<CardHorizontalRatings />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardAppleWatch />
</Grid>
<Grid item xs={12} md={8}>
<CardMembership />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardInfluencer />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardVerticalRatings />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardSupport />
</Grid>
<Grid item xs={12} sx={{ pb: 4, pt: theme => `${theme.spacing(17.5)} !important` }}>
<Typography variant="h5">Navigation Cards</Typography>
</Grid>
<Grid item xs={12} md={6}>
<CardNavigation />
</Grid>
<Grid item xs={12} md={6}>
<CardNavigationCenter />
</Grid>
<Grid item xs={12} sx={{ pb: 4, pt: theme => `${theme.spacing(17.5)} !important` }}>
<Typography variant="h5">Solid Cards</Typography>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardTwitter />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardFacebook />
</Grid>
<Grid item xs={12} sm={6} md={4}>
<CardLinkedIn />
</Grid>
</Grid>
);
};
export default CardBasic;

View File

@@ -0,0 +1,37 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
// ** Styled Component
import DatePickerWrapper from 'src/@core/styles/libs/react-datepicker';
// ** Demo Components Imports
import FormLayoutsBasic from 'src/views/form-layouts/FormLayoutsBasic';
import FormLayoutsIcons from 'src/views/form-layouts/FormLayoutsIcons';
import FormLayoutsSeparator from 'src/views/form-layouts/FormLayoutsSeparator';
import FormLayoutsAlignment from 'src/views/form-layouts/FormLayoutsAlignment';
// ** Third Party Styles Imports
import 'react-datepicker/dist/react-datepicker.css';
const FormLayouts = () => {
return (
<DatePickerWrapper>
<Grid container spacing={6}>
<Grid item xs={12} md={6}>
<FormLayoutsBasic />
</Grid>
<Grid item xs={12} md={6}>
<FormLayoutsIcons />
</Grid>
<Grid item xs={12}>
<FormLayoutsSeparator />
</Grid>
<Grid item xs={12}>
<FormLayoutsAlignment />
</Grid>
</Grid>
</DatePickerWrapper>
);
};
export default FormLayouts;

View File

@@ -0,0 +1,164 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Card from '@mui/material/Card';
import Link from '@mui/material/Link';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import CardContent from '@mui/material/CardContent';
/**
** Icons Imports:
* ! You need to import all the icons which come from the API or from your server and then add these icons in 'icons' variable.
* ! If you need all the icons from the library, use "import * as Icon from 'mdi-material-ui'"
* */
import Abacus from 'mdi-material-ui/Abacus';
import Account from 'mdi-material-ui/Account';
import AbTesting from 'mdi-material-ui/AbTesting';
import AccountBox from 'mdi-material-ui/AccountBox';
import AccountCog from 'mdi-material-ui/AccountCog';
import AbjadArabic from 'mdi-material-ui/AbjadArabic';
import AbjadHebrew from 'mdi-material-ui/AbjadHebrew';
import AbugidaThai from 'mdi-material-ui/AbugidaThai';
import AccessPoint from 'mdi-material-ui/AccessPoint';
import AccountCash from 'mdi-material-ui/AccountCash';
import AccountEdit from 'mdi-material-ui/AccountEdit';
import AccountAlert from 'mdi-material-ui/AccountAlert';
import AccountCheck from 'mdi-material-ui/AccountCheck';
import AccountChild from 'mdi-material-ui/AccountChild';
import AccountClock from 'mdi-material-ui/AccountClock';
import AccountGroup from 'mdi-material-ui/AccountGroup';
import AccountCancel from 'mdi-material-ui/AccountCancel';
import AccountCircle from 'mdi-material-ui/AccountCircle';
import AccessPointOff from 'mdi-material-ui/AccessPointOff';
import AccountConvert from 'mdi-material-ui/AccountConvert';
import AccountDetails from 'mdi-material-ui/AccountDetails';
import AccessPointPlus from 'mdi-material-ui/AccessPointPlus';
import AccessPointCheck from 'mdi-material-ui/AccessPointCheck';
import AccessPointMinus from 'mdi-material-ui/AccessPointMinus';
import AccountArrowLeft from 'mdi-material-ui/AccountArrowLeft';
import AccountCowboyHat from 'mdi-material-ui/AccountCowboyHat';
import AbugidaDevanagari from 'mdi-material-ui/AbugidaDevanagari';
import AccessPointRemove from 'mdi-material-ui/AccessPointRemove';
import AccountArrowRight from 'mdi-material-ui/AccountArrowRight';
import AccountBoxOutline from 'mdi-material-ui/AccountBoxOutline';
import AccountCogOutline from 'mdi-material-ui/AccountCogOutline';
import AccessPointNetwork from 'mdi-material-ui/AccessPointNetwork';
import AccountBoxMultiple from 'mdi-material-ui/AccountBoxMultiple';
import AccountCashOutline from 'mdi-material-ui/AccountCashOutline';
import AccountChildCircle from 'mdi-material-ui/AccountChildCircle';
import AccountEditOutline from 'mdi-material-ui/AccountEditOutline';
import AccountAlertOutline from 'mdi-material-ui/AccountAlertOutline';
import AccountCheckOutline from 'mdi-material-ui/AccountCheckOutline';
import AccountChildOutline from 'mdi-material-ui/AccountChildOutline';
import AccountClockOutline from 'mdi-material-ui/AccountClockOutline';
import AccountCancelOutline from 'mdi-material-ui/AccountCancelOutline';
import AccountCircleOutline from 'mdi-material-ui/AccountCircleOutline';
import AccessPointNetworkOff from 'mdi-material-ui/AccessPointNetworkOff';
import AccountConvertOutline from 'mdi-material-ui/AccountConvertOutline';
import AccountDetailsOutline from 'mdi-material-ui/AccountDetailsOutline';
import AccountArrowLeftOutline from 'mdi-material-ui/AccountArrowLeftOutline';
import AccountArrowRightOutline from 'mdi-material-ui/AccountArrowRightOutline';
import AccountBoxMultipleOutline from 'mdi-material-ui/AccountBoxMultipleOutline';
const icons = {
Abacus,
Account,
AbTesting,
AccountBox,
AccountCog,
AbjadArabic,
AbjadHebrew,
AbugidaThai,
AccessPoint,
AccountCash,
AccountEdit,
AccountAlert,
AccountCheck,
AccountChild,
AccountClock,
AccountGroup,
AccountCancel,
AccountCircle,
AccessPointOff,
AccountConvert,
AccountDetails,
AccessPointPlus,
AccessPointCheck,
AccessPointMinus,
AccountArrowLeft,
AccountCowboyHat,
AbugidaDevanagari,
AccessPointRemove,
AccountArrowRight,
AccountBoxOutline,
AccountCogOutline,
AccessPointNetwork,
AccountBoxMultiple,
AccountCashOutline,
AccountChildCircle,
AccountEditOutline,
AccountAlertOutline,
AccountCheckOutline,
AccountChildOutline,
AccountClockOutline,
AccountCancelOutline,
AccountCircleOutline,
AccessPointNetworkOff,
AccountConvertOutline,
AccountDetailsOutline,
AccountArrowLeftOutline,
AccountArrowRightOutline,
AccountBoxMultipleOutline,
};
const Icons = () => {
const renderIconGrids = () => {
return Object.keys(icons).map(key => {
const IconTag = icons[key];
return (
<Grid item key={key}>
<Tooltip arrow title={key} placement="top">
<Card>
<CardContent sx={{ display: 'flex' }}>
<IconTag />
</CardContent>
</Card>
</Tooltip>
</Grid>
);
});
};
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<Typography variant="h5">
<Link href="https://materialdesignicons.com/" target="_blank">
Material Design Icons
</Link>
</Typography>
<Typography variant="body2">Material Design Icons from the Community</Typography>
</Grid>
<Grid item xs={12}>
<Grid container spacing={6}>
{renderIconGrids()}
</Grid>
</Grid>
<Grid item xs={12} sx={{ textAlign: 'center' }}>
<Button
target="_blank"
rel="noreferrer"
component={Link}
variant="contained"
href="https://materialdesignicons.com/"
>
View All Material Design Icons
</Button>
</Grid>
</Grid>
);
};
export default Icons;

View File

@@ -0,0 +1,37 @@
// ** MUI Imports
// ** Icons Imports
// ** Custom Components Imports
// ** Styled Component Import
// ** Demo Components Imports
import { setup } from 'src/lib/csrf';
import { useRouter } from 'next/router';
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import { useEffect } from 'react';
import Redirecting from 'src/components/Redirecting';
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});
const Dashboard = () => {
let router = useRouter();
useEffect(() => {
router.replace('/shopfront');
}, []);
return (
<>
<Redirecting />
</>
);
};
Dashboard.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export default Dashboard;

View File

@@ -0,0 +1,101 @@
// ** MUI Imports
import Grid from '@mui/material/Grid'
// ** Icons Imports
import Poll from 'mdi-material-ui/Poll'
import CurrencyUsd from 'mdi-material-ui/CurrencyUsd'
import HelpCircleOutline from 'mdi-material-ui/HelpCircleOutline'
import BriefcaseVariantOutline from 'mdi-material-ui/BriefcaseVariantOutline'
// ** Custom Components Imports
import CardStatisticsVerticalComponent from 'src/@core/components/card-statistics/card-stats-vertical'
// ** Styled Component Import
import ApexChartWrapper from 'src/@core/styles/libs/react-apexcharts'
// ** Demo Components Imports
import Table from 'src/views/dashboard/Table'
import Trophy from 'src/views/dashboard/Trophy'
import TotalEarning from 'src/views/dashboard/TotalEarning'
import StatisticsCard from 'src/views/dashboard/StatisticsCard'
import WeeklyOverview from 'src/views/dashboard/WeeklyOverview'
import DepositWithdraw from 'src/views/dashboard/DepositWithdraw'
import SalesByCountries from 'src/views/dashboard/SalesByCountries'
const Dashboard = () => {
return (
<ApexChartWrapper>
<Grid container spacing={6}>
<Grid item xs={12} md={4}>
<Trophy />
</Grid>
<Grid item xs={12} md={8}>
<StatisticsCard />
</Grid>
<Grid item xs={12} md={6} lg={4}>
<WeeklyOverview />
</Grid>
<Grid item xs={12} md={6} lg={4}>
<TotalEarning />
</Grid>
<Grid item xs={12} md={6} lg={4}>
<Grid container spacing={6}>
<Grid item xs={6}>
<CardStatisticsVerticalComponent
stats='$25.6k'
icon={<Poll />}
color='success'
trendNumber='+42%'
title='Total Profit'
subtitle='Weekly Profit'
/>
</Grid>
<Grid item xs={6}>
<CardStatisticsVerticalComponent
stats='$78'
title='Refunds'
trend='negative'
color='secondary'
trendNumber='-15%'
subtitle='Past Month'
icon={<CurrencyUsd />}
/>
</Grid>
<Grid item xs={6}>
<CardStatisticsVerticalComponent
stats='862'
trend='negative'
trendNumber='-18%'
title='New Project'
subtitle='Yearly Project'
icon={<BriefcaseVariantOutline />}
/>
</Grid>
<Grid item xs={6}>
<CardStatisticsVerticalComponent
stats='15'
color='warning'
trend='negative'
trendNumber='-18%'
subtitle='Last Week'
title='Sales Queries'
icon={<HelpCircleOutline />}
/>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} md={6} lg={4}>
<SalesByCountries />
</Grid>
<Grid item xs={12} md={12} lg={8}>
<DepositWithdraw />
</Grid>
<Grid item xs={12}>
<Table />
</Grid>
</Grid>
</ApexChartWrapper>
)
}
export default Dashboard

View File

@@ -0,0 +1,10 @@
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Component Import
import Error404 from 'src/pages/404';
const ErrorPage = () => <Error404 />;
ErrorPage.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default ErrorPage;

View File

@@ -0,0 +1,251 @@
// ** React Imports
import { useState } from 'react';
// ** Next Imports
import Link from 'next/link';
import { useRouter } from 'next/router';
// ** MUI Components
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Checkbox from '@mui/material/Checkbox';
import TextField from '@mui/material/TextField';
import InputLabel from '@mui/material/InputLabel';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import CardContent from '@mui/material/CardContent';
import FormControl from '@mui/material/FormControl';
import OutlinedInput from '@mui/material/OutlinedInput';
import { styled, useTheme } from '@mui/material/styles';
import MuiCard from '@mui/material/Card';
import InputAdornment from '@mui/material/InputAdornment';
import MuiFormControlLabel from '@mui/material/FormControlLabel';
// ** Icons Imports
import Google from 'mdi-material-ui/Google';
import Github from 'mdi-material-ui/Github';
import Twitter from 'mdi-material-ui/Twitter';
import Facebook from 'mdi-material-ui/Facebook';
import EyeOutline from 'mdi-material-ui/EyeOutline';
import EyeOffOutline from 'mdi-material-ui/EyeOffOutline';
// ** Configs
import themeConfig from 'src/configs/themeConfig';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import FooterIllustrationsV1 from 'src/views/pages/auth/FooterIllustration';
// ** Styled Components
const Card = styled(MuiCard)(({ theme }) => ({
[theme.breakpoints.up('sm')]: { width: '28rem' },
}));
const LinkStyled = styled('a')(({ theme }) => ({
fontSize: '0.875rem',
textDecoration: 'none',
color: theme.palette.primary.main,
}));
const FormControlLabel = styled(MuiFormControlLabel)(({ theme }) => ({
'& .MuiFormControlLabel-label': {
fontSize: '0.875rem',
color: theme.palette.text.secondary,
},
}));
const LoginPage = () => {
// ** State
const [values, setValues] = useState({
password: '',
showPassword: false,
});
// ** Hook
const theme = useTheme();
const router = useRouter();
const handleChange = prop => event => {
setValues({ ...values, [prop]: event.target.value });
};
const handleClickShowPassword = () => {
setValues({ ...values, showPassword: !values.showPassword });
};
const handleMouseDownPassword = event => {
event.preventDefault();
};
return (
<Box className="content-center">
<Card sx={{ zIndex: 1 }}>
<CardContent sx={{ padding: theme => `${theme.spacing(12, 9, 7)} !important` }}>
<Box sx={{ mb: 8, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg
width={35}
height={29}
version="1.1"
viewBox="0 0 30 23"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Artboard" transform="translate(-95.000000, -51.000000)">
<g id="logo" transform="translate(95.000000, 50.000000)">
<path
id="Combined-Shape"
fill={theme.palette.primary.main}
d="M30,21.3918362 C30,21.7535219 29.9019196,22.1084381 29.7162004,22.4188007 C29.1490236,23.366632 27.9208668,23.6752135 26.9730355,23.1080366 L26.9730355,23.1080366 L23.714971,21.1584295 C23.1114106,20.7972624 22.7419355,20.1455972 22.7419355,19.4422291 L22.7419355,19.4422291 L22.741,12.7425689 L15,17.1774194 L7.258,12.7425689 L7.25806452,19.4422291 C7.25806452,20.1455972 6.88858935,20.7972624 6.28502902,21.1584295 L3.0269645,23.1080366 C2.07913318,23.6752135 0.850976404,23.366632 0.283799571,22.4188007 C0.0980803893,22.1084381 2.0190442e-15,21.7535219 0,21.3918362 L0,3.58469444 L0.00548573643,3.43543209 L0.00548573643,3.43543209 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 L15,9.19354839 L26.9548759,1.86636639 C27.2693965,1.67359571 27.6311047,1.5715689 28,1.5715689 C29.1045695,1.5715689 30,2.4669994 30,3.5715689 L30,3.5715689 Z"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.7505183 7.25806452 16.8305646"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.6445567 7.25806452 15.1370162"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.7417372 30 16.9537453"
transform="translate(26.370968, 12.771227) scale(-1, 1) translate(-26.370968, -12.771227) "
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.6409734 30 15.2601969"
transform="translate(26.370968, 11.924453) scale(-1, 1) translate(-26.370968, -11.924453) "
/>
<path
id="Rectangle"
fillOpacity="0.15"
fill={theme.palette.common.white}
d="M3.04512412,1.86636639 L15,9.19354839 L15,9.19354839 L15,17.1774194 L0,8.58649679 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 Z"
/>
<path
id="Rectangle"
fillOpacity="0.35"
fill={theme.palette.common.white}
transform="translate(22.500000, 8.588710) scale(-1, 1) translate(-22.500000, -8.588710) "
d="M18.0451241,1.86636639 L30,9.19354839 L30,9.19354839 L30,17.1774194 L15,8.58649679 L15,3.5715689 C15,2.4669994 15.8954305,1.5715689 17,1.5715689 C17.3688953,1.5715689 17.7306035,1.67359571 18.0451241,1.86636639 Z"
/>
</g>
</g>
</g>
</svg>
<Typography
variant="h6"
sx={{
ml: 3,
lineHeight: 1,
fontWeight: 600,
textTransform: 'uppercase',
fontSize: '1.5rem !important',
}}
>
{themeConfig.templateName}
</Typography>
</Box>
<Box sx={{ mb: 6 }}>
<Typography variant="h5" sx={{ fontWeight: 600, marginBottom: 1.5 }}>
Welcome to {themeConfig.templateName}! 👋🏻
</Typography>
<Typography variant="body2">Please sign-in to your account and start the adventure</Typography>
</Box>
<form noValidate autoComplete="off" onSubmit={e => e.preventDefault()}>
<TextField autoFocus fullWidth id="email" label="Email" sx={{ marginBottom: 4 }} />
<FormControl fullWidth>
<InputLabel htmlFor="auth-login-password">Password</InputLabel>
<OutlinedInput
label="Password"
value={values.password}
id="auth-login-password"
onChange={handleChange('password')}
type={values.showPassword ? 'text' : 'password'}
endAdornment={
<InputAdornment position="end">
<IconButton
edge="end"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
aria-label="toggle password visibility"
>
{values.showPassword ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
</FormControl>
<Box
sx={{ mb: 4, display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'space-between' }}
>
<FormControlLabel control={<Checkbox />} label="Remember Me" />
<Link passHref href="/">
<LinkStyled onClick={e => e.preventDefault()}>Forgot Password?</LinkStyled>
</Link>
</Box>
<Button
fullWidth
size="large"
variant="contained"
sx={{ marginBottom: 7 }}
onClick={() => router.push('/')}
>
Login
</Button>
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'center' }}>
<Typography variant="body2" sx={{ marginRight: 2 }}>
New on our platform?
</Typography>
<Typography variant="body2">
<Link passHref href="/pages/register">
<LinkStyled>Create an account</LinkStyled>
</Link>
</Typography>
</Box>
<Divider sx={{ my: 5 }}>or</Divider>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Facebook sx={{ color: '#497ce2' }} />
</IconButton>
</Link>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Twitter sx={{ color: '#1da1f2' }} />
</IconButton>
</Link>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Github
sx={{ color: theme => (theme.palette.mode === 'light' ? '#272727' : theme.palette.grey[300]) }}
/>
</IconButton>
</Link>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Google sx={{ color: '#db4437' }} />
</IconButton>
</Link>
</Box>
</form>
</CardContent>
</Card>
<FooterIllustrationsV1 />
</Box>
);
};
LoginPage.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default LoginPage;

View File

@@ -0,0 +1,249 @@
// ** React Imports
import { useState, Fragment } from 'react';
// ** Next Imports
import Link from 'next/link';
// ** MUI Components
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Checkbox from '@mui/material/Checkbox';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import InputLabel from '@mui/material/InputLabel';
import IconButton from '@mui/material/IconButton';
import CardContent from '@mui/material/CardContent';
import FormControl from '@mui/material/FormControl';
import OutlinedInput from '@mui/material/OutlinedInput';
import { styled, useTheme } from '@mui/material/styles';
import MuiCard from '@mui/material/Card';
import InputAdornment from '@mui/material/InputAdornment';
import MuiFormControlLabel from '@mui/material/FormControlLabel';
// ** Icons Imports
import Google from 'mdi-material-ui/Google';
import Github from 'mdi-material-ui/Github';
import Twitter from 'mdi-material-ui/Twitter';
import Facebook from 'mdi-material-ui/Facebook';
import EyeOutline from 'mdi-material-ui/EyeOutline';
import EyeOffOutline from 'mdi-material-ui/EyeOffOutline';
// ** Configs
import themeConfig from 'src/configs/themeConfig';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import FooterIllustrationsV1 from 'src/views/pages/auth/FooterIllustration';
// ** Styled Components
const Card = styled(MuiCard)(({ theme }) => ({
[theme.breakpoints.up('sm')]: { width: '28rem' },
}));
const LinkStyled = styled('a')(({ theme }) => ({
fontSize: '0.875rem',
textDecoration: 'none',
color: theme.palette.primary.main,
}));
const FormControlLabel = styled(MuiFormControlLabel)(({ theme }) => ({
marginTop: theme.spacing(1.5),
marginBottom: theme.spacing(4),
'& .MuiFormControlLabel-label': {
fontSize: '0.875rem',
color: theme.palette.text.secondary,
},
}));
const RegisterPage = () => {
// ** States
const [values, setValues] = useState({
password: '',
showPassword: false,
});
// ** Hook
const theme = useTheme();
const handleChange = prop => event => {
setValues({ ...values, [prop]: event.target.value });
};
const handleClickShowPassword = () => {
setValues({ ...values, showPassword: !values.showPassword });
};
const handleMouseDownPassword = event => {
event.preventDefault();
};
return (
<Box className="content-center">
<Card sx={{ zIndex: 1 }}>
<CardContent sx={{ padding: theme => `${theme.spacing(12, 9, 7)} !important` }}>
<Box sx={{ mb: 8, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg
width={35}
height={29}
version="1.1"
viewBox="0 0 30 23"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Artboard" transform="translate(-95.000000, -51.000000)">
<g id="logo" transform="translate(95.000000, 50.000000)">
<path
id="Combined-Shape"
fill={theme.palette.primary.main}
d="M30,21.3918362 C30,21.7535219 29.9019196,22.1084381 29.7162004,22.4188007 C29.1490236,23.366632 27.9208668,23.6752135 26.9730355,23.1080366 L26.9730355,23.1080366 L23.714971,21.1584295 C23.1114106,20.7972624 22.7419355,20.1455972 22.7419355,19.4422291 L22.7419355,19.4422291 L22.741,12.7425689 L15,17.1774194 L7.258,12.7425689 L7.25806452,19.4422291 C7.25806452,20.1455972 6.88858935,20.7972624 6.28502902,21.1584295 L3.0269645,23.1080366 C2.07913318,23.6752135 0.850976404,23.366632 0.283799571,22.4188007 C0.0980803893,22.1084381 2.0190442e-15,21.7535219 0,21.3918362 L0,3.58469444 L0.00548573643,3.43543209 L0.00548573643,3.43543209 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 L15,9.19354839 L26.9548759,1.86636639 C27.2693965,1.67359571 27.6311047,1.5715689 28,1.5715689 C29.1045695,1.5715689 30,2.4669994 30,3.5715689 L30,3.5715689 Z"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.7505183 7.25806452 16.8305646"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.6445567 7.25806452 15.1370162"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.7417372 30 16.9537453"
transform="translate(26.370968, 12.771227) scale(-1, 1) translate(-26.370968, -12.771227) "
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.6409734 30 15.2601969"
transform="translate(26.370968, 11.924453) scale(-1, 1) translate(-26.370968, -11.924453) "
/>
<path
id="Rectangle"
fillOpacity="0.15"
fill={theme.palette.common.white}
d="M3.04512412,1.86636639 L15,9.19354839 L15,9.19354839 L15,17.1774194 L0,8.58649679 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 Z"
/>
<path
id="Rectangle"
fillOpacity="0.35"
fill={theme.palette.common.white}
transform="translate(22.500000, 8.588710) scale(-1, 1) translate(-22.500000, -8.588710) "
d="M18.0451241,1.86636639 L30,9.19354839 L30,9.19354839 L30,17.1774194 L15,8.58649679 L15,3.5715689 C15,2.4669994 15.8954305,1.5715689 17,1.5715689 C17.3688953,1.5715689 17.7306035,1.67359571 18.0451241,1.86636639 Z"
/>
</g>
</g>
</g>
</svg>
<Typography
variant="h6"
sx={{
ml: 3,
lineHeight: 1,
fontWeight: 600,
textTransform: 'uppercase',
fontSize: '1.5rem !important',
}}
>
{themeConfig.templateName}
</Typography>
</Box>
<Box sx={{ mb: 6 }}>
<Typography variant="h5" sx={{ fontWeight: 600, marginBottom: 1.5 }}>
Adventure starts here 🚀
</Typography>
<Typography variant="body2">Make your app management easy and fun!</Typography>
</Box>
<form noValidate autoComplete="off" onSubmit={e => e.preventDefault()}>
<TextField autoFocus fullWidth id="username" label="Username" sx={{ marginBottom: 4 }} />
<TextField fullWidth type="email" label="Email" sx={{ marginBottom: 4 }} />
<FormControl fullWidth>
<InputLabel htmlFor="auth-register-password">Password</InputLabel>
<OutlinedInput
label="Password"
value={values.password}
id="auth-register-password"
onChange={handleChange('password')}
type={values.showPassword ? 'text' : 'password'}
endAdornment={
<InputAdornment position="end">
<IconButton
edge="end"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
aria-label="toggle password visibility"
>
{values.showPassword ? <EyeOutline fontSize="small" /> : <EyeOffOutline fontSize="small" />}
</IconButton>
</InputAdornment>
}
/>
</FormControl>
<FormControlLabel
control={<Checkbox />}
label={
<Fragment>
<span>I agree to </span>
<Link href="/" passHref>
<LinkStyled onClick={e => e.preventDefault()}>privacy policy & terms</LinkStyled>
</Link>
</Fragment>
}
/>
<Button fullWidth size="large" type="submit" variant="contained" sx={{ marginBottom: 7 }}>
Sign up
</Button>
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'center' }}>
<Typography variant="body2" sx={{ marginRight: 2 }}>
Already have an account?
</Typography>
<Typography variant="body2">
<Link passHref href="/pages/login">
<LinkStyled>Sign in instead</LinkStyled>
</Link>
</Typography>
</Box>
<Divider sx={{ my: 5 }}>or</Divider>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Facebook sx={{ color: '#497ce2' }} />
</IconButton>
</Link>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Twitter sx={{ color: '#1da1f2' }} />
</IconButton>
</Link>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Github
sx={{ color: theme => (theme.palette.mode === 'light' ? '#272727' : theme.palette.grey[300]) }}
/>
</IconButton>
</Link>
<Link href="/" passHref>
<IconButton component="a" onClick={e => e.preventDefault()}>
<Google sx={{ color: '#db4437' }} />
</IconButton>
</Link>
</Box>
</form>
</CardContent>
</Card>
<FooterIllustrationsV1 />
</Box>
);
};
RegisterPage.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default RegisterPage;

View File

@@ -0,0 +1,39 @@
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import { Button, Stack, Typography } from '@mui/material';
import AccountArrowLeftOutline from 'mdi-material-ui/AccountArrowLeftOutline';
import Link from 'next/dist/client/link';
import { useContext, useEffect } from 'react';
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import { CartContext } from 'src/contexts/cart';
function Thankyou() {
let { emptyCart } = useContext(CartContext);
useEffect(() => {
emptyCart();
}, []);
return (
<>
<Stack
direction="column"
justifyContent={'center'}
alignItems={'center'}
height={['80vh']}
sx={{}}
spacing={'2rem'}
>
<Typography variant="h4">Thaks for purchase !!!</Typography>
<Typography variant="h4">checkout done</Typography>
<Link href="/shopfront">
<Button variant="contained" startIcon={<ChevronLeft />}>
Back
</Button>
</Link>
</Stack>
</>
);
}
Thankyou.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export default Thankyou;

View File

@@ -0,0 +1,158 @@
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import { Box, Button, Stack, Typography } from '@mui/material';
import { useContext, useEffect, useState } from 'react';
import { CartContext } from 'src/contexts/cart';
import Link from 'next/link';
import fetchProduct from 'src/api/fetchProduct';
import Loading from 'src/components/Loading';
import checkoutCart from 'src/api/checkoutCart';
import { LoadingButton } from '@mui/lab';
import { useRouter } from 'next/router';
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import CartItemDetail from 'src/components/CartItemDetail';
import Checkout from 'src/components/Checkout';
import { PayPalScriptProvider } from '@paypal/react-paypal-js';
import CheckSession from 'src/api/checkSession';
import { setup } from 'src/lib/csrf';
const initialOptions = {
'client-id': 'AQT5-eAKNK7IhAhBGlbHBu_9jBx74ZLCfEioKUWQMXMuMmLcnffmpoUz_z-ewOuKZmpSlDk74UtlH58O',
currency: 'HKD',
intent: 'capture',
};
function NoProductInCart() {
return (
<>
<Stack direction="column" height="30vh" justifyContent={'center'} alignItems={'center'} spacing={'3rem'}>
<Typography variant="h5">No product in cart,</Typography>
<Typography variant="h5">
<Link href="/">
<Button variant={'contained'} size="large" startIcon={<ChevronLeft />}>
go add some
</Button>
</Link>
</Typography>
</Stack>
</>
);
}
function CartPage() {
let route = useRouter();
const { cart, total_price, products, proceedCheckOutCart } = useContext(CartContext);
let [checking_out, setCheckingOut] = useState(false);
// let [purchase_units, setPurchaseUnits] = useState([{ amount: { value: total_price } }]);
const handleCheckoutOnClick = () => {
const proceedCheckout = async () => {
try {
setCheckingOut(true);
await proceedCheckOutCart();
route.replace('/shopfront/cart/Thankyou');
} catch (error) {
console.error(error);
alert('error found during checkout');
}
};
proceedCheckout();
};
useEffect(() => {
const check = async () => {
try {
let { session } = JSON.parse(localStorage.getItem('session')) || '';
let data = await CheckSession({ session });
if (data['status'] != 'OK') {
return route.replace('/shopfront/login');
} else {
// assume 'status' is ok here
if (data['role'] == 'admin') {
return route.replace('/admin');
} else {
return route.replace('/shopfront/cart');
}
}
} catch (error) {
console.log(error);
}
};
return check();
}, []);
let [refresh_paypal, setRefreshPaypal] = useState(false);
useEffect(() => {
setRefreshPaypal(true);
setTimeout(() => {
setRefreshPaypal(false);
}, 1);
// force regenerate component
}, [total_price]);
if (!cart || !products) return <Loading />;
if (products.length < 1) return <Loading />;
return (
<>
<Stack gap="1rem" width="100%" justifyContent={'center'} alignItems={'center'}>
{cart.length < 1 ? (
<NoProductInCart />
) : (
<>
{cart.map((p, idx) => {
return (
<>
<CartItemDetail
checking_out={checking_out}
products={products}
key={idx}
pid={p.pid}
qty={p.quantity}
/>
</>
);
})}
<Box sx={{ borderTop: '1px solid rgba(0,0,0,0.2)', width: '50%', padding: '1rem' }} />
<Box>
<Box>
<Typography variant="h6">Total: HKD {total_price.toFixed(2)}</Typography>
{/*
<LoadingButton loading={checking_out} onClick={handleCheckoutOnClick} variant={'contained'}>
{checking_out ? 'Checking out...' : 'press to Checkout'}
</LoadingButton>
*/}
<PayPalScriptProvider options={initialOptions}>
<Box sx={{ height: '100px', minWidth: '200px', maxWidth: '300px' }}>
{refresh_paypal ? (
<>refreshing</>
) : (
<>
<Checkout />
</>
)}
</Box>
</PayPalScriptProvider>
</Box>
</Box>
</>
)}
</Stack>
</>
);
}
CartPage.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});
export default CartPage;

View File

@@ -0,0 +1,127 @@
import { Box, Button, FormControl, IconButton, InputAdornment, InputLabel, OutlinedInput, Stack } from '@mui/material';
import { Formik } from 'formik';
import Link from 'next/link';
import { useContext } from 'react';
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import { AuthContext } from 'src/contexts/auth';
function ChangePassword() {
// let initialValues = { username: 'admin@vtkhmall.com', password: 'nimda' };
const { username, performChangePassword } = useContext(AuthContext);
let initialValues = {
username,
old_password: '',
new_password: '',
retype_password: '',
};
let onSubmit = async (values, { setSubmitting }) => {
let { old_password, new_password, retype_password } = values;
if (new_password != retype_password) {
alert('Passwords do not match');
return;
} else {
setSubmitting(true);
// check password for empty string
if (!old_password || !new_password || !retype_password) {
alert('Password cannot be empty');
setSubmitting(false);
return;
}
await performChangePassword({
username,
old_password,
new_password,
});
}
};
return (
<>
<Stack
direction="row"
maxHeight="100vh"
sx={{ width: '100%' }}
spacing={'2rem'}
justifyContent={'center'}
marginTop={'1rem'}
>
<Stack direction="column" spacing="2rem">
<Box>
<Link href="/shopfront/customer/profile">
<Button>Orders</Button>
</Link>
</Box>
<Box>
<Link href="/shopfront/change_password">
<Button>Change password</Button>
</Link>
</Box>
<Box>
<Link href="/shopfront">
<Button>Back to shop</Button>
</Link>
</Box>
<Box>
<Link href="/shopfront/logout">
<Button variant="contained">Logout</Button>
</Link>
</Box>
</Stack>
<Box sx={{ borderLeft: '1px solid rgba(32,32,32,0.2)' }}></Box>
<Box sx={{ minWidth: '66vw' }}>
<Stack direction="column" alignItems={'center'} spacing="2rem">
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue }) => (
<>
<FormControl>
<InputLabel htmlFor="auth-login-password">Old Password</InputLabel>
<OutlinedInput
label="Old Password"
value={values.password}
id="old-password"
onChange={handleChange('old_password')}
type={'password'}
/>
</FormControl>
<FormControl>
<InputLabel htmlFor="auth-login-password">New Password</InputLabel>
<OutlinedInput
label="New Password"
value={values.password}
id="new-password"
onChange={handleChange('new_password')}
type={'password'}
/>
</FormControl>
<FormControl>
<InputLabel htmlFor="auth-login-password">Retype new Password</InputLabel>
<OutlinedInput
label="Retype new Password"
value={values.password}
id="retype-password"
onChange={handleChange('retype_password')}
type={'password'}
/>
</FormControl>
<Box>
<Button type="submit" onClick={handleSubmit}>
change password
</Button>
</Box>
</>
)}
</Formik>
</Stack>
</Box>
</Stack>
</>
);
}
ChangePassword.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export default ChangePassword;

View File

@@ -0,0 +1,75 @@
import { Box, Button, Stack } from '@mui/material';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import CheckSession from 'src/api/checkSession';
import CustomerOrderTable from 'src/components/CustomerOrderTable';
function CustomerProfile() {
let route = useRouter();
useEffect(() => {
const check = async () => {
try {
let { session } = JSON.parse(localStorage.getItem('session')) || '';
let data = await CheckSession({ session });
if (data['status'] != 'OK') {
return route.replace('/shopfront/login');
} else {
// assume 'status' is ok here
if (data['role'] == 'admin') {
return route.replace('/admin');
} else {
return route.replace('/shopfront/customer/profile');
}
}
} catch (error) {}
};
return check();
}, []);
return (
<>
<Stack
direction="row"
maxHeight="100vh"
sx={{ width: '100%' }}
spacing={'2rem'}
justifyContent={'center'}
marginTop={'1rem'}
>
<Stack direction="column" spacing="2rem">
<Box>
<Button>Orders</Button>
</Box>
<Box>
<Link href="/shopfront/change_password">
<Button>Change password</Button>
</Link>
</Box>
<Box>
<Link href="/shopfront">
<Button>Back to shop</Button>
</Link>
</Box>
<Box>
<Link href="/shopfront/logout">
<Button variant="contained">Logout</Button>
</Link>
</Box>
</Stack>
<Box sx={{ borderLeft: '1px solid rgba(32,32,32,0.2)' }}></Box>
<Box>
<CustomerOrderTable />
</Box>
</Stack>
</>
);
}
CustomerProfile.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export default CustomerProfile;

View File

@@ -0,0 +1,127 @@
import { Box, Button, Grid, Link, Stack, Tab, Tabs, Typography } from '@mui/material';
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import BlankLayout from 'src/@core/layouts/BlankLayout';
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import CardMobile from 'src/views/cards/CardMobile';
import CardMobileSamsung from 'src/views/cards/CardMobileSamsung';
import CardMobileVisionPro from 'src/views/cards/CardMobileVisionPro';
// import CardProduct from './product/CardProduct';
import { CartProvider } from 'src/contexts/cart';
import fetchListByCategory from 'src/api/fetchListByCategory';
import fetchListByCategoryUnsold from 'src/api/fetchListByCategoryUnsold';
import fetchCategories from 'src/api/fetchCategories';
import Loading from 'src/components/Loading';
import CardProduct from 'src/components/CardProduct';
import { setup } from 'src/lib/csrf';
import SwapHorizontalCircleIcon from '@mui/icons-material/SwapHorizontalCircle';
function ShopFront() {
// const [value, setCid] = React.useState(0);
const [categories, setCategories] = useState([]);
const [products, setProducts] = useState([]);
const [tab_pos, setTabPos] = useState(0);
const [cid, setCid] = useState();
const handleChange = (event, newValue) => {
setTabPos(newValue);
setCid(categories[newValue]['cid']);
};
useEffect(() => {
const update = async () => {
if (cid == undefined) {
setProducts([]);
} else {
let result = await fetchListByCategoryUnsold({ cid });
setProducts(result);
}
};
update();
}, [cid]);
useEffect(() => {
const update = async () => {
let categories = await fetchCategories();
setCategories(categories);
if (categories) {
setCid(categories[0].cid);
}
};
update();
}, []);
if (!categories || !products)
return (
<>
<Loading />
</>
);
if (categories == [])
return (
<>
<Loading />
</>
);
if (products == [])
return (
<>
<Loading />
</>
);
return (
<>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={tab_pos}
onChange={handleChange}
aria-label="basic tabs example"
scrollButtons="auto"
variant="scrollable"
>
{categories?.map((c, i) => (
<Tab key={i} label={c.name} />
))}
</Tabs>
</Box>
<Box my={'2rem'} width={'100%'} minHeight={'80vh'}>
<Grid container rowSpacing={{ xs: 1, sm: 2, md: 3 }} columnSpacing={{ xs: 1, sm: 2, md: 3 }}>
{products.length < 1 ? (
<>
<Grid item xs={12}>
<Stack justifyContent="center" alignItems={'center'} mt="3rem" height={'50vh'}>
<SwapHorizontalCircleIcon />
<Typography variant="h6">loading items ...</Typography>
</Stack>
</Grid>
</>
) : (
<>
{products.map((p, idx) => {
return (
<>
<Grid item xs={4}>
<CardProduct product={p} i={idx} />
</Grid>
</>
);
})}
</>
)}
</Grid>
</Box>
</>
);
}
ShopFront.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});
export default ShopFront;

View File

@@ -0,0 +1,227 @@
// ** React Imports
import { useContext, useState } from 'react';
// ** Next Imports
import Link from 'next/link';
import { useRouter } from 'next/router';
// ** MUI Components
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import InputLabel from '@mui/material/InputLabel';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import CardContent from '@mui/material/CardContent';
import FormControl from '@mui/material/FormControl';
import OutlinedInput from '@mui/material/OutlinedInput';
import { styled, useTheme } from '@mui/material/styles';
import MuiCard from '@mui/material/Card';
import InputAdornment from '@mui/material/InputAdornment';
import MuiFormControlLabel from '@mui/material/FormControlLabel';
import LoginIcon from '@mui/icons-material/Login';
// ** Icons Imports
import EyeOutline from 'mdi-material-ui/EyeOutline';
import EyeOffOutline from 'mdi-material-ui/EyeOffOutline';
// ** Configs
import themeConfig from 'src/configs/themeConfig';
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import { Formik } from 'formik';
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout';
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import { Stack } from '@mui/material';
import { setup } from 'src/lib/csrf';
import { AuthContext } from 'src/contexts/auth';
// ** Styled Components
const Card = styled(MuiCard)(({ theme }) => ({
[theme.breakpoints.up('sm')]: { width: '28rem' },
}));
const LoginPage = () => {
const { performLogin } = useContext(AuthContext);
// ** State
const [show_password, setShowPassword] = useState(false);
const [values, setValues] = useState({
password: '',
showPassword: false,
});
// ** Hook
const theme = useTheme();
const router = useRouter();
const handleChange = prop => event => {
setValues({ ...values, [prop]: event.target.value });
};
const handleClickShowPassword = () => {
setShowPassword(!show_password);
};
const handleMouseDownPassword = event => {
event.preventDefault();
};
// let initialValues = { username: 'admin@vtkhmall.com', password: 'nimda' };
let initialValues = { username: '', password: '' };
let onSubmit = async (values, { setSubmitting }) => {
setSubmitting(true);
await performLogin({ values });
};
return (
<Box className="content-center">
<Card sx={{ zIndex: 1 }}>
<CardContent sx={{ padding: theme => `${theme.spacing(12, 9, 7)} !important` }}>
<Box sx={{ mb: 8, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg
width={35}
height={29}
version="1.1"
viewBox="0 0 30 23"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Artboard" transform="translate(-95.000000, -51.000000)">
<g id="logo" transform="translate(95.000000, 50.000000)">
<path
id="Combined-Shape"
fill={theme.palette.primary.main}
d="M30,21.3918362 C30,21.7535219 29.9019196,22.1084381 29.7162004,22.4188007 C29.1490236,23.366632 27.9208668,23.6752135 26.9730355,23.1080366 L26.9730355,23.1080366 L23.714971,21.1584295 C23.1114106,20.7972624 22.7419355,20.1455972 22.7419355,19.4422291 L22.7419355,19.4422291 L22.741,12.7425689 L15,17.1774194 L7.258,12.7425689 L7.25806452,19.4422291 C7.25806452,20.1455972 6.88858935,20.7972624 6.28502902,21.1584295 L3.0269645,23.1080366 C2.07913318,23.6752135 0.850976404,23.366632 0.283799571,22.4188007 C0.0980803893,22.1084381 2.0190442e-15,21.7535219 0,21.3918362 L0,3.58469444 L0.00548573643,3.43543209 L0.00548573643,3.43543209 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 L15,9.19354839 L26.9548759,1.86636639 C27.2693965,1.67359571 27.6311047,1.5715689 28,1.5715689 C29.1045695,1.5715689 30,2.4669994 30,3.5715689 L30,3.5715689 Z"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.7505183 7.25806452 16.8305646"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="0 8.58870968 7.25806452 12.6445567 7.25806452 15.1370162"
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.7417372 30 16.9537453"
transform="translate(26.370968, 12.771227) scale(-1, 1) translate(-26.370968, -12.771227) "
/>
<polygon
id="Rectangle"
opacity="0.077704"
fill={theme.palette.common.black}
points="22.7419355 8.58870968 30 12.6409734 30 15.2601969"
transform="translate(26.370968, 11.924453) scale(-1, 1) translate(-26.370968, -11.924453) "
/>
<path
id="Rectangle"
fillOpacity="0.15"
fill={theme.palette.common.white}
d="M3.04512412,1.86636639 L15,9.19354839 L15,9.19354839 L15,17.1774194 L0,8.58649679 L0,3.5715689 C3.0881846e-16,2.4669994 0.8954305,1.5715689 2,1.5715689 C2.36889529,1.5715689 2.73060353,1.67359571 3.04512412,1.86636639 Z"
/>
<path
id="Rectangle"
fillOpacity="0.35"
fill={theme.palette.common.white}
transform="translate(22.500000, 8.588710) scale(-1, 1) translate(-22.500000, -8.588710) "
d="M18.0451241,1.86636639 L30,9.19354839 L30,9.19354839 L30,17.1774194 L15,8.58649679 L15,3.5715689 C15,2.4669994 15.8954305,1.5715689 17,1.5715689 C17.3688953,1.5715689 17.7306035,1.67359571 18.0451241,1.86636639 Z"
/>
</g>
</g>
</g>
</svg>
<Typography
variant="h6"
sx={{
ml: 3,
lineHeight: 1,
fontWeight: 600,
textTransform: 'uppercase',
fontSize: '1.5rem !important',
}}
>
VTKH Customer Login
</Typography>
</Box>
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue }) => (
<form noValidate autoComplete="off" onSubmit={e => e.preventDefault()}>
<TextField
autoFocus
fullWidth
id="username"
label="Email"
sx={{ marginBottom: 4 }}
value={values.username}
onChange={handleChange('username')}
/>
<FormControl fullWidth>
<InputLabel htmlFor="auth-login-password">Password</InputLabel>
<OutlinedInput
label="Password"
value={values.password}
id="auth-login-password"
onChange={handleChange('password')}
type={show_password ? 'text' : 'password'}
endAdornment={
<InputAdornment position="end">
<IconButton
edge="end"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
aria-label="toggle password visibility"
>
{show_password ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
</FormControl>
<Box
sx={{
mb: 4,
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
justifyContent: 'space-between',
}}
></Box>
<Button onClick={handleSubmit} fullWidth size="large" variant="contained" sx={{ marginBottom: 7 }}>
<Stack direction="row" spacing="0.25rem">
<LoginIcon /> <Box>Login</Box>
</Stack>
</Button>
</form>
)}
</Formik>
<Link href="/">
<Button fullWidth size="large" variant="outlined" sx={{ marginBottom: 7 }} onClick={() => router.push('/')}>
<Stack direction="row" spacing="0.25rem">
<ChevronLeft /> <Box>back</Box>
</Stack>
</Button>
</Link>
</CardContent>
</Card>
</Box>
);
};
LoginPage.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>;
export const getServerSideProps = setup(async ({ req, res }) => {
return { props: {} };
});
export default LoginPage;

View File

@@ -0,0 +1,62 @@
// ** React Imports
import { useContext, useEffect } from 'react';
// ** Next Imports
import { useRouter } from 'next/router';
// ** MUI Components
import Box from '@mui/material/Box';
import { styled } from '@mui/material/styles';
import MuiCard from '@mui/material/Card';
import MuiFormControlLabel from '@mui/material/FormControlLabel';
// ** Icons Imports
// ** Configs
// ** Layout Import
import BlankLayout from 'src/@core/layouts/BlankLayout';
// ** Demo Imports
import { Stack } from '@mui/material';
import { AuthContext } from 'src/contexts/auth';
// ** Styled Components
const Card = styled(MuiCard)(({ theme }) => ({
[theme.breakpoints.up('sm')]: { width: '28rem' },
}));
const LinkStyled = styled('a')(({ theme }) => ({
fontSize: '0.875rem',
textDecoration: 'none',
color: theme.palette.primary.main,
}));
const FormControlLabel = styled(MuiFormControlLabel)(({ theme }) => ({
'& .MuiFormControlLabel-label': {
fontSize: '0.875rem',
color: theme.palette.text.secondary,
},
}));
const LogoutPage = () => {
const router = useRouter();
const { performLogout } = useContext(AuthContext);
useEffect(() => {
performLogout();
router.push('/');
}, []);
return (
<Box className="content-center">
<Stack direction="row" justifyContent="center" alignItems="center">
logging you out
</Stack>
</Box>
);
};
LogoutPage.getLayout = page => <BlankLayout>{page}</BlankLayout>;
export default LogoutPage;

View File

@@ -0,0 +1,36 @@
import { Box, Button, Stack, Typography } from '@mui/material'
import React from 'react'
import BlankLayout from 'src/@core/layouts/BlankLayout'
import ShopfrontLayout from 'src/@core/layouts/ShopfrontLayout'
import { useRouter } from 'next/router'
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
import CardProductDetail from 'src/components/CardProductDetail'
function ProductShow() {
let router = useRouter()
return (
<>
<Stack direction='column' justifyContent={'center'} alignItems={'center'}>
<Box width='80vw'>
<Button
onClick={() => {
router.push('/')
}}
>
<ChevronLeftIcon />
<Typography variant='caption'>Back</Typography>
</Button>
</Box>
<Box width='80vw' mt={'1rem'}>
<CardProductDetail />
</Box>
</Stack>
</>
)
}
ProductShow.getLayout = page => <ShopfrontLayout>{page}</ShopfrontLayout>
export default ProductShow

View File

@@ -0,0 +1,67 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Card from '@mui/material/Card';
import Typography from '@mui/material/Typography';
import CardHeader from '@mui/material/CardHeader';
// ** Demo Components Imports
import TableBasic from 'src/views/tables/TableBasic';
import TableDense from 'src/views/tables/TableDense';
import TableSpanning from 'src/views/tables/TableSpanning';
import TableCustomized from 'src/views/tables/TableCustomized';
import TableCollapsible from 'src/views/tables/TableCollapsible';
import TableStickyHeader from 'src/views/tables/TableStickyHeader';
const MUITable = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<Typography variant="h5">
<Link href="https://mui.com/components/tables/" target="_blank">
MUI Tables
</Link>
</Typography>
<Typography variant="body2">Tables display sets of data. They can be fully customized</Typography>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader title="Basic Table" titleTypographyProps={{ variant: 'h6' }} />
<TableBasic />
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader title="Dense Table" titleTypographyProps={{ variant: 'h6' }} />
<TableDense />
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader title="Sticky Header" titleTypographyProps={{ variant: 'h6' }} />
<TableStickyHeader />
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader title="Collapsible Table" titleTypographyProps={{ variant: 'h6' }} />
<TableCollapsible />
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader title="Spanning Table" titleTypographyProps={{ variant: 'h6' }} />
<TableSpanning />
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader title="Customized Table" titleTypographyProps={{ variant: 'h6' }} />
<TableCustomized />
</Card>
</Grid>
</Grid>
);
};
export default MUITable;

View File

@@ -0,0 +1,21 @@
// ** MUI Imports
import Grid from '@mui/material/Grid';
// ** Demo Components Imports
import TypographyTexts from 'src/views/typography/TypographyTexts';
import TypographyHeadings from 'src/views/typography/TypographyHeadings';
const TypographyPage = () => {
return (
<Grid container spacing={6}>
<Grid item xs={12}>
<TypographyHeadings />
</Grid>
<Grid item xs={12}>
<TypographyTexts />
</Grid>
</Grid>
);
};
export default TypographyPage;