update,
This commit is contained in:
61
tsc1877/task1/project/admin/src/pages/401.js
Normal file
61
tsc1877/task1/project/admin/src/pages/401.js
Normal 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′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;
|
70
tsc1877/task1/project/admin/src/pages/404.js
Normal file
70
tsc1877/task1/project/admin/src/pages/404.js
Normal 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′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;
|
70
tsc1877/task1/project/admin/src/pages/500.js
Normal file
70
tsc1877/task1/project/admin/src/pages/500.js
Normal 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;
|
78
tsc1877/task1/project/admin/src/pages/_app.js
Normal file
78
tsc1877/task1/project/admin/src/pages/_app.js
Normal 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;
|
68
tsc1877/task1/project/admin/src/pages/_document.js
Normal file
68
tsc1877/task1/project/admin/src/pages/_document.js
Normal 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;
|
103
tsc1877/task1/project/admin/src/pages/account-settings/index.js
Normal file
103
tsc1877/task1/project/admin/src/pages/account-settings/index.js
Normal 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;
|
@@ -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: {} };
|
||||
});
|
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -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: {} };
|
||||
});
|
@@ -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: {} };
|
||||
});
|
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/PageLayout.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/PageLayout.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/UpdateShouldBeGreaterThanZero.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/UpdateShouldBeGreaterThanZero.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/chrome_FAOCstXMzU.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/chrome_FAOCstXMzU.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/chrome_FgEZO9Esbd.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/chrome_FgEZO9Esbd.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/chrome_hG8wDvhzM7.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/chrome_hG8wDvhzM7.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -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;
|
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/localStorageSavingLoadingCart.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/localStorageSavingLoadingCart.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/login.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/login.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/reverse_proxy.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/reverse_proxy.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/secure_cookies.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/secure_cookies.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/signal_flow_of_paypal.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/signal_flow_of_paypal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/sql_injection_test.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/sql_injection_test.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/table_list.png
(Stored with Git LFS)
Normal file
BIN
tsc1877/task1/project/admin/src/pages/admin/documentation/table_list.png
(Stored with Git LFS)
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.7 KiB |
60
tsc1877/task1/project/admin/src/pages/admin/index.js
Normal file
60
tsc1877/task1/project/admin/src/pages/admin/index.js
Normal 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: {} };
|
||||
});
|
@@ -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: {} };
|
||||
});
|
241
tsc1877/task1/project/admin/src/pages/admin/login/index.js
Normal file
241
tsc1877/task1/project/admin/src/pages/admin/login/index.js
Normal 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: {} };
|
||||
});
|
63
tsc1877/task1/project/admin/src/pages/admin/logout/index.js
Normal file
63
tsc1877/task1/project/admin/src/pages/admin/logout/index.js
Normal 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;
|
35
tsc1877/task1/project/admin/src/pages/admin/order/index.js
Normal file
35
tsc1877/task1/project/admin/src/pages/admin/order/index.js
Normal 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: {} };
|
||||
});
|
@@ -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: {} };
|
||||
});
|
@@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
14
tsc1877/task1/project/admin/src/pages/admin/products/add.js
Normal file
14
tsc1877/task1/project/admin/src/pages/admin/products/add.js
Normal 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: {} };
|
||||
});
|
@@ -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: {} };
|
||||
});
|
@@ -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);
|
@@ -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);
|
22
tsc1877/task1/project/admin/src/pages/api/auth/helloworld.js
Normal file
22
tsc1877/task1/project/admin/src/pages/api/auth/helloworld.js
Normal 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);
|
62
tsc1877/task1/project/admin/src/pages/api/auth/login.js
Normal file
62
tsc1877/task1/project/admin/src/pages/api/auth/login.js
Normal 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);
|
23
tsc1877/task1/project/admin/src/pages/api/auth/model.js
Normal file
23
tsc1877/task1/project/admin/src/pages/api/auth/model.js
Normal 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 };
|
50
tsc1877/task1/project/admin/src/pages/api/categories/add.js
Normal file
50
tsc1877/task1/project/admin/src/pages/api/categories/add.js
Normal 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);
|
@@ -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);
|
@@ -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' });
|
||||
}
|
||||
}
|
24
tsc1877/task1/project/admin/src/pages/api/categories/list.js
Normal file
24
tsc1877/task1/project/admin/src/pages/api/categories/list.js
Normal 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' });
|
||||
}
|
||||
}
|
@@ -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 };
|
@@ -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);
|
27
tsc1877/task1/project/admin/src/pages/api/categories/view.js
Normal file
27
tsc1877/task1/project/admin/src/pages/api/categories/view.js
Normal 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' });
|
||||
}
|
||||
}
|
31
tsc1877/task1/project/admin/src/pages/api/files/get.js
Normal file
31
tsc1877/task1/project/admin/src/pages/api/files/get.js
Normal 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' });
|
||||
}
|
||||
}
|
@@ -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
BIN
tsc1877/task1/project/admin/src/pages/api/files/image.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -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);
|
@@ -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,
|
||||
},
|
||||
};
|
27
tsc1877/task1/project/admin/src/pages/api/inventory/count.js
Normal file
27
tsc1877/task1/project/admin/src/pages/api/inventory/count.js
Normal 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' });
|
||||
}
|
||||
}
|
@@ -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' });
|
||||
}
|
||||
}
|
22
tsc1877/task1/project/admin/src/pages/api/inventory/model.js
Normal file
22
tsc1877/task1/project/admin/src/pages/api/inventory/model.js
Normal 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 };
|
@@ -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);
|
@@ -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);
|
@@ -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' });
|
||||
}
|
||||
}
|
36
tsc1877/task1/project/admin/src/pages/api/order/list.js
Normal file
36
tsc1877/task1/project/admin/src/pages/api/order/list.js
Normal 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' });
|
||||
}
|
||||
}
|
@@ -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' });
|
||||
}
|
||||
}
|
28
tsc1877/task1/project/admin/src/pages/api/order/model.js
Normal file
28
tsc1877/task1/project/admin/src/pages/api/order/model.js
Normal 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 };
|
@@ -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);
|
27
tsc1877/task1/project/admin/src/pages/api/order/view.js
Normal file
27
tsc1877/task1/project/admin/src/pages/api/order/view.js
Normal 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' });
|
||||
}
|
||||
}
|
98
tsc1877/task1/project/admin/src/pages/api/products/add.js
Normal file
98
tsc1877/task1/project/admin/src/pages/api/products/add.js
Normal 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);
|
29
tsc1877/task1/project/admin/src/pages/api/products/delete.js
Normal file
29
tsc1877/task1/project/admin/src/pages/api/products/delete.js
Normal 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);
|
@@ -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' });
|
||||
}
|
||||
}
|
41
tsc1877/task1/project/admin/src/pages/api/products/list.js
Normal file
41
tsc1877/task1/project/admin/src/pages/api/products/list.js
Normal 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);
|
@@ -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' });
|
||||
}
|
||||
}
|
@@ -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' });
|
||||
}
|
||||
}
|
24
tsc1877/task1/project/admin/src/pages/api/products/model.js
Normal file
24
tsc1877/task1/project/admin/src/pages/api/products/model.js
Normal 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 };
|
@@ -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' });
|
||||
}
|
||||
}
|
92
tsc1877/task1/project/admin/src/pages/api/products/update.js
Normal file
92
tsc1877/task1/project/admin/src/pages/api/products/update.js
Normal 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);
|
42
tsc1877/task1/project/admin/src/pages/api/products/view.js
Normal file
42
tsc1877/task1/project/admin/src/pages/api/products/view.js
Normal 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' });
|
||||
}
|
||||
}
|
@@ -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' });
|
||||
}
|
||||
}
|
83
tsc1877/task1/project/admin/src/pages/cards/index.js
Normal file
83
tsc1877/task1/project/admin/src/pages/cards/index.js
Normal 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;
|
37
tsc1877/task1/project/admin/src/pages/form-layouts/index.js
Normal file
37
tsc1877/task1/project/admin/src/pages/form-layouts/index.js
Normal 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;
|
164
tsc1877/task1/project/admin/src/pages/icons/index.js
Normal file
164
tsc1877/task1/project/admin/src/pages/icons/index.js
Normal 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;
|
37
tsc1877/task1/project/admin/src/pages/index.js
Normal file
37
tsc1877/task1/project/admin/src/pages/index.js
Normal 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;
|
101
tsc1877/task1/project/admin/src/pages/index.js.bak
Normal file
101
tsc1877/task1/project/admin/src/pages/index.js.bak
Normal 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
|
10
tsc1877/task1/project/admin/src/pages/pages/error/index.js
Normal file
10
tsc1877/task1/project/admin/src/pages/pages/error/index.js
Normal 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;
|
251
tsc1877/task1/project/admin/src/pages/pages/login/index.js
Normal file
251
tsc1877/task1/project/admin/src/pages/pages/login/index.js
Normal 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;
|
249
tsc1877/task1/project/admin/src/pages/pages/register/index.js
Normal file
249
tsc1877/task1/project/admin/src/pages/pages/register/index.js
Normal 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;
|
@@ -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;
|
158
tsc1877/task1/project/admin/src/pages/shopfront/cart/index.js
Normal file
158
tsc1877/task1/project/admin/src/pages/shopfront/cart/index.js
Normal 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;
|
@@ -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;
|
@@ -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;
|
127
tsc1877/task1/project/admin/src/pages/shopfront/index.js
Normal file
127
tsc1877/task1/project/admin/src/pages/shopfront/index.js
Normal 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;
|
227
tsc1877/task1/project/admin/src/pages/shopfront/login/index.js
Normal file
227
tsc1877/task1/project/admin/src/pages/shopfront/login/index.js
Normal 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;
|
@@ -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;
|
@@ -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
|
67
tsc1877/task1/project/admin/src/pages/tables/index.js
Normal file
67
tsc1877/task1/project/admin/src/pages/tables/index.js
Normal 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;
|
21
tsc1877/task1/project/admin/src/pages/typography/index.js
Normal file
21
tsc1877/task1/project/admin/src/pages/typography/index.js
Normal 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;
|
Reference in New Issue
Block a user