@@ -0,0 +1,2 @@ | |||
REACT_APP_VERSION = v0.5.0 | |||
GENERATE_SOURCEMAP = false |
@@ -0,0 +1,90 @@ | |||
{ | |||
"root": true, | |||
"env": { | |||
"browser": true, | |||
"es2021": true | |||
}, | |||
"extends": [ | |||
"prettier", | |||
"plugin:react/jsx-runtime", | |||
"plugin:jsx-a11y/recommended", | |||
"plugin:react-hooks/recommended", | |||
"eslint:recommended", | |||
"plugin:react/recommended" | |||
], | |||
"settings": { | |||
"react": { | |||
"createClass": "createReactClass", // Regex for Component Factory to use, | |||
// default to "createReactClass" | |||
"pragma": "React", // Pragma to use, default to "React" | |||
"fragment": "Fragment", // Fragment to use (may be a property of <pragma>), default to "Fragment" | |||
"version": "detect", // React version. "detect" automatically picks the version you have installed. | |||
// You can also use `16.0`, `16.3`, etc, if you want to override the detected value. | |||
// It will default to "latest" and warn if missing, and to "detect" in the future | |||
"flowVersion": "0.53" // Flow version | |||
}, | |||
"import/resolver": { | |||
"node": { | |||
"moduleDirectory": ["node_modules", "src/"] | |||
} | |||
} | |||
}, | |||
"parser": "@babel/eslint-parser", | |||
"parserOptions": { | |||
"ecmaFeatures": { | |||
"experimentalObjectRestSpread": true, | |||
"impliedStrict": true, | |||
"jsx": true | |||
}, | |||
"ecmaVersion": 12 | |||
}, | |||
"plugins": ["prettier", "react", "react-hooks"], | |||
"rules": { | |||
"no-debugger": "error", | |||
"react/jsx-uses-react": "error", | |||
"react/jsx-uses-vars": "error", | |||
"react/react-in-jsx-scope": "off", | |||
"no-undef": "off", | |||
"react/display-name": "off", | |||
"react/jsx-filename-extension": "off", | |||
"no-param-reassign": "off", | |||
"react/prop-types": 1, | |||
"react/require-default-props": "off", | |||
"react/no-array-index-key": "off", | |||
"react/jsx-props-no-spreading": "off", | |||
"react/forbid-prop-types": "off", | |||
"import/order": "off", | |||
"import/no-cycle": "off", | |||
"no-console": "off", | |||
"jsx-a11y/anchor-is-valid": "off", | |||
"prefer-destructuring": "off", | |||
"no-shadow": "off", | |||
"import/no-named-as-default": "off", | |||
"import/no-extraneous-dependencies": "off", | |||
"jsx-a11y/no-autofocus": "off", | |||
"no-restricted-imports": [ | |||
"error", | |||
{ | |||
"patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"] | |||
} | |||
], | |||
"no-unused-vars": [ | |||
"error", | |||
{ | |||
"ignoreRestSiblings": false | |||
} | |||
], | |||
"prettier/prettier": [ | |||
"warn", | |||
{ | |||
"bracketSpacing": true, | |||
"printWidth": 140, | |||
"singleQuote": true, | |||
"trailingComma": "none", | |||
"tabWidth": 2, | |||
"useTabs": false, | |||
"endOfLine": "auto" | |||
} | |||
] | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
# Logs | |||
logs | |||
*.log | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
lerna-debug.log* | |||
# Diagnostic reports (https://nodejs.org/api/report.html) | |||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | |||
# Runtime data | |||
pids | |||
*.pid | |||
*.seed | |||
*.pid.lock | |||
# Directory for instrumented libs generated by jscoverage/JSCover | |||
lib-cov | |||
# Coverage directory used by tools like istanbul | |||
coverage | |||
*.lcov | |||
# nyc test coverage | |||
.nyc_output | |||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | |||
.grunt | |||
# Bower dependency directory (https://bower.io/) | |||
bower_components | |||
# node-waf configuration | |||
.lock-wscript | |||
# Compiled binary addons (https://nodejs.org/api/addons.html) | |||
build/Release | |||
# Dependency directories | |||
node_modules/ | |||
jspm_packages/ | |||
# TypeScript v1 declaration files | |||
typings/ | |||
# TypeScript cache | |||
*.tsbuildinfo | |||
# Optional npm cache directory | |||
.npm | |||
# Optional eslint cache | |||
.eslintcache | |||
# Microbundle cache | |||
.rpt2_cache/ | |||
.rts2_cache_cjs/ | |||
.rts2_cache_es/ | |||
.rts2_cache_umd/ | |||
# Optional REPL history | |||
.node_repl_history | |||
# Output of 'npm pack' | |||
*.tgz | |||
# Yarn Integrity file | |||
.yarn-integrity | |||
.idea | |||
# dotenv environment variables file | |||
# .env | |||
.env.test | |||
# parcel-bundler cache (https://parceljs.org/) | |||
.cache | |||
# Next.js build output | |||
.next | |||
# Nuxt.js build / generate output | |||
.nuxt | |||
dist | |||
# Gatsby files | |||
.cache/ | |||
# Comment in the public line in if your project uses Gatsby and *not* Next.js | |||
# https://nextjs.org/blog/next-9-1#public-directory-support | |||
# public | |||
# vuepress build output | |||
.vuepress/dist | |||
# Serverless directories | |||
.serverless/ | |||
# FuseBox cache | |||
.fusebox/ | |||
# DynamoDB Local files | |||
.dynamodb/ | |||
# TernJS port file | |||
.tern-port | |||
# Backup file | |||
*.bak | |||
#output | |||
build |
@@ -0,0 +1,8 @@ | |||
{ | |||
"bracketSpacing": true, | |||
"printWidth": 140, | |||
"singleQuote": true, | |||
"trailingComma": "none", | |||
"tabWidth": 2, | |||
"useTabs": false | |||
} |
@@ -0,0 +1,128 @@ | |||
# Contributor Covenant Code of Conduct | |||
## Our Pledge | |||
We as members, contributors, and leaders pledge to make participation in our | |||
community a harassment-free experience for everyone, regardless of age, body | |||
size, visible or invisible disability, ethnicity, sex characteristics, gender | |||
identity and expression, level of experience, education, socio-economic status, | |||
nationality, personal appearance, race, religion, or sexual identity | |||
and orientation. | |||
We pledge to act and interact in ways that contribute to an open, welcoming, | |||
diverse, inclusive, and healthy community. | |||
## Our Standards | |||
Examples of behavior that contributes to a positive environment for our | |||
community include: | |||
- Demonstrating empathy and kindness toward other people | |||
- Being respectful of differing opinions, viewpoints, and experiences | |||
- Giving and gracefully accepting constructive feedback | |||
- Accepting responsibility and apologizing to those affected by our mistakes, | |||
and learning from the experience | |||
- Focusing on what is best not just for us as individuals, but for the | |||
overall community | |||
Examples of unacceptable behavior include: | |||
- The use of sexualized language or imagery, and sexual attention or | |||
advances of any kind | |||
- Trolling, insulting or derogatory comments, and personal or political attacks | |||
- Public or private harassment | |||
- Publishing others' private information, such as a physical or email | |||
address, without their explicit permission | |||
- Other conduct which could reasonably be considered inappropriate in a | |||
professional setting | |||
## Enforcement Responsibilities | |||
Community leaders are responsible for clarifying and enforcing our standards of | |||
acceptable behavior and will take appropriate and fair corrective action in | |||
response to any behavior that they deem inappropriate, threatening, offensive, | |||
or harmful. | |||
Community leaders have the right and responsibility to remove, edit, or reject | |||
comments, commits, code, wiki edits, issues, and other contributions that are | |||
not aligned to this Code of Conduct, and will communicate reasons for moderation | |||
decisions when appropriate. | |||
## Scope | |||
This Code of Conduct applies within all community spaces, and also applies when | |||
an individual is officially representing the community in public spaces. | |||
Examples of representing our community include using an official e-mail address, | |||
posting via an official social media account, or acting as an appointed | |||
representative at an online or offline event. | |||
## Enforcement | |||
Instances of abusive, harassing, or otherwise unacceptable behavior may be | |||
reported to the community leaders responsible for enforcement at | |||
[email protected]. | |||
All complaints will be reviewed and investigated promptly and fairly. | |||
All community leaders are obligated to respect the privacy and security of the | |||
reporter of any incident. | |||
## Enforcement Guidelines | |||
Community leaders will follow these Community Impact Guidelines in determining | |||
the consequences for any action they deem in violation of this Code of Conduct: | |||
### 1. Correction | |||
**Community Impact**: Use of inappropriate language or other behavior deemed | |||
unprofessional or unwelcome in the community. | |||
**Consequence**: A private, written warning from community leaders, providing | |||
clarity around the nature of the violation and an explanation of why the | |||
behavior was inappropriate. A public apology may be requested. | |||
### 2. Warning | |||
**Community Impact**: A violation through a single incident or series | |||
of actions. | |||
**Consequence**: A warning with consequences for continued behavior. No | |||
interaction with the people involved, including unsolicited interaction with | |||
those enforcing the Code of Conduct, for a specified period of time. This | |||
includes avoiding interactions in community spaces as well as external channels | |||
like social media. Violating these terms may lead to a temporary or | |||
permanent ban. | |||
### 3. Temporary Ban | |||
**Community Impact**: A serious violation of community standards, including | |||
sustained inappropriate behavior. | |||
**Consequence**: A temporary ban from any sort of interaction or public | |||
communication with the community for a specified period of time. No public or | |||
private interaction with the people involved, including unsolicited interaction | |||
with those enforcing the Code of Conduct, is allowed during this period. | |||
Violating these terms may lead to a permanent ban. | |||
### 4. Permanent Ban | |||
**Community Impact**: Demonstrating a pattern of violation of community | |||
standards, including sustained inappropriate behavior, harassment of an | |||
individual, or aggression toward or disparagement of classes of individuals. | |||
**Consequence**: A permanent ban from any sort of public interaction within | |||
the community. | |||
## Attribution | |||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], | |||
version 2.0, available at | |||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. | |||
Community Impact Guidelines were inspired by [Mozilla's code of conduct | |||
enforcement ladder](https://github.com/mozilla/diversity). | |||
[homepage]: https://www.contributor-covenant.org | |||
For answers to common questions about this code of conduct, see the FAQ at | |||
https://www.contributor-covenant.org/faq. Translations are available at | |||
https://www.contributor-covenant.org/translations. |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2022 CodedThemes | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1 @@ | |||
2fi PNSPS frontend temp |
@@ -0,0 +1,9 @@ | |||
{ | |||
"compilerOptions": { | |||
"target": "esnext", | |||
"module": "esnext", | |||
"baseUrl": "src" | |||
}, | |||
"include": ["src/**/*"], | |||
"exclude": ["node_modules"] | |||
} |
@@ -0,0 +1,93 @@ | |||
{ | |||
"name": "pnsps-react", | |||
"version": "1.1.2", | |||
"private": true, | |||
"homepage": ".", | |||
"dependencies": { | |||
"@ant-design/colors": "^6.0.0", | |||
"@ant-design/icons": "^4.7.0", | |||
"@casl/react": "^3.1.0", | |||
"@emotion/cache": "^11.10.3", | |||
"@emotion/react": "^11.10.4", | |||
"@emotion/styled": "^11.10.4", | |||
"@mui/icons-material": "^5.14.3", | |||
"@mui/lab": "^5.0.0-alpha.100", | |||
"@mui/material": "^5.10.6", | |||
"@reduxjs/toolkit": "^1.8.5", | |||
"@testing-library/jest-dom": "^5.16.5", | |||
"@testing-library/react": "^13.4.0", | |||
"@testing-library/user-event": "^14.4.3", | |||
"apexcharts": "^3.35.5", | |||
"axios": "^1.4.0", | |||
"formik": "^2.2.9", | |||
"framer-motion": "^7.3.6", | |||
"history": "^5.3.0", | |||
"jwt-decode": "^3.1.2", | |||
"lodash": "^4.17.21", | |||
"prop-types": "^15.8.1", | |||
"react": "^18.2.0", | |||
"react-apexcharts": "^1.4.0", | |||
"react-copy-to-clipboard": "^5.1.0", | |||
"react-device-detect": "^2.2.2", | |||
"react-dom": "^18.2.0", | |||
"react-draggable": "^4.4.5", | |||
"react-element-to-jsx-string": "^15.0.0", | |||
"react-number-format": "^4.9.4", | |||
"react-perfect-scrollbar": "^1.5.8", | |||
"react-redux": "^8.0.4", | |||
"react-router": "^6.4.1", | |||
"react-router-dom": "^6.4.1", | |||
"react-scripts": "^5.0.1", | |||
"react-syntax-highlighter": "^15.5.0", | |||
"react-window": "^1.8.7", | |||
"redux": "^4.2.0", | |||
"simplebar": "^5.3.8", | |||
"simplebar-react": "^2.4.1", | |||
"typescript": "4.8.3", | |||
"web-vitals": "^3.0.2", | |||
"yup": "^0.32.11" | |||
}, | |||
"scripts": { | |||
"start": "react-scripts start", | |||
"build": "react-scripts build", | |||
"test": "react-scripts test", | |||
"eject": "react-scripts eject" | |||
}, | |||
"eslintConfig": { | |||
"extends": [ | |||
"react-app", | |||
"react-app/jest" | |||
] | |||
}, | |||
"babel": { | |||
"presets": [ | |||
"@babel/preset-react" | |||
] | |||
}, | |||
"browserslist": { | |||
"production": [ | |||
">0.2%", | |||
"not dead", | |||
"not op_mini all" | |||
], | |||
"development": [ | |||
"last 1 chrome version", | |||
"last 1 firefox version", | |||
"last 1 safari version" | |||
] | |||
}, | |||
"devDependencies": { | |||
"@babel/core": "^7.21.4", | |||
"@babel/eslint-parser": "^7.21.3", | |||
"eslint": "^8.38.0", | |||
"eslint-config-prettier": "^8.8.0", | |||
"eslint-config-react-app": "^7.0.1", | |||
"eslint-plugin-flowtype": "^8.0.3", | |||
"eslint-plugin-import": "^2.27.5", | |||
"eslint-plugin-jsx-a11y": "^6.7.1", | |||
"eslint-plugin-prettier": "^4.2.1", | |||
"eslint-plugin-react": "^7.32.2", | |||
"eslint-plugin-react-hooks": "^4.6.0", | |||
"prettier": "^2.8.7" | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<path d="M1.63954 13.3644L3.95187 11.052L3.95428 11.0496H8.30453L6.5736 12.7806L6.12669 13.2275L4.35415 15L4.57368 15.2201L14.5039 25.1498L24.6537 15L22.8805 13.2275L22.7557 13.102L20.7033 11.0496H25.0535L25.0559 11.052L26.8683 12.8644L29.0039 15L14.5039 29.5L0.00390625 15L1.63954 13.3644ZM14.5039 0.5L22.8823 8.87842H18.5321L14.5039 4.85024L10.4757 8.87842H6.12548L14.5039 0.5Z" fill="#096DD9"/> | |||
<path d="M4.35477 15.0002L6.12731 13.2276L6.57422 12.7807L4.84389 11.0498H3.9549L3.95249 11.0522L1.64016 13.3645L3.85961 15.5731L4.35477 15.0002Z" fill="url(#paint0_linear_112102_1824)"/> | |||
<path d="M22.8814 13.2276L24.6545 15.0002L24.479 15.1757L24.4796 15.1763L26.8691 12.8646L25.0568 11.0522L25.0544 11.0498H24.8783L22.7565 13.1022L22.8814 13.2276Z" fill="url(#paint1_linear_112102_1824)"/> | |||
<path d="M3.9497 11.0498L3.95211 11.0522L6.12693 13.2276L14.5041 21.6043L25.0586 11.0498H3.9497Z" fill="#1890FF"/> | |||
<defs> | |||
<linearGradient id="paint0_linear_112102_1824" x1="5.62978" y1="11.5889" x2="2.57161" y2="14.6471" gradientUnits="userSpaceOnUse"> | |||
<stop stop-color="#023B95"/> | |||
<stop offset="0.9637" stop-color="#096CD9" stop-opacity="0"/> | |||
</linearGradient> | |||
<linearGradient id="paint1_linear_112102_1824" x1="23.2722" y1="11.6281" x2="25.7451" y2="14.4382" gradientUnits="userSpaceOnUse"> | |||
<stop stop-color="#023B95"/> | |||
<stop offset="1" stop-color="#096DD9" stop-opacity="0"/> | |||
</linearGradient> | |||
</defs> | |||
</svg> |
@@ -0,0 +1,72 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
<meta name="theme-color" content="#1f1f1f" /> | |||
<meta name="title" content="PNSPS" /> | |||
<meta | |||
name="description" | |||
content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license." | |||
/> | |||
<meta | |||
name="keywords" | |||
content="react dashboard, react admin, react redux dashboard, ant design template, saas admin, free react dashboard" | |||
/> | |||
<meta name="author" content="CodedThemes" /> | |||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | |||
<!-- Open Graph / Facebook --> | |||
<meta property="og:locale" content="en_US" /> | |||
<meta property="og:type" content="website" /> | |||
<meta property="og:url" content="https://mantisdashboard.io/" /> | |||
<meta property="og:site_name" content="mantisdashboard.io" /> | |||
<meta property="article:publisher" content="https://www.facebook.com/codedthemes" /> | |||
<meta property="og:title" content="PNSPS" /> | |||
<meta | |||
property="og:description" | |||
content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license." | |||
/> | |||
<meta property="og:image" content="https://mantisdashboard.io/adv-banner-images/og-social.png" /> | |||
<!-- Twitter --> | |||
<meta property="twitter:card" content="summary_large_image" /> | |||
<meta property="twitter:url" content="https://mantisdashboard.io" /> | |||
<meta property="twitter:title" content="PNSPS" /> | |||
<meta | |||
property="twitter:description" | |||
content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license." | |||
/> | |||
<meta property="twitter:image" content="https://mantisdashboard.io/adv-banner-images/og-social.png" /> | |||
<meta name="twitter:creator" content="@codedthemes" /> | |||
<!-- | |||
Notice the use of %PUBLIC_URL% in the tags above. | |||
It will be replaced with the URL of the `public` folder during the build. | |||
Only files inside the `public` folder can be referenced from the HTML. | |||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | |||
work correctly both with client-side routing and a non-root public URL. | |||
Learn how to configure a non-root public URL by running `npm run build`. | |||
--> | |||
<title>PNSPS</title> | |||
<link rel="preconnect" href="https://fonts.gstatic.com" /> | |||
<link | |||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap&family=Public+Sans:wght@400;500;600;700" | |||
rel="stylesheet" | |||
/> | |||
</head> | |||
<body> | |||
<noscript>You need to enable JavaScript to run this app.</noscript> | |||
<div id="root"></div> | |||
<!-- | |||
This HTML file is a template. | |||
If you open it directly in the browser, you will see an empty page. | |||
You can add webfonts, meta tags, or analytics to this file. | |||
The build step will place the bundled scripts into the <body> tag. | |||
To begin the development, run `npm start` or `yarn start`. | |||
To create a production bundle, use `npm run build` or `yarn build`. | |||
--> | |||
</body> | |||
</html> |
@@ -0,0 +1,18 @@ | |||
// project import | |||
import Routes from 'routes'; | |||
import ThemeCustomization from 'themes'; | |||
import ScrollTop from 'components/ScrollTop'; | |||
//import {isUserLoggedIn} from 'utils/Utils'; | |||
//import {DefaultRoute} from 'routes/index' | |||
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== // | |||
const App = () => ( | |||
<ThemeCustomization> | |||
<ScrollTop> | |||
<Routes> | |||
</Routes> | |||
</ScrollTop> | |||
</ThemeCustomization> | |||
); | |||
export default App; |
@@ -0,0 +1,9 @@ | |||
import React from 'react'; | |||
import { render, screen } from '@testing-library/react'; | |||
import App from './App'; | |||
test('renders learn react link', () => { | |||
render(<App />); | |||
const linkElement = screen.getByText(/learn react/i); | |||
expect(linkElement).toBeInTheDocument(); | |||
}); |
@@ -0,0 +1,38 @@ | |||
// material-ui | |||
// import { useTheme } from '@mui/material/styles'; | |||
import { Box } from '@mui/material'; | |||
// ==============================|| AUTH BLUR BACK SVG ||============================== // | |||
const AuthBackground = () => { | |||
// const theme = useTheme(); | |||
return ( | |||
<Box sx={{ position: 'absolute', filter: 'blur(18px)', zIndex: -1, bottom: 0 }}> | |||
{/* <svg width="100%" height="calc(100vh - 175px)" viewBox="0 0 405 809" fill="none" xmlns="http://www.w3.org/2000/svg"> */} | |||
<svg width="100%" viewBox="0 0 405 809" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<defs> | |||
<clipPath id="clipPath22"> | |||
<path d="m0 256h213v-256h-213z"/> | |||
</clipPath> | |||
</defs> | |||
<g transform="translate(-31.999 -25.836)"> | |||
<g transform="matrix(.35278 0 0 -.35278 46.979 25.836)"> | |||
<path d="m0 0h16.273v-37.417l17.85-0.035v-12.864h-34.123z" fill="#00a650"/> | |||
</g> | |||
<g transform="matrix(.35278 0 0 -.35278 16.798 99.848)"> | |||
<g clipPath="url(#clipPath22)"> | |||
<g transform="translate(82.049 183.46)"> | |||
<path d="m0 0c-17.536-0.633-27.516-14.176-27.516-28.318 0-14.143 9.241-28.019 27.563-28.071 3.447-9e-3 8.679 1.279 21.255 17.378l-17.808 0.07v10.335l34.132 0.061v-35.705l-10.467 3.577 0.045 12.858c-12.924-19.307-24.442-19.361-26.895-19.378-23.677-0.157-39.268 17.825-39.268 38.884s14.944 37.674 38.959 38.589z" fill="#1c63b7"/> | |||
</g> | |||
<g transform="translate(119.64 176.52)"> | |||
<path d="m0 0-10.391 0.041v11.372c3.151 1.943 6.524 3.573 10.424 4.515 3.633 0.879 7.526 1.321 11.567 1.321 24.407 0 39.006-17.548 39.006-38.608s-14.633-39.843-39.006-38.608c-2.765 0.14-3.93 0.531-7.946 1.652l0.055 9.924c3.341-0.628 3.344-0.529 7.891-1.047 17.298-1.971 27.561 13.927 27.561 28.069 0 14.143-10.11 28.09-27.561 28.09-4.162 0-8.175-0.816-11.567-2.433l-0.033-4.288" fill="#1c63b7"/> | |||
</g> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
</Box> | |||
); | |||
}; | |||
export default AuthBackground; |
@@ -0,0 +1,3 @@ | |||
<svg width="9" height="16" viewBox="0 0 9 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<path d="M6.04102 0.125034C3.96989 0.125034 2.29102 1.80391 2.29102 3.87503V5.75003H0.0410156V8.75003H2.29102V15.875H5.29102V8.75003H7.91602L8.29102 5.75003H5.29102V4.25003C5.29102 3.42166 5.96264 2.75003 6.79102 2.75003H8.29102V0.245784C7.57514 0.171909 6.76139 0.123534 6.04102 0.125034Z" fill="#4267B2"/> | |||
</svg> |
@@ -0,0 +1,6 @@ | |||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<path d="M15.6871 6.53113H15.083V6.5H8.33301V9.5H12.5716C11.9533 11.2464 10.2916 12.5 8.33301 12.5C5.84788 12.5 3.83301 10.4851 3.83301 8C3.83301 5.51487 5.84788 3.5 8.33301 3.5C9.48013 3.5 10.5238 3.93275 11.3184 4.63962L13.4398 2.51825C12.1003 1.26987 10.3085 0.5 8.33301 0.5C4.19113 0.5 0.833008 3.85812 0.833008 8C0.833008 12.1419 4.19113 15.5 8.33301 15.5C12.4749 15.5 15.833 12.1419 15.833 8C15.833 7.49713 15.7813 7.00625 15.6871 6.53113Z" fill="#FFC107"/> | |||
<path d="M1.69824 4.50913L4.16237 6.31625C4.82912 4.6655 6.44387 3.5 8.33349 3.5C9.48062 3.5 10.5242 3.93275 11.3189 4.63963L13.4402 2.51825C12.1007 1.26988 10.309 0.5 8.33349 0.5C5.45274 0.5 2.95449 2.12638 1.69824 4.50913Z" fill="#FF3D00"/> | |||
<path d="M8.33312 15.5C10.2704 15.5 12.0306 14.7586 13.3615 13.553L11.0402 11.5888C10.2872 12.1591 9.35125 12.5 8.33312 12.5C6.38237 12.5 4.726 11.2561 4.102 9.52026L1.65625 11.4046C2.8975 13.8335 5.41825 15.5 8.33312 15.5Z" fill="#4CAF50"/> | |||
<path d="M15.6871 6.53113H15.083V6.5H8.33301V9.5H12.5716C12.2746 10.3389 11.735 11.0622 11.039 11.5891C11.0394 11.5887 11.0398 11.5887 11.0401 11.5884L13.3614 13.5526C13.1971 13.7019 15.833 11.75 15.833 8C15.833 7.49713 15.7813 7.00625 15.6871 6.53113Z" fill="#1976D2"/> | |||
</svg> |
@@ -0,0 +1,3 @@ | |||
<svg width="16" height="13" viewBox="0 0 16 13" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<path d="M15.5 1.42863C14.9488 1.67278 14.3559 1.83568 13.7306 1.91276C14.3663 1.53529 14.8555 0.933256 15.085 0.222065C14.4901 0.570786 13.831 0.827014 13.1298 0.962003C12.5698 0.368303 11.7711 0 10.8862 0C9.18636 0 7.80856 1.36572 7.80856 3.04975C7.80856 3.28806 7.83647 3.52012 7.88897 3.74552C5.33168 3.6172 3.06354 2.40147 1.54616 0.55662C1.27952 1.00742 1.12953 1.53529 1.12953 2.09233C1.12953 3.15099 1.67157 4.08299 2.49817 4.63211C1.99363 4.6167 1.51866 4.47629 1.10287 4.25131C1.10287 4.26048 1.10287 4.27423 1.10287 4.28714C1.10287 5.7666 2.16403 6.99858 3.57058 7.27898C3.31352 7.34939 3.04187 7.38855 2.76189 7.38855C2.56316 7.38855 2.36943 7.36605 2.18194 7.33231C2.57358 8.54137 3.70973 9.42505 5.05587 9.4513C4.00262 10.2679 2.67607 10.757 1.23369 10.757C0.984543 10.757 0.740813 10.7429 0.5 10.7137C1.8628 11.5765 3.481 12.0823 5.21794 12.0823C10.8779 12.0823 13.9743 7.43438 13.9743 3.40222C13.9743 3.27014 13.9701 3.13849 13.9639 3.0085C14.568 2.58187 15.0888 2.04358 15.5 1.42863Z" fill="#03A9F4"/> | |||
</svg> |
@@ -0,0 +1,3 @@ | |||
body{ | |||
padding-top: 0px; | |||
} |
@@ -0,0 +1,84 @@ | |||
#nav{ | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
background-color: white; | |||
/* padding: 20px 80px; */ | |||
box-shadow: 0 5px 15px rgba(0,0 0,0,0.06); | |||
position:fixed; | |||
top: 0px; | |||
width: 100%; | |||
z-index: 9999; | |||
border-bottom: 3px solid #0C489E; | |||
} | |||
#navbar{ | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
#navbar li{ | |||
list-style: none; | |||
padding: 0 20px; | |||
position: relative; | |||
} | |||
#navbar li a{ | |||
text-decoration: none; | |||
font-size: 1.5rem; | |||
font-weight: 600; | |||
font-family: 微軟正黑體; | |||
color: black; | |||
transition: 0.3s ease-in-out; | |||
} | |||
#navbar li a:hover{ | |||
color: #0C489E; | |||
} | |||
#navbar li a:hover::after, | |||
#navbar li a:focus::after{ | |||
content: ""; | |||
width: 60%; | |||
height: 2px; | |||
background:#0C489E; | |||
position: absolute; | |||
top: -4px; | |||
left: 20px; | |||
} | |||
#systemTitle{ | |||
text-decoration: none; | |||
font-size: 1.3rem; | |||
font-weight: 600; | |||
color: #0C489E; | |||
transition: 0.3s ease-in-out; | |||
font-family: 微軟正黑體; | |||
text-align: center; | |||
} | |||
#mobileTitle{ | |||
text-decoration: none; | |||
font-size: 1.2rem; | |||
font-weight: 600; | |||
color: #0C489E; | |||
transition: 0.3s ease-in-out; | |||
font-family: 微軟正黑體; | |||
text-align: center; | |||
} | |||
#sidebar{ | |||
align-items: center; | |||
justify-content: center; | |||
padding: 0; | |||
} | |||
#sidebar li{ | |||
list-style: none; | |||
padding: 0 20px; | |||
position: relative; | |||
} | |||
#sidebar li a{ | |||
text-decoration: none; | |||
font-size: 1.3rem; | |||
font-weight: 600; | |||
font-family: 微軟正黑體; | |||
color: black; | |||
transition: 0.3s ease-in-out; | |||
} | |||
#sidebar li a:hover{ | |||
color: #0C489E; | |||
} |
@@ -0,0 +1,4 @@ | |||
body{ | |||
padding-top: 43px; | |||
font-family: 微軟正黑體; | |||
} |
@@ -0,0 +1,4 @@ | |||
.apexcharts-legend-series .apexcharts-legend-marker { | |||
left: -4px !important; | |||
top: 2px !important; | |||
} |
@@ -0,0 +1,92 @@ | |||
// ** UseJWT import to get config | |||
import axios from "axios"; | |||
import {isUserLoggedIn} from "../utils/Utils"; | |||
import jwtApplicationConfig from "auth/jwtApplicationConfig"; | |||
import jwt_decode from "jwt-decode"; | |||
//import axios from "axios"; | |||
export const refreshIntervalName = 'refreshInterval' | |||
export const predictProductionQty = 'predictProductionQty' | |||
export const predictUsageCount = 'predictUsageCount' | |||
export const windowCount = 'windowCount' | |||
// ** Handle User Login | |||
export const handleLogin = data => { | |||
return dispatch => { | |||
dispatch({ | |||
type: 'LOGIN', | |||
data, | |||
jwtApplicationConfig, | |||
accessToken: data['accessToken'], | |||
refreshToken: data['refreshToken'] | |||
}) | |||
console.log(data) | |||
// ** Add to user, accessToken & refreshToken to localStorage | |||
localStorage.setItem('userData', JSON.stringify(data)) | |||
localStorage.setItem('accessToken', data.accessToken) | |||
localStorage.setItem('refreshToken', data.refreshToken) | |||
localStorage.setItem('axiosToken', "Bearer " + data.accessToken) | |||
//localStorage.setItem(config.storageUserRoleKeyName, JSON.stringify(data.role).slice(1).slice(0, -1)) | |||
localStorage.setItem(refreshIntervalName, "60") | |||
// for demo only | |||
localStorage.setItem(windowCount, '0') | |||
localStorage.setItem(predictProductionQty, '0') | |||
localStorage.setItem(predictUsageCount, '0') | |||
} | |||
} | |||
export const isTokenValid = () =>{ | |||
let isExpired = false; | |||
const token = localStorage.getItem('accessToken'); | |||
let decodedToken=jwt_decode(token); | |||
let dateNow = new Date(); | |||
if(decodedToken.exp < dateNow.getTime()) | |||
isExpired = true; | |||
return isExpired; | |||
} | |||
// ** Handle User Logout | |||
export const handleLogoutFunction = () => { | |||
return dispatch => { | |||
dispatch({ | |||
type: 'LOGOUT', | |||
accessToken: null, | |||
refreshToken: null | |||
} | |||
) | |||
console.log("logout"); | |||
// ** Remove user, accessToken & refreshToken from localStorage | |||
localStorage.removeItem('userData') | |||
localStorage.removeItem('accessToken') | |||
localStorage.removeItem('refreshToken') | |||
//localStorage.removeItem(config.storageUserRoleKeyName) | |||
localStorage.removeItem(refreshIntervalName) | |||
localStorage.removeItem(windowCount) | |||
localStorage.removeItem(predictProductionQty) | |||
localStorage.removeItem(predictUsageCount) | |||
} | |||
} | |||
// ** Handle axios token | |||
export const setupAxiosInterceptors = () => { | |||
axios.interceptors.request.use( | |||
config => { | |||
// ** Get token from localStorage | |||
const accessToken = localStorage.getItem("accessToken") | |||
// ** If token is present add it to request's Authorization Header | |||
if (isUserLoggedIn()) { | |||
config.headers.Authorization = `${jwtApplicationConfig.tokenType} ${accessToken}` | |||
} | |||
config.headers['X-Authorization'] = process.env.REACT_APP_API_KEY | |||
return config | |||
}, | |||
error => Promise.reject(error) | |||
) | |||
} |
@@ -0,0 +1,11 @@ | |||
// ** JWT Service Import | |||
import JwtService from './jwtService' | |||
// ** Export Service as useJwt | |||
export default function useJwt(jwtOverrideConfig) { | |||
const jwt = new JwtService(jwtOverrideConfig) | |||
return { | |||
jwt | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
// ** Auth Endpoints | |||
export default { | |||
loginEndpoint: '/jwt/login', | |||
registerEndpoint: '/jwt/register', | |||
refreshEndpoint: '/jwt/refresh-token', | |||
logoutEndpoint: '/jwt/logout', | |||
// ** This will be prefixed in authorization header with token | |||
// ? e.g. Authorization: Bearer <token> | |||
tokenType: 'Bearer', | |||
// ** Value of this property will be used as key to store JWT token in storage | |||
storageTokenKeyName: 'accessToken', | |||
storageRefreshTokenKeyName: 'refreshToken' | |||
} |
@@ -0,0 +1,111 @@ | |||
import axios from 'axios' | |||
import jwtDefaultConfig from './jwtDefaultConfig' | |||
export default class JwtService { | |||
// ** jwtConfig <= Will be used by this service | |||
jwtConfig = {...jwtDefaultConfig} | |||
// ** For Refreshing Token | |||
isAlreadyFetchingAccessToken = false | |||
// ** For Refreshing Token | |||
subscribers = [] | |||
constructor(jwtOverrideConfig) { | |||
this.jwtConfig = {...this.jwtConfig, ...jwtOverrideConfig} | |||
// ** Request Interceptor | |||
axios.interceptors.request.use( | |||
config => { | |||
// ** Get token from localStorage | |||
const accessToken = this.getToken() | |||
// ** If token is present add it to request's Authorization Header | |||
if (accessToken) { | |||
// ** eslint-disable-next-line no-param-reassign | |||
config.headers.Authorization = `${this.jwtConfig.tokenType} ${accessToken}` | |||
} | |||
config.headers['X-Authorization'] = process.env.REACT_APP_API_KEY | |||
return config | |||
}, | |||
error => Promise.reject(error) | |||
) | |||
// ** Add request/response interceptor | |||
axios.interceptors.response.use( | |||
response => response, | |||
error => { | |||
// ** const { config, response: { status } } = error | |||
const {config, response} = error | |||
const originalRequest = config | |||
// ** if (status === 401) { | |||
if (response && response.status === 401) { | |||
if (!this.isAlreadyFetchingAccessToken) { | |||
this.isAlreadyFetchingAccessToken = true | |||
this.refreshToken().then(r => { | |||
this.isAlreadyFetchingAccessToken = false | |||
// ** Update accessToken in localStorage | |||
this.setToken(r.data.accessToken) | |||
this.setRefreshToken(r.data.refreshToken) | |||
this.onAccessTokenFetched(r.data.accessToken) | |||
}) | |||
} | |||
const retryOriginalRequest = new Promise(resolve => { | |||
this.addSubscriber(accessToken => { | |||
// ** Make sure to assign accessToken according to your response. | |||
// ** Check: https://pixinvent.ticksy.com/ticket/2413870 | |||
// ** Change Authorization header | |||
originalRequest.headers.Authorization = `${this.jwtConfig.tokenType} ${accessToken}` | |||
resolve(this.axios(originalRequest)) | |||
}) | |||
}) | |||
return retryOriginalRequest | |||
} | |||
return Promise.reject(error) | |||
} | |||
) | |||
} | |||
onAccessTokenFetched(accessToken) { | |||
this.subscribers = this.subscribers.filter(callback => callback(accessToken)) | |||
} | |||
addSubscriber(callback) { | |||
this.subscribers.push(callback) | |||
} | |||
getToken() { | |||
return localStorage.getItem(this.jwtConfig.storageTokenKeyName) | |||
} | |||
getRefreshToken() { | |||
return localStorage.getItem(this.jwtConfig.storageRefreshTokenKeyName) | |||
} | |||
setToken(value) { | |||
localStorage.setItem(this.jwtConfig.storageTokenKeyName, value) | |||
} | |||
setRefreshToken(value) { | |||
localStorage.setItem(this.jwtConfig.storageRefreshTokenKeyName, value) | |||
} | |||
login(...args) { | |||
return axios.post(this.jwtConfig.loginEndpoint, ...args) | |||
} | |||
register(...args) { | |||
return axios.post(this.jwtConfig.registerEndpoint, ...args) | |||
} | |||
refreshToken() { | |||
return axios.post(this.jwtConfig.refreshEndpoint, { | |||
refreshToken: this.getRefreshToken() | |||
}) | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
import createJwt from './coreUseJwt' | |||
import jwtApplicationConfig from "../jwtApplicationConfig" | |||
const { jwt } = createJwt(jwtApplicationConfig) | |||
export default jwt |
@@ -0,0 +1,17 @@ | |||
import {apiPath} from "./utils" | |||
export default { | |||
loginEndpoint: `${apiPath}/login`, | |||
registerEndpoint: `${apiPath}/register`, | |||
refreshEndpoint: `${apiPath}/refresh/token`, | |||
logoutEndpoint: `/jwt/logout`, | |||
// ** This will be prefixed in authorization header with token | |||
// ? e.g. Authorization: Bearer <token> | |||
tokenType: 'Bearer', | |||
// ** Value of this property will be used as key to store JWT token in storage | |||
storageTokenKeyName: 'accessToken', | |||
storageRefreshTokenKeyName: 'refreshToken', | |||
storageUserRoleKeyName: 'role' | |||
} |
@@ -0,0 +1,17 @@ | |||
import {apiPath} from "./utils" | |||
export default { | |||
loginEndpoint: `${apiPath}/login`, | |||
registerEndpoint: `${apiPath}/register`, | |||
refreshEndpoint: `${apiPath}/refresh/token`, | |||
logoutEndpoint: `/jwt/logout`, | |||
// ** This will be prefixed in authorization header with token | |||
// ? e.g. Authorization: Bearer <token> | |||
tokenType: 'Bearer', | |||
// ** Value of this property will be used as key to store JWT token in storage | |||
storageTokenKeyName: 'accessToken', | |||
storageRefreshTokenKeyName: 'refreshToken', | |||
storageUserRoleKeyName: 'role' | |||
} |
@@ -0,0 +1,33 @@ | |||
import useJwt from 'auth/jwt/coreUseJwt' | |||
/** | |||
* Return if user is logged in | |||
* This is completely up to you and how you want to store the token in your frontend application | |||
* e.g. If you are using cookies to store the application please update this function | |||
*/ | |||
// eslint-disable-next-line arrow-body-style | |||
export const hostname = "localhost" | |||
const hostPort = "8090" | |||
export const hostPath = `http://${hostname}:${hostPort}` | |||
export const apiPath = `${hostPath}/api` | |||
export const isUserLoggedIn = () => { | |||
return localStorage.getItem('userData') && localStorage.getItem(useJwt.jwtConfig.storageTokenKeyName) | |||
} | |||
export const getUserData = () => JSON.parse(localStorage.getItem('userData')) | |||
/** | |||
* This function is used for demo purpose route navigation | |||
* In real app you won't need this function because your app will navigate to same route for each users regardless of ability | |||
* Please note role field is just for showing purpose it's not used by anything in frontend | |||
* We are checking role just for ease | |||
* NOTE: If you have different pages to navigate based on user ability then this function can be useful. However, you need to update it. | |||
* @param {String} userRole Role of user | |||
*/ | |||
export const getHomeRouteForLoggedInUser = userRole => { | |||
if (userRole === 'admin') return '/' | |||
if (userRole === 'user') return '/' | |||
if (userRole === 'client') return {name: 'access-control'} | |||
return {name: 'auth-login'} | |||
} |
@@ -0,0 +1,29 @@ | |||
import PropTypes from 'prop-types'; | |||
// third-party | |||
import { motion } from 'framer-motion'; | |||
// ==============================|| ANIMATION BUTTON ||============================== // | |||
export default function AnimateButton({ children, type }) { | |||
switch (type) { | |||
case 'rotate': // only available in paid version | |||
case 'slide': // only available in paid version | |||
case 'scale': // only available in paid version | |||
default: | |||
return ( | |||
<motion.div whileHover={{ scale: 1 }} whileTap={{ scale: 0.9 }}> | |||
{children} | |||
</motion.div> | |||
); | |||
} | |||
} | |||
AnimateButton.propTypes = { | |||
children: PropTypes.node, | |||
type: PropTypes.oneOf(['slide', 'scale', 'rotate']) | |||
}; | |||
AnimateButton.defaultProps = { | |||
type: 'scale' | |||
}; |
@@ -0,0 +1,106 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useEffect, useState } from 'react'; | |||
import { Link, useLocation } from 'react-router-dom'; | |||
// material-ui | |||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs'; | |||
import { Grid, Typography } from '@mui/material'; | |||
// project imports | |||
import MainCard from '../MainCard'; | |||
// ==============================|| BREADCRUMBS ||============================== // | |||
const Breadcrumbs = ({ navigation, title, ...others }) => { | |||
const location = useLocation(); | |||
const [main, setMain] = useState(); | |||
const [item, setItem] = useState(); | |||
// set active item state | |||
const getCollapse = (menu) => { | |||
if (menu.children) { | |||
menu.children.filter((collapse) => { | |||
if (collapse.type && collapse.type === 'collapse') { | |||
getCollapse(collapse); | |||
} else if (collapse.type && collapse.type === 'item') { | |||
if (location.pathname === collapse.url) { | |||
setMain(menu); | |||
setItem(collapse); | |||
} | |||
} | |||
return false; | |||
}); | |||
} | |||
}; | |||
useEffect(() => { | |||
navigation?.items?.map((menu) => { | |||
if (menu.type && menu.type === 'group') { | |||
getCollapse(menu); | |||
} | |||
return false; | |||
}); | |||
}); | |||
// only used for component demo breadcrumbs | |||
if (location.pathname === '/breadcrumbs') { | |||
location.pathname = '/dashboard/analytics'; | |||
} | |||
let mainContent; | |||
let itemContent; | |||
let breadcrumbContent = <Typography />; | |||
let itemTitle = ''; | |||
// collapse item | |||
if (main && main.type === 'collapse') { | |||
mainContent = ( | |||
<Typography component={Link} to={document.location.pathname} variant="h6" sx={{ textDecoration: 'none' }} color="textSecondary"> | |||
{main.title} | |||
</Typography> | |||
); | |||
} | |||
// items | |||
if (item && item.type === 'item') { | |||
itemTitle = item.title; | |||
itemContent = ( | |||
<Typography variant="subtitle1" color="textPrimary"> | |||
{itemTitle} | |||
</Typography> | |||
); | |||
// main | |||
if (item.breadcrumbs !== false) { | |||
breadcrumbContent = ( | |||
<MainCard border={false} sx={{ mb: 3, bgcolor: 'transparent' }} {...others} content={false}> | |||
<Grid container direction="column" justifyContent="flex-start" alignItems="flex-start" spacing={1}> | |||
<Grid item> | |||
<MuiBreadcrumbs aria-label="breadcrumb"> | |||
<Typography component={Link} to="/" color="textSecondary" variant="h6" sx={{ textDecoration: 'none' }}> | |||
Home | |||
</Typography> | |||
{mainContent} | |||
{itemContent} | |||
</MuiBreadcrumbs> | |||
</Grid> | |||
{title && ( | |||
<Grid item sx={{ mt: 2 }}> | |||
<Typography variant="h5">{item.title}</Typography> | |||
</Grid> | |||
)} | |||
</Grid> | |||
</MainCard> | |||
); | |||
} | |||
} | |||
return breadcrumbContent; | |||
}; | |||
Breadcrumbs.propTypes = { | |||
navigation: PropTypes.object, | |||
title: PropTypes.bool | |||
}; | |||
export default Breadcrumbs; |
@@ -0,0 +1,48 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Box } from '@mui/material'; | |||
const Dot = ({ color, size }) => { | |||
const theme = useTheme(); | |||
let main; | |||
switch (color) { | |||
case 'secondary': | |||
main = theme.palette.secondary.main; | |||
break; | |||
case 'error': | |||
main = theme.palette.error.main; | |||
break; | |||
case 'warning': | |||
main = theme.palette.warning.main; | |||
break; | |||
case 'info': | |||
main = theme.palette.info.main; | |||
break; | |||
case 'success': | |||
main = theme.palette.success.main; | |||
break; | |||
case 'primary': | |||
default: | |||
main = theme.palette.primary.main; | |||
} | |||
return ( | |||
<Box | |||
sx={{ | |||
width: size || 8, | |||
height: size || 8, | |||
borderRadius: '50%', | |||
bgcolor: main | |||
}} | |||
/> | |||
); | |||
}; | |||
Dot.propTypes = { | |||
color: PropTypes.string, | |||
size: PropTypes.number | |||
}; | |||
export default Dot; |
@@ -0,0 +1,62 @@ | |||
import PropTypes from 'prop-types'; | |||
import { forwardRef } from 'react'; | |||
// material-ui | |||
import { Fade, Box, Grow } from '@mui/material'; | |||
// ==============================|| TRANSITIONS ||============================== // | |||
const Transitions = forwardRef(({ children, position, type, ...others }, ref) => { | |||
let positionSX = { | |||
transformOrigin: '0 0 0' | |||
}; | |||
switch (position) { | |||
case 'top-right': | |||
case 'top': | |||
case 'bottom-left': | |||
case 'bottom-right': | |||
case 'bottom': | |||
case 'top-left': | |||
default: | |||
positionSX = { | |||
transformOrigin: '0 0 0' | |||
}; | |||
break; | |||
} | |||
return ( | |||
<Box ref={ref}> | |||
{type === 'grow' && ( | |||
<Grow {...others}> | |||
<Box sx={positionSX}>{children}</Box> | |||
</Grow> | |||
)} | |||
{type === 'fade' && ( | |||
<Fade | |||
{...others} | |||
timeout={{ | |||
appear: 0, | |||
enter: 300, | |||
exit: 150 | |||
}} | |||
> | |||
<Box sx={positionSX}>{children}</Box> | |||
</Fade> | |||
)} | |||
</Box> | |||
); | |||
}); | |||
Transitions.propTypes = { | |||
children: PropTypes.node, | |||
type: PropTypes.oneOf(['grow', 'fade', 'collapse', 'slide', 'zoom']), | |||
position: PropTypes.oneOf(['top-left', 'top-right', 'top', 'bottom-left', 'bottom-right', 'bottom']) | |||
}; | |||
Transitions.defaultProps = { | |||
type: 'grow', | |||
position: 'top-left' | |||
}; | |||
export default Transitions; |
@@ -0,0 +1,15 @@ | |||
import { Suspense } from 'react'; | |||
// project import | |||
import Loader from './Loader'; | |||
// ==============================|| LOADABLE - LAZY LOADING ||============================== // | |||
const Loadable = (Component) => (props) => | |||
( | |||
<Suspense fallback={<Loader />}> | |||
<Component {...props} /> | |||
</Suspense> | |||
); | |||
export default Loadable; |
@@ -0,0 +1,25 @@ | |||
// material-ui | |||
import { styled } from '@mui/material/styles'; | |||
import LinearProgress from '@mui/material/LinearProgress'; | |||
// loader style | |||
const LoaderWrapper = styled('div')(({ theme }) => ({ | |||
position: 'fixed', | |||
top: 0, | |||
left: 0, | |||
zIndex: 2001, | |||
width: '100%', | |||
'& > * + *': { | |||
marginTop: theme.spacing(2) | |||
} | |||
})); | |||
// ==============================|| Loader ||============================== // | |||
const Loader = () => ( | |||
<LoaderWrapper> | |||
<LinearProgress color="primary" /> | |||
</LoaderWrapper> | |||
); | |||
export default Loader; |
@@ -0,0 +1,51 @@ | |||
// material-ui | |||
// import { useTheme } from '@mui/material/styles'; | |||
/** | |||
* if you want to use image instead of <svg> uncomment following. | |||
* | |||
* import logoDark from 'assets/images/logo-dark.svg'; | |||
* import logo from 'assets/images/logo.svg'; | |||
* | |||
*/ | |||
// ==============================|| LOGO SVG ||============================== // | |||
const Logo = () => { | |||
// const theme = useTheme(); | |||
return ( | |||
/** | |||
* if you want to use image instead of svg uncomment following, and comment out <svg> element. | |||
* | |||
* <img src={logo} alt="Mantis" width="100" /> | |||
* | |||
*/ | |||
<> | |||
<svg width="130" height="85" viewBox="20 0 10 35" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<defs> | |||
<clipPath id="clipPath22"> | |||
<path d="m0 256h213v-256h-213z"/> | |||
</clipPath> | |||
</defs> | |||
<g transform="translate(-31.999 -25.836)"> | |||
<g transform="matrix(.35278 0 0 -.35278 46.979 25.836)"> | |||
<path d="m0 0h16.273v-37.417l17.85-0.035v-12.864h-34.123z" fill="#00a650"/> | |||
</g> | |||
<g transform="matrix(.35278 0 0 -.35278 16.798 99.848)"> | |||
<g clipPath="url(#clipPath22)"> | |||
<g transform="translate(82.049 183.46)"> | |||
<path d="m0 0c-17.536-0.633-27.516-14.176-27.516-28.318 0-14.143 9.241-28.019 27.563-28.071 3.447-9e-3 8.679 1.279 21.255 17.378l-17.808 0.07v10.335l34.132 0.061v-35.705l-10.467 3.577 0.045 12.858c-12.924-19.307-24.442-19.361-26.895-19.378-23.677-0.157-39.268 17.825-39.268 38.884s14.944 37.674 38.959 38.589z" fill="#1c63b7"/> | |||
</g> | |||
<g transform="translate(119.64 176.52)"> | |||
<path d="m0 0-10.391 0.041v11.372c3.151 1.943 6.524 3.573 10.424 4.515 3.633 0.879 7.526 1.321 11.567 1.321 24.407 0 39.006-17.548 39.006-38.608s-14.633-39.843-39.006-38.608c-2.765 0.14-3.93 0.531-7.946 1.652l0.055 9.924c3.341-0.628 3.344-0.529 7.891-1.047 17.298-1.971 27.561 13.927 27.561 28.069 0 14.143-10.11 28.09-27.561 28.09-4.162 0-8.175-0.816-11.567-2.433l-0.033-4.288" fill="#1c63b7"/> | |||
</g> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
</> | |||
); | |||
}; | |||
export default Logo; |
@@ -0,0 +1,36 @@ | |||
import PropTypes from 'prop-types'; | |||
import { Link } from 'react-router-dom'; | |||
// material-ui | |||
import { ButtonBase } from '@mui/material'; | |||
import { useDispatch, useSelector } from 'react-redux'; | |||
// project import | |||
import Logo from './Logo'; | |||
import config from 'config'; | |||
import { activeItem } from 'store/reducers/menu'; | |||
// ==============================|| MAIN LOGO ||============================== // | |||
const LogoSection = ({ sx, to }) => { | |||
const { defaultId } = useSelector((state) => state.menu); | |||
const dispatch = useDispatch(); | |||
return ( | |||
<ButtonBase | |||
disableRipple | |||
component={Link} | |||
onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
to={!to ? config.defaultPath : to} | |||
sx={sx} | |||
> | |||
<Logo /> | |||
</ButtonBase> | |||
); | |||
}; | |||
LogoSection.propTypes = { | |||
sx: PropTypes.object, | |||
to: PropTypes.string | |||
}; | |||
export default LogoSection; |
@@ -0,0 +1,103 @@ | |||
import PropTypes from 'prop-types'; | |||
import { forwardRef } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material'; | |||
// project import | |||
import Highlighter from './third-party/Highlighter'; | |||
// header style | |||
const headerSX = { | |||
p: 2.5, | |||
'& .MuiCardHeader-action': { m: '0px auto', alignSelf: 'center' } | |||
}; | |||
// ==============================|| CUSTOM - MAIN CARD ||============================== // | |||
const MainCard = forwardRef( | |||
( | |||
{ | |||
border = true, | |||
boxShadow, | |||
children, | |||
content = true, | |||
contentSX = {}, | |||
darkTitle, | |||
elevation, | |||
secondary, | |||
shadow, | |||
sx = {}, | |||
title, | |||
codeHighlight, | |||
...others | |||
}, | |||
ref | |||
) => { | |||
const theme = useTheme(); | |||
boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow; | |||
return ( | |||
<Card | |||
elevation={elevation || 0} | |||
ref={ref} | |||
{...others} | |||
sx={{ | |||
border: border ? '1px solid' : 'none', | |||
borderRadius: 2, | |||
borderColor: theme.palette.mode === 'dark' ? theme.palette.divider : theme.palette.grey.A800, | |||
boxShadow: boxShadow && (!border || theme.palette.mode === 'dark') ? shadow || theme.customShadows.z1 : 'inherit', | |||
':hover': { | |||
boxShadow: boxShadow ? shadow || theme.customShadows.z1 : 'inherit' | |||
}, | |||
'& pre': { | |||
m: 0, | |||
p: '16px !important', | |||
fontFamily: theme.typography.fontFamily, | |||
fontSize: '0.75rem' | |||
}, | |||
...sx | |||
}} | |||
> | |||
{/* card header and action */} | |||
{!darkTitle && title && ( | |||
<CardHeader sx={headerSX} titleTypographyProps={{ variant: 'subtitle1' }} title={title} action={secondary} /> | |||
)} | |||
{darkTitle && title && <CardHeader sx={headerSX} title={<Typography variant="h3">{title}</Typography>} action={secondary} />} | |||
{/* card content */} | |||
{content && <CardContent sx={contentSX}>{children}</CardContent>} | |||
{!content && children} | |||
{/* card footer - clipboard & highlighter */} | |||
{codeHighlight && ( | |||
<> | |||
<Divider sx={{ borderStyle: 'dashed' }} /> | |||
<Highlighter codeHighlight={codeHighlight} main> | |||
{children} | |||
</Highlighter> | |||
</> | |||
)} | |||
</Card> | |||
); | |||
} | |||
); | |||
MainCard.propTypes = { | |||
border: PropTypes.bool, | |||
boxShadow: PropTypes.bool, | |||
contentSX: PropTypes.object, | |||
darkTitle: PropTypes.bool, | |||
divider: PropTypes.bool, | |||
elevation: PropTypes.number, | |||
secondary: PropTypes.node, | |||
shadow: PropTypes.string, | |||
sx: PropTypes.object, | |||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), | |||
codeHighlight: PropTypes.bool, | |||
content: PropTypes.bool, | |||
children: PropTypes.node | |||
}; | |||
export default MainCard; |
@@ -0,0 +1,51 @@ | |||
// material-ui | |||
// import { useTheme } from '@mui/material/styles'; | |||
/** | |||
* if you want to use image instead of <svg> uncomment following. | |||
* | |||
* import logoDark from 'assets/images/logo-dark.svg'; | |||
* import logo from 'assets/images/logo.svg'; | |||
* | |||
*/ | |||
// ==============================|| LOGO SVG ||============================== // | |||
const MobileLogo = () => { | |||
// const theme = useTheme(); | |||
return ( | |||
/** | |||
* if you want to use image instead of svg uncomment following, and comment out <svg> element. | |||
* | |||
* <img src={logo} alt="Mantis" width="100" /> | |||
* | |||
*/ | |||
<> | |||
<svg width="60" height="42" viewBox="20 0 10 35" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<defs> | |||
<clipPath id="clipPath22"> | |||
<path d="m0 256h213v-256h-213z"/> | |||
</clipPath> | |||
</defs> | |||
<g transform="translate(-31.999 -25.836)"> | |||
<g transform="matrix(.35278 0 0 -.35278 46.979 25.836)"> | |||
<path d="m0 0h16.273v-37.417l17.85-0.035v-12.864h-34.123z" fill="#00a650"/> | |||
</g> | |||
<g transform="matrix(.35278 0 0 -.35278 16.798 99.848)"> | |||
<g clipPath="url(#clipPath22)"> | |||
<g transform="translate(82.049 183.46)"> | |||
<path d="m0 0c-17.536-0.633-27.516-14.176-27.516-28.318 0-14.143 9.241-28.019 27.563-28.071 3.447-9e-3 8.679 1.279 21.255 17.378l-17.808 0.07v10.335l34.132 0.061v-35.705l-10.467 3.577 0.045 12.858c-12.924-19.307-24.442-19.361-26.895-19.378-23.677-0.157-39.268 17.825-39.268 38.884s14.944 37.674 38.959 38.589z" fill="#1c63b7"/> | |||
</g> | |||
<g transform="translate(119.64 176.52)"> | |||
<path d="m0 0-10.391 0.041v11.372c3.151 1.943 6.524 3.573 10.424 4.515 3.633 0.879 7.526 1.321 11.567 1.321 24.407 0 39.006-17.548 39.006-38.608s-14.633-39.843-39.006-38.608c-2.765 0.14-3.93 0.531-7.946 1.652l0.055 9.924c3.341-0.628 3.344-0.529 7.891-1.047 17.298-1.971 27.561 13.927 27.561 28.069 0 14.143-10.11 28.09-27.561 28.09-4.162 0-8.175-0.816-11.567-2.433l-0.033-4.288" fill="#1c63b7"/> | |||
</g> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
</> | |||
); | |||
}; | |||
export default MobileLogo; |
@@ -0,0 +1,36 @@ | |||
import PropTypes from 'prop-types'; | |||
import { Link } from 'react-router-dom'; | |||
// material-ui | |||
import { ButtonBase } from '@mui/material'; | |||
import { useDispatch, useSelector } from 'react-redux'; | |||
// project import | |||
import Logo from './MobileLogo'; | |||
import config from 'config'; | |||
import { activeItem } from 'store/reducers/menu'; | |||
// ==============================|| MAIN LOGO ||============================== // | |||
const LogoSection = ({ sx, to }) => { | |||
const { defaultId } = useSelector((state) => state.menu); | |||
const dispatch = useDispatch(); | |||
return ( | |||
<ButtonBase | |||
disableRipple | |||
component={Link} | |||
onClick={() => dispatch(activeItem({ openItem: [defaultId] }))} | |||
to={!to ? config.defaultPath : to} | |||
sx={sx} | |||
> | |||
<Logo /> | |||
</ButtonBase> | |||
); | |||
}; | |||
LogoSection.propTypes = { | |||
sx: PropTypes.object, | |||
to: PropTypes.string | |||
}; | |||
export default LogoSection; |
@@ -0,0 +1,26 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useEffect } from 'react'; | |||
import { useLocation } from 'react-router-dom'; | |||
// ==============================|| NAVIGATION - SCROLL TO TOP ||============================== // | |||
const ScrollTop = ({ children }) => { | |||
const location = useLocation(); | |||
const { pathname } = location; | |||
useEffect(() => { | |||
window.scrollTo({ | |||
top: 0, | |||
left: 0, | |||
behavior: 'smooth' | |||
}); | |||
}, [pathname]); | |||
return children || null; | |||
}; | |||
ScrollTop.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default ScrollTop; |
@@ -0,0 +1,61 @@ | |||
// material-ui | |||
import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material'; | |||
import bhkLogo from 'assets/images/BHK_logo_rgb_zh-hk.png'; | |||
// ==============================|| FOOTER - AUTHENTICATION ||============================== // | |||
const AuthFooter = () => { | |||
const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); | |||
return ( | |||
<Container maxWidth= "xl"> | |||
<Stack | |||
direction={matchDownSM ? 'column' : 'row'} | |||
justifyContent={matchDownSM ? 'center' : 'flex-start'} | |||
spacing={2} | |||
textAlign={matchDownSM ? 'center' : 'inherit'} | |||
alignItems="center" | |||
> | |||
<Typography variant="subtitle2" color="secondary" component="span"> | |||
2024 © 政府物流服務署 | |||
</Typography> | |||
<Typography | |||
variant="subtitle2" | |||
color="secondary" | |||
component={Link} | |||
// href="https://material-ui.com/store/contributors/codedthemes/" | |||
target="_blank" | |||
underline="hover" | |||
> | |||
重要告示 | |||
</Typography> | |||
<Typography | |||
variant="subtitle2" | |||
color="secondary" | |||
component={Link} | |||
// href="https://codedthemes.com" | |||
href="/testMailPage" | |||
target="_blank" | |||
underline="hover" | |||
> | |||
私隱政策 | |||
</Typography> | |||
</Stack> | |||
<Stack direction={matchDownSM ? 'column' : 'row'} spacing={matchDownSM ? 1 : 3} textAlign={matchDownSM ? 'center' : 'inherit'} justifyContent={matchDownSM?"center":"flex-end"}> | |||
<a href="https://www.w3.org/WAI/WCAG2AAA-Conformance" | |||
title="Explanation of WCAG 2 Level AAA conformance"> | |||
<img height="32" width="88" | |||
src="https://www.w3.org/WAI/wcag2AAA" | |||
alt="Level AAA conformance, | |||
W3C WAI Web Content Accessibility Guidelines 2.0"/> | |||
</a> | |||
<a href="https://www.brandhk.gov.hk/zh-hk" | |||
title=""> | |||
<img src={bhkLogo} alt="logo" height="32" width="88" /> | |||
</a> | |||
</Stack> | |||
</Container> | |||
); | |||
}; | |||
export default AuthFooter; |
@@ -0,0 +1,70 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box, Chip, Grid, Stack, Typography } from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
// assets | |||
import { RiseOutlined, FallOutlined } from '@ant-design/icons'; | |||
// ==============================|| STATISTICS - ECOMMERCE CARD ||============================== // | |||
const AnalyticEcommerce = ({ color, title, count, percentage, isLoss, extra }) => ( | |||
<MainCard contentSX={{ p: 2.25 }}> | |||
<Stack spacing={0.5}> | |||
<Typography variant="h6" color="textSecondary"> | |||
{title} | |||
</Typography> | |||
<Grid container alignItems="center"> | |||
<Grid item> | |||
<Typography variant="h4" color="inherit"> | |||
{count} | |||
</Typography> | |||
</Grid> | |||
{percentage && ( | |||
<Grid item> | |||
<Chip | |||
variant="combined" | |||
color={color} | |||
icon={ | |||
<> | |||
{!isLoss && <RiseOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />} | |||
{isLoss && <FallOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />} | |||
</> | |||
} | |||
label={`${percentage}%`} | |||
sx={{ ml: 1.25, pl: 1 }} | |||
size="small" | |||
/> | |||
</Grid> | |||
)} | |||
</Grid> | |||
</Stack> | |||
<Box sx={{ pt: 2.25 }}> | |||
<Typography variant="caption" color="textSecondary"> | |||
You made an extra{' '} | |||
<Typography component="span" variant="caption" sx={{ color: `${color || 'primary'}.main` }}> | |||
{extra} | |||
</Typography>{' '} | |||
this year | |||
</Typography> | |||
</Box> | |||
</MainCard> | |||
); | |||
AnalyticEcommerce.propTypes = { | |||
color: PropTypes.string, | |||
title: PropTypes.string, | |||
count: PropTypes.string, | |||
percentage: PropTypes.number, | |||
isLoss: PropTypes.bool, | |||
extra: PropTypes.oneOfType([PropTypes.node, PropTypes.string]) | |||
}; | |||
AnalyticEcommerce.defaultProps = { | |||
color: 'primary' | |||
}; | |||
export default AnalyticEcommerce; |
@@ -0,0 +1,65 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useState } from 'react'; | |||
// material-ui | |||
import { Box, CardActions, Collapse, Divider, IconButton, Tooltip } from '@mui/material'; | |||
// third-party | |||
import { CopyToClipboard } from 'react-copy-to-clipboard'; | |||
import reactElementToJSXString from 'react-element-to-jsx-string'; | |||
// project import | |||
import SyntaxHighlight from 'utils/SyntaxHighlight'; | |||
// assets | |||
import { CodeOutlined, CopyOutlined } from '@ant-design/icons'; | |||
// ==============================|| CLIPBOARD & HIGHLIGHTER ||============================== // | |||
const Highlighter = ({ children }) => { | |||
const [highlight, setHighlight] = useState(false); | |||
return ( | |||
<Box sx={{ position: 'relative' }}> | |||
<CardActions sx={{ justifyContent: 'flex-end', p: 1, mb: highlight ? 1 : 0 }}> | |||
<Box sx={{ display: 'flex', position: 'inherit', right: 0, top: 6 }}> | |||
<CopyToClipboard text={reactElementToJSXString(children, { showFunctions: true, maxInlineAttributesLineLength: 100 })}> | |||
<Tooltip title="Copy the source" placement="top-end"> | |||
<IconButton color="secondary" size="small" sx={{ fontSize: '0.875rem' }}> | |||
<CopyOutlined /> | |||
</IconButton> | |||
</Tooltip> | |||
</CopyToClipboard> | |||
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} /> | |||
<Tooltip title="Show the source" placement="top-end"> | |||
<IconButton | |||
sx={{ fontSize: '0.875rem' }} | |||
size="small" | |||
color={highlight ? 'primary' : 'secondary'} | |||
onClick={() => setHighlight(!highlight)} | |||
> | |||
<CodeOutlined /> | |||
</IconButton> | |||
</Tooltip> | |||
</Box> | |||
</CardActions> | |||
<Collapse in={highlight}> | |||
{highlight && ( | |||
<SyntaxHighlight> | |||
{reactElementToJSXString(children, { | |||
showFunctions: true, | |||
showDefaultProps: false, | |||
maxInlineAttributesLineLength: 100 | |||
})} | |||
</SyntaxHighlight> | |||
)} | |||
</Collapse> | |||
</Box> | |||
); | |||
}; | |||
Highlighter.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default Highlighter; |
@@ -0,0 +1,62 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { alpha, styled } from '@mui/material/styles'; | |||
import { Box } from '@mui/material'; | |||
// third-party | |||
import SimpleBar from 'simplebar-react'; | |||
import { BrowserView, MobileView } from 'react-device-detect'; | |||
// root style | |||
const RootStyle = styled(BrowserView)({ | |||
flexGrow: 1, | |||
height: '100%', | |||
overflow: 'hidden' | |||
}); | |||
// scroll bar wrapper | |||
const SimpleBarStyle = styled(SimpleBar)(({ theme }) => ({ | |||
// maxHeight: '100%', | |||
'& .simplebar-scrollbar': { | |||
'&:before': { | |||
backgroundColor: alpha(theme.palette.grey[500], 0.48) | |||
}, | |||
'&.simplebar-visible:before': { | |||
opacity: 1 | |||
} | |||
}, | |||
'& .simplebar-track.simplebar-vertical': { | |||
width: 10 | |||
}, | |||
'& .simplebar-track.simplebar-horizontal .simplebar-scrollbar': { | |||
height: 6 | |||
}, | |||
'& .simplebar-mask': { | |||
zIndex: 'inherit' | |||
} | |||
})); | |||
// ==============================|| SIMPLE SCROLL BAR ||============================== // | |||
export default function SimpleBarScroll({ children, sx, ...other }) { | |||
return ( | |||
<> | |||
<RootStyle> | |||
<SimpleBarStyle timeout={500} clickOnTrack={false} sx={sx} {...other}> | |||
{children} | |||
</SimpleBarStyle> | |||
</RootStyle> | |||
<MobileView> | |||
<Box sx={{ overflowX: 'auto', ...sx }} {...other}> | |||
{children} | |||
</Box> | |||
</MobileView> | |||
</> | |||
); | |||
} | |||
SimpleBarScroll.propTypes = { | |||
children: PropTypes.node, | |||
sx: PropTypes.object | |||
}; |
@@ -0,0 +1,19 @@ | |||
// ==============================|| THEME CONFIG ||============================== // | |||
const config = { | |||
defaultPath: '/dashboard', | |||
fontFamily: `'Public Sans', sans-serif`, | |||
i18n: 'en', | |||
miniDrawer: false, | |||
container: true, | |||
mode: 'light', | |||
presetColor: 'default', | |||
themeDirection: 'ltr' | |||
}; | |||
export default config; | |||
export const drawerWidth = 260; | |||
export const twitterColor = '#1DA1F2'; | |||
export const facebookColor = '#3b5998'; | |||
export const linkedInColor = '#0e76a8'; |
@@ -0,0 +1,38 @@ | |||
import { StrictMode } from 'react'; | |||
import { createRoot } from 'react-dom/client'; | |||
import { BrowserRouter } from 'react-router-dom'; | |||
// scroll bar | |||
import 'simplebar/src/simplebar.css'; | |||
// third-party | |||
import { Provider as ReduxProvider } from 'react-redux'; | |||
// apex-chart | |||
import 'assets/third-party/apex-chart.css'; | |||
// project import | |||
import App from './App'; | |||
import { store } from 'store'; | |||
import reportWebVitals from './reportWebVitals'; | |||
// ==============================|| MAIN - REACT DOM RENDER ||============================== // | |||
const container = document.getElementById('root'); | |||
const root = createRoot(container); // createRoot(container!) if you use TypeScript | |||
//const NotAuthorized = lazy(() => import('../views/NotAuthorized')) | |||
//const Error = lazy(() => import('../views/Error')) | |||
root.render( | |||
<StrictMode> | |||
<ReduxProvider store={store}> | |||
<BrowserRouter basename="/"> | |||
<App /> | |||
</BrowserRouter> | |||
</ReduxProvider> | |||
</StrictMode> | |||
); | |||
// If you want to start measuring performance in your app, pass a function | |||
// to log results (for example: reportWebVitals(console.log)) | |||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | |||
reportWebVitals(); |
@@ -0,0 +1,32 @@ | |||
// material-ui | |||
import { Button, CardMedia, Link, Stack, Typography } from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
// assets | |||
import avatar from 'assets/images/users/avatar-group.png'; | |||
import AnimateButton from 'components/@extended/AnimateButton'; | |||
// ==============================|| DRAWER CONTENT - NAVIGATION CARD ||============================== // | |||
const NavCard = () => ( | |||
<MainCard sx={{ bgcolor: 'grey.50', m: 3 }}> | |||
<Stack alignItems="center" spacing={2.5}> | |||
<CardMedia component="img" image={avatar} sx={{ width: 112 }} /> | |||
<Stack alignItems="center"> | |||
<Typography variant="h5">Mantis Pro</Typography> | |||
<Typography variant="h6" color="secondary"> | |||
Checkout pro features | |||
</Typography> | |||
</Stack> | |||
<AnimateButton> | |||
<Button component={Link} target="_blank" href="https://mantisdashboard.io" variant="contained" color="success" size="small"> | |||
Pro | |||
</Button> | |||
</AnimateButton> | |||
</Stack> | |||
</MainCard> | |||
); | |||
export default NavCard; |
@@ -0,0 +1,59 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useSelector } from 'react-redux'; | |||
// material-ui | |||
import { Box, List, Typography } from '@mui/material'; | |||
// project import | |||
import NavItem from './NavItem'; | |||
// ==============================|| NAVIGATION - LIST GROUP ||============================== // | |||
const NavGroup = ({ item }) => { | |||
const menu = useSelector((state) => state.menu); | |||
const { drawerOpen } = menu; | |||
const navCollapse = item.children?.map((menuItem) => { | |||
switch (menuItem.type) { | |||
case 'collapse': | |||
return ( | |||
<Typography key={menuItem.id} variant="caption" color="error" sx={{ p: 2.5 }}> | |||
collapse - only available in paid version | |||
</Typography> | |||
); | |||
case 'item': | |||
return <NavItem key={menuItem.id} item={menuItem} level={1} />; | |||
default: | |||
return ( | |||
<Typography key={menuItem.id} variant="h6" color="error" align="center"> | |||
Fix - Group Collapse or Items | |||
</Typography> | |||
); | |||
} | |||
}); | |||
return ( | |||
<List | |||
subheader={ | |||
item.title && | |||
drawerOpen && ( | |||
<Box sx={{ pl: 3, mb: 1.5 }}> | |||
<Typography variant="subtitle2" color="textSecondary"> | |||
{item.title} | |||
</Typography> | |||
{/* only available in paid version */} | |||
</Box> | |||
) | |||
} | |||
sx={{ mb: drawerOpen ? 1.5 : 0, py: 0, zIndex: 0 }} | |||
> | |||
{navCollapse} | |||
</List> | |||
); | |||
}; | |||
NavGroup.propTypes = { | |||
item: PropTypes.object | |||
}; | |||
export default NavGroup; |
@@ -0,0 +1,142 @@ | |||
import PropTypes from 'prop-types'; | |||
import { forwardRef, useEffect } from 'react'; | |||
import { Link, useLocation } from 'react-router-dom'; | |||
import { useDispatch, useSelector } from 'react-redux'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material'; | |||
// project import | |||
import { activeItem } from 'store/reducers/menu'; | |||
// ==============================|| NAVIGATION - LIST ITEM ||============================== // | |||
const NavItem = ({ item, level }) => { | |||
const theme = useTheme(); | |||
const dispatch = useDispatch(); | |||
const { pathname } = useLocation(); | |||
const { drawerOpen, openItem } = useSelector((state) => state.menu); | |||
let itemTarget = '_self'; | |||
if (item.target) { | |||
itemTarget = '_blank'; | |||
} | |||
let listItemProps = { component: forwardRef((props, ref) => <Link ref={ref} {...props} to={item.url} target={itemTarget} />) }; | |||
if (item?.external) { | |||
listItemProps = { component: 'a', href: item.url, target: itemTarget }; | |||
} | |||
const itemHandler = (id) => { | |||
dispatch(activeItem({ openItem: [id] })); | |||
}; | |||
const Icon = item.icon; | |||
const itemIcon = item.icon ? <Icon style={{ fontSize: drawerOpen ? '1rem' : '1.25rem' }} /> : false; | |||
const isSelected = openItem.findIndex((id) => id === item.id) > -1; | |||
// active menu item on page load | |||
useEffect(() => { | |||
if (pathname.includes(item.url)) { | |||
dispatch(activeItem({ openItem: [item.id] })); | |||
} | |||
// eslint-disable-next-line | |||
}, [pathname]); | |||
const textColor = 'text.primary'; | |||
const iconSelectedColor = 'primary.main'; | |||
return ( | |||
<ListItemButton | |||
{...listItemProps} | |||
disabled={item.disabled} | |||
onClick={() => itemHandler(item.id)} | |||
selected={isSelected} | |||
sx={{ | |||
zIndex: 1201, | |||
pl: drawerOpen ? `${level * 28}px` : 1.5, | |||
py: !drawerOpen && level === 1 ? 1.25 : 1, | |||
...(drawerOpen && { | |||
'&:hover': { | |||
bgcolor: 'primary.lighter' | |||
}, | |||
'&.Mui-selected': { | |||
bgcolor: 'primary.lighter', | |||
borderRight: `2px solid ${theme.palette.primary.main}`, | |||
color: iconSelectedColor, | |||
'&:hover': { | |||
color: iconSelectedColor, | |||
bgcolor: 'primary.lighter' | |||
} | |||
} | |||
}), | |||
...(!drawerOpen && { | |||
'&:hover': { | |||
bgcolor: 'transparent' | |||
}, | |||
'&.Mui-selected': { | |||
'&:hover': { | |||
bgcolor: 'transparent' | |||
}, | |||
bgcolor: 'transparent' | |||
} | |||
}) | |||
}} | |||
> | |||
{itemIcon && ( | |||
<ListItemIcon | |||
sx={{ | |||
minWidth: 28, | |||
color: isSelected ? iconSelectedColor : textColor, | |||
...(!drawerOpen && { | |||
borderRadius: 1.5, | |||
width: 36, | |||
height: 36, | |||
alignItems: 'center', | |||
justifyContent: 'center', | |||
'&:hover': { | |||
bgcolor: 'secondary.lighter' | |||
} | |||
}), | |||
...(!drawerOpen && | |||
isSelected && { | |||
bgcolor: 'primary.lighter', | |||
'&:hover': { | |||
bgcolor: 'primary.lighter' | |||
} | |||
}) | |||
}} | |||
> | |||
{itemIcon} | |||
</ListItemIcon> | |||
)} | |||
{(drawerOpen || (!drawerOpen && level !== 1)) && ( | |||
<ListItemText | |||
primary={ | |||
<Typography variant="h6" sx={{ color: isSelected ? iconSelectedColor : textColor }}> | |||
{item.title} | |||
</Typography> | |||
} | |||
/> | |||
)} | |||
{(drawerOpen || (!drawerOpen && level !== 1)) && item.chip && ( | |||
<Chip | |||
color={item.chip.color} | |||
variant={item.chip.variant} | |||
size={item.chip.size} | |||
label={item.chip.label} | |||
avatar={item.chip.avatar && <Avatar>{item.chip.avatar}</Avatar>} | |||
/> | |||
)} | |||
</ListItemButton> | |||
); | |||
}; | |||
NavItem.propTypes = { | |||
item: PropTypes.object, | |||
level: PropTypes.number | |||
}; | |||
export default NavItem; |
@@ -0,0 +1,27 @@ | |||
// material-ui | |||
import { Box, Typography } from '@mui/material'; | |||
// project import | |||
import NavGroup from './NavGroup'; | |||
import menuItem from 'menu-items'; | |||
// ==============================|| DRAWER CONTENT - NAVIGATION ||============================== // | |||
const Navigation = () => { | |||
const navGroups = menuItem.items.map((item) => { | |||
switch (item.type) { | |||
case 'group': | |||
return <NavGroup key={item.id} item={item} />; | |||
default: | |||
return ( | |||
<Typography key={item.id} variant="h6" color="error" align="center"> | |||
Fix - Navigation Group | |||
</Typography> | |||
); | |||
} | |||
}); | |||
return <Box sx={{ pt: 2 }}>{navGroups}</Box>; | |||
}; | |||
export default Navigation; |
@@ -0,0 +1,21 @@ | |||
// project import | |||
//import NavCard from './NavCard'; | |||
import Navigation from './Navigation'; | |||
import SimpleBar from 'components/third-party/SimpleBar'; | |||
// ==============================|| DRAWER CONTENT ||============================== // | |||
const DrawerContent = () => ( | |||
<SimpleBar | |||
sx={{ | |||
'& .simplebar-content': { | |||
display: 'flex', | |||
flexDirection: 'column' | |||
} | |||
}} | |||
> | |||
<Navigation /> | |||
</SimpleBar> | |||
); | |||
export default DrawerContent; |
@@ -0,0 +1,15 @@ | |||
// material-ui | |||
import { styled } from '@mui/material/styles'; | |||
import { Box } from '@mui/material'; | |||
// ==============================|| DRAWER HEADER - STYLED ||============================== // | |||
const DrawerHeaderStyled = styled(Box, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ | |||
...theme.mixins.toolbar, | |||
display: 'flex', | |||
alignItems: 'center', | |||
justifyContent: open ? 'flex-start' : 'center', | |||
paddingLeft: theme.spacing(open ? 3 : 0) | |||
})); | |||
export default DrawerHeaderStyled; |
@@ -0,0 +1,38 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Stack } from '@mui/material'; | |||
// project import | |||
import DrawerHeaderStyled from './DrawerHeaderStyled'; | |||
import Logo from 'components/Logo'; | |||
// ==============================|| DRAWER HEADER ||============================== // | |||
const DrawerHeader = ({ open }) => { | |||
const theme = useTheme(); | |||
return ( | |||
// only available in paid version | |||
<DrawerHeaderStyled theme={theme} open={open}> | |||
<Stack direction="row" spacing={1} alignItems="center"> | |||
<Logo /> | |||
{/* <Chip | |||
label={process.env.REACT_APP_VERSION} | |||
size="small" | |||
sx={{ height: 16, '& .MuiChip-label': { fontSize: '0.625rem', py: 0.25 } }} | |||
component="a" | |||
target="_blank" | |||
clickable | |||
/> */} | |||
</Stack> | |||
</DrawerHeaderStyled> | |||
); | |||
}; | |||
DrawerHeader.propTypes = { | |||
open: PropTypes.bool | |||
}; | |||
export default DrawerHeader; |
@@ -0,0 +1,47 @@ | |||
// material-ui | |||
import { styled } from '@mui/material/styles'; | |||
import Drawer from '@mui/material/Drawer'; | |||
// project import | |||
import { drawerWidth } from 'config'; | |||
const openedMixin = (theme) => ({ | |||
width: drawerWidth, | |||
borderRight: `1px solid ${theme.palette.divider}`, | |||
transition: theme.transitions.create('width', { | |||
easing: theme.transitions.easing.sharp, | |||
duration: theme.transitions.duration.enteringScreen | |||
}), | |||
overflowX: 'hidden', | |||
boxShadow: 'none' | |||
}); | |||
const closedMixin = (theme) => ({ | |||
transition: theme.transitions.create('width', { | |||
easing: theme.transitions.easing.sharp, | |||
duration: theme.transitions.duration.leavingScreen | |||
}), | |||
overflowX: 'hidden', | |||
width: 0, | |||
borderRight: 'none', | |||
boxShadow: theme.customShadows.z1 | |||
}); | |||
// ==============================|| DRAWER - MINI STYLED ||============================== // | |||
const MiniDrawerStyled = styled(Drawer, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ | |||
width: drawerWidth, | |||
flexShrink: 0, | |||
whiteSpace: 'nowrap', | |||
boxSizing: 'border-box', | |||
...(open && { | |||
...openedMixin(theme), | |||
'& .MuiDrawer-paper': openedMixin(theme) | |||
}), | |||
...(!open && { | |||
...closedMixin(theme), | |||
'& .MuiDrawer-paper': closedMixin(theme) | |||
}) | |||
})); | |||
export default MiniDrawerStyled; |
@@ -0,0 +1,66 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useMemo } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Box, Drawer, useMediaQuery } from '@mui/material'; | |||
// project import | |||
import DrawerHeader from './DrawerHeader'; | |||
import DrawerContent from './DrawerContent'; | |||
import MiniDrawerStyled from './MiniDrawerStyled'; | |||
import { drawerWidth } from 'config'; | |||
// ==============================|| MAIN LAYOUT - DRAWER ||============================== // | |||
const MainDrawer = ({ open, handleDrawerToggle, window }) => { | |||
const theme = useTheme(); | |||
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); | |||
// responsive drawer container | |||
const container = window !== undefined ? () => window().document.body : undefined; | |||
// header content | |||
const drawerContent = useMemo(() => <DrawerContent />, []); | |||
const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]); | |||
return ( | |||
<Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label="mailbox folders"> | |||
{!matchDownMD ? ( | |||
<MiniDrawerStyled variant="permanent" open={open}> | |||
{drawerHeader} | |||
{drawerContent} | |||
</MiniDrawerStyled> | |||
) : ( | |||
<Drawer | |||
container={container} | |||
variant="temporary" | |||
open={open} | |||
onClose={handleDrawerToggle} | |||
ModalProps={{ keepMounted: true }} | |||
sx={{ | |||
display: { xs: 'block', lg: 'none' }, | |||
'& .MuiDrawer-paper': { | |||
boxSizing: 'border-box', | |||
width: drawerWidth, | |||
borderRight: `1px solid ${theme.palette.divider}`, | |||
backgroundImage: 'none', | |||
boxShadow: 'inherit' | |||
} | |||
}} | |||
> | |||
{open && drawerHeader} | |||
{open && drawerContent} | |||
</Drawer> | |||
)} | |||
</Box> | |||
); | |||
}; | |||
MainDrawer.propTypes = { | |||
open: PropTypes.bool, | |||
handleDrawerToggle: PropTypes.func, | |||
window: PropTypes.object | |||
}; | |||
export default MainDrawer; |
@@ -0,0 +1,28 @@ | |||
// material-ui | |||
import { styled } from '@mui/material/styles'; | |||
import AppBar from '@mui/material/AppBar'; | |||
// project import | |||
import { drawerWidth } from 'config'; | |||
// ==============================|| HEADER - APP BAR STYLED ||============================== // | |||
const AppBarStyled = styled(AppBar, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ | |||
zIndex: theme.zIndex.drawer + 1, | |||
transition: theme.transitions.create(['width', 'margin'], { | |||
easing: theme.transitions.easing.sharp, | |||
duration: theme.transitions.duration.leavingScreen | |||
}), | |||
...(open && { | |||
marginLeft: drawerWidth, | |||
// width: `calc(100% - ${drawerWidth}px)`, | |||
width: "100%", | |||
// height:"100px", | |||
transition: theme.transitions.create(['width', 'margin'], { | |||
easing: theme.transitions.easing.sharp, | |||
duration: theme.transitions.duration.enteringScreen | |||
}) | |||
}) | |||
})); | |||
export default AppBarStyled; |
@@ -0,0 +1,102 @@ | |||
import { useEffect, useRef, useState } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { AppBar, Box, ClickAwayListener, IconButton, Paper, Popper, Toolbar } from '@mui/material'; | |||
// project import | |||
import Search from './Search'; | |||
import Profile from './Profile'; | |||
import Transitions from 'components/@extended/Transitions'; | |||
// assets | |||
import { MoreOutlined } from '@ant-design/icons'; | |||
// ==============================|| HEADER CONTENT - MOBILE ||============================== // | |||
const MobileSection = () => { | |||
const theme = useTheme(); | |||
const [open, setOpen] = useState(false); | |||
const anchorRef = useRef(null); | |||
const handleToggle = () => { | |||
setOpen((prevOpen) => !prevOpen); | |||
}; | |||
const handleClose = (event) => { | |||
if (anchorRef.current && anchorRef.current.contains(event.target)) { | |||
return; | |||
} | |||
setOpen(false); | |||
}; | |||
const prevOpen = useRef(open); | |||
useEffect(() => { | |||
if (prevOpen.current === true && open === false) { | |||
anchorRef.current.focus(); | |||
} | |||
prevOpen.current = open; | |||
}, [open]); | |||
return ( | |||
<> | |||
<Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
<IconButton | |||
component="span" | |||
disableRipple | |||
sx={{ | |||
bgcolor: open ? 'grey.300' : 'grey.100' | |||
}} | |||
ref={anchorRef} | |||
aria-controls={open ? 'menu-list-grow' : undefined} | |||
aria-haspopup="true" | |||
onClick={handleToggle} | |||
color="inherit" | |||
> | |||
<MoreOutlined /> | |||
</IconButton> | |||
</Box> | |||
<Popper | |||
placement="bottom-end" | |||
open={open} | |||
anchorEl={anchorRef.current} | |||
role={undefined} | |||
transition | |||
disablePortal | |||
style={{ | |||
width: '100%' | |||
}} | |||
popperOptions={{ | |||
modifiers: [ | |||
{ | |||
name: 'offset', | |||
options: { | |||
offset: [0, 9] | |||
} | |||
} | |||
] | |||
}} | |||
> | |||
{({ TransitionProps }) => ( | |||
<Transitions type="fade" in={open} {...TransitionProps}> | |||
<Paper sx={{ boxShadow: theme.customShadows.z1 }}> | |||
<ClickAwayListener onClickAway={handleClose}> | |||
<AppBar color="inherit"> | |||
<Toolbar> | |||
<Search /> | |||
<Profile /> | |||
</Toolbar> | |||
</AppBar> | |||
</ClickAwayListener> | |||
</Paper> | |||
</Transitions> | |||
)} | |||
</Popper> | |||
</> | |||
); | |||
}; | |||
export default MobileSection; |
@@ -0,0 +1,279 @@ | |||
import { useRef, useState } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { | |||
Avatar, | |||
Badge, | |||
Box, | |||
ClickAwayListener, | |||
Divider, | |||
IconButton, | |||
List, | |||
ListItemButton, | |||
ListItemAvatar, | |||
ListItemText, | |||
ListItemSecondaryAction, | |||
Paper, | |||
Popper, | |||
Typography, | |||
useMediaQuery | |||
} from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
import Transitions from 'components/@extended/Transitions'; | |||
// assets | |||
import { BellOutlined, CloseOutlined, GiftOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons'; | |||
// sx styles | |||
const avatarSX = { | |||
width: 36, | |||
height: 36, | |||
fontSize: '1rem' | |||
}; | |||
const actionSX = { | |||
mt: '6px', | |||
ml: 1, | |||
top: 'auto', | |||
right: 'auto', | |||
alignSelf: 'flex-start', | |||
transform: 'none' | |||
}; | |||
// ==============================|| HEADER CONTENT - NOTIFICATION ||============================== // | |||
const Notification = () => { | |||
const theme = useTheme(); | |||
const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | |||
const anchorRef = useRef(null); | |||
const [open, setOpen] = useState(false); | |||
const handleToggle = () => { | |||
setOpen((prevOpen) => !prevOpen); | |||
}; | |||
const handleClose = (event) => { | |||
if (anchorRef.current && anchorRef.current.contains(event.target)) { | |||
return; | |||
} | |||
setOpen(false); | |||
}; | |||
const iconBackColorOpen = 'grey.300'; | |||
const iconBackColor = 'grey.100'; | |||
return ( | |||
<Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
<IconButton | |||
disableRipple | |||
color="secondary" | |||
sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor }} | |||
aria-label="open profile" | |||
ref={anchorRef} | |||
aria-controls={open ? 'profile-grow' : undefined} | |||
aria-haspopup="true" | |||
onClick={handleToggle} | |||
> | |||
<Badge badgeContent={4} color="primary"> | |||
<BellOutlined /> | |||
</Badge> | |||
</IconButton> | |||
<Popper | |||
placement={matchesXs ? 'bottom' : 'bottom-end'} | |||
open={open} | |||
anchorEl={anchorRef.current} | |||
role={undefined} | |||
transition | |||
disablePortal | |||
popperOptions={{ | |||
modifiers: [ | |||
{ | |||
name: 'offset', | |||
options: { | |||
offset: [matchesXs ? -5 : 0, 9] | |||
} | |||
} | |||
] | |||
}} | |||
> | |||
{({ TransitionProps }) => ( | |||
<Transitions type="fade" in={open} {...TransitionProps}> | |||
<Paper | |||
sx={{ | |||
boxShadow: theme.customShadows.z1, | |||
width: '100%', | |||
minWidth: 285, | |||
maxWidth: 420, | |||
[theme.breakpoints.down('md')]: { | |||
maxWidth: 285 | |||
} | |||
}} | |||
> | |||
<ClickAwayListener onClickAway={handleClose}> | |||
<MainCard | |||
title="Notification" | |||
elevation={0} | |||
border={false} | |||
content={false} | |||
secondary={ | |||
<IconButton size="small" onClick={handleToggle}> | |||
<CloseOutlined /> | |||
</IconButton> | |||
} | |||
> | |||
<List | |||
component="nav" | |||
sx={{ | |||
p: 0, | |||
'& .MuiListItemButton-root': { | |||
py: 0.5, | |||
'& .MuiAvatar-root': avatarSX, | |||
'& .MuiListItemSecondaryAction-root': { ...actionSX, position: 'relative' } | |||
} | |||
}} | |||
> | |||
<ListItemButton> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'success.main', | |||
bgcolor: 'success.lighter' | |||
}} | |||
> | |||
<GiftOutlined /> | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="h6"> | |||
It's{' '} | |||
<Typography component="span" variant="subtitle1"> | |||
Cristina danny's | |||
</Typography>{' '} | |||
birthday today. | |||
</Typography> | |||
} | |||
secondary="2 min ago" | |||
/> | |||
<ListItemSecondaryAction> | |||
<Typography variant="caption" noWrap> | |||
3:00 AM | |||
</Typography> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
<Divider /> | |||
<ListItemButton> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'primary.main', | |||
bgcolor: 'primary.lighter' | |||
}} | |||
> | |||
<MessageOutlined /> | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="h6"> | |||
<Typography component="span" variant="subtitle1"> | |||
Aida Burg | |||
</Typography>{' '} | |||
commented your post. | |||
</Typography> | |||
} | |||
secondary="5 August" | |||
/> | |||
<ListItemSecondaryAction> | |||
<Typography variant="caption" noWrap> | |||
6:00 PM | |||
</Typography> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
<Divider /> | |||
<ListItemButton> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'error.main', | |||
bgcolor: 'error.lighter' | |||
}} | |||
> | |||
<SettingOutlined /> | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="h6"> | |||
Your Profile is Complete | |||
<Typography component="span" variant="subtitle1"> | |||
60% | |||
</Typography>{' '} | |||
</Typography> | |||
} | |||
secondary="7 hours ago" | |||
/> | |||
<ListItemSecondaryAction> | |||
<Typography variant="caption" noWrap> | |||
2:45 PM | |||
</Typography> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
<Divider /> | |||
<ListItemButton> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'primary.main', | |||
bgcolor: 'primary.lighter' | |||
}} | |||
> | |||
C | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="h6"> | |||
<Typography component="span" variant="subtitle1"> | |||
Cristina Danny | |||
</Typography>{' '} | |||
invited to join{' '} | |||
<Typography component="span" variant="subtitle1"> | |||
Meeting. | |||
</Typography> | |||
</Typography> | |||
} | |||
secondary="Daily scrum meeting time" | |||
/> | |||
<ListItemSecondaryAction> | |||
<Typography variant="caption" noWrap> | |||
9:10 PM | |||
</Typography> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
<Divider /> | |||
<ListItemButton sx={{ textAlign: 'center', py: `${12}px !important` }}> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="h6" color="primary"> | |||
View All | |||
</Typography> | |||
} | |||
/> | |||
</ListItemButton> | |||
</List> | |||
</MainCard> | |||
</ClickAwayListener> | |||
</Paper> | |||
</Transitions> | |||
)} | |||
</Popper> | |||
</Box> | |||
); | |||
}; | |||
export default Notification; |
@@ -0,0 +1,62 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useState } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; | |||
// assets | |||
import { EditOutlined, ProfileOutlined, LogoutOutlined, UserOutlined, WalletOutlined } from '@ant-design/icons'; | |||
// ==============================|| HEADER PROFILE - PROFILE TAB ||============================== // | |||
const ProfileTab = ({ handleLogout }) => { | |||
const theme = useTheme(); | |||
const [selectedIndex, setSelectedIndex] = useState(0); | |||
const handleListItemClick = (event, index) => { | |||
setSelectedIndex(index); | |||
}; | |||
return ( | |||
<List component="nav" sx={{ p: 0, '& .MuiListItemIcon-root': { minWidth: 32, color: theme.palette.grey[500] } }}> | |||
<ListItemButton selected={selectedIndex === 0} onClick={(event) => handleListItemClick(event, 0)}> | |||
<ListItemIcon> | |||
<EditOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Edit Profile" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 1} onClick={(event) => handleListItemClick(event, 1)}> | |||
<ListItemIcon> | |||
<UserOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="View Profile" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 3} onClick={(event) => handleListItemClick(event, 3)}> | |||
<ListItemIcon> | |||
<ProfileOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Social Profile" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 4} onClick={(event) => handleListItemClick(event, 4)}> | |||
<ListItemIcon> | |||
<WalletOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Billing" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 2} onClick={handleLogout}> | |||
<ListItemIcon> | |||
<LogoutOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Logout" /> | |||
</ListItemButton> | |||
</List> | |||
); | |||
}; | |||
ProfileTab.propTypes = { | |||
handleLogout: PropTypes.func | |||
}; | |||
export default ProfileTab; |
@@ -0,0 +1,56 @@ | |||
import { useState } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; | |||
// assets | |||
import { CommentOutlined, LockOutlined, QuestionCircleOutlined, UserOutlined, UnorderedListOutlined } from '@ant-design/icons'; | |||
// ==============================|| HEADER PROFILE - SETTING TAB ||============================== // | |||
const SettingTab = () => { | |||
const theme = useTheme(); | |||
const [selectedIndex, setSelectedIndex] = useState(0); | |||
const handleListItemClick = (event, index) => { | |||
setSelectedIndex(index); | |||
}; | |||
return ( | |||
<List component="nav" sx={{ p: 0, '& .MuiListItemIcon-root': { minWidth: 32, color: theme.palette.grey[500] } }}> | |||
<ListItemButton selected={selectedIndex === 0} onClick={(event) => handleListItemClick(event, 0)}> | |||
<ListItemIcon> | |||
<QuestionCircleOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Support" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 1} onClick={(event) => handleListItemClick(event, 1)}> | |||
<ListItemIcon> | |||
<UserOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Account Settings" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 2} onClick={(event) => handleListItemClick(event, 2)}> | |||
<ListItemIcon> | |||
<LockOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Privacy Center" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 3} onClick={(event) => handleListItemClick(event, 3)}> | |||
<ListItemIcon> | |||
<CommentOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="Feedback" /> | |||
</ListItemButton> | |||
<ListItemButton selected={selectedIndex === 4} onClick={(event) => handleListItemClick(event, 4)}> | |||
<ListItemIcon> | |||
<UnorderedListOutlined /> | |||
</ListItemIcon> | |||
<ListItemText primary="History" /> | |||
</ListItemButton> | |||
</List> | |||
); | |||
}; | |||
export default SettingTab; |
@@ -0,0 +1,217 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useRef, useState } from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { | |||
// Avatar, | |||
Box, | |||
ButtonBase, | |||
CardContent, | |||
ClickAwayListener, | |||
Grid, | |||
IconButton, | |||
Paper, | |||
Popper, | |||
Stack, | |||
Tab, | |||
Tabs, | |||
Typography | |||
} from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
import Transitions from 'components/@extended/Transitions'; | |||
import ProfileTab from './ProfileTab'; | |||
import SettingTab from './SettingTab'; | |||
// assets | |||
import avatar1 from 'assets/images/users/avatar-1.png'; | |||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; | |||
import { handleLogoutFunction } from 'auth/index'; | |||
import {useNavigate} from "react-router-dom"; | |||
import {useDispatch} from "react-redux"; | |||
import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | |||
// tab panel wrapper | |||
function TabPanel({ children, value, index, ...other }) { | |||
return ( | |||
<div role="tabpanel" hidden={value !== index} id={`profile-tabpanel-${index}`} aria-labelledby={`profile-tab-${index}`} {...other}> | |||
{value === index && children} | |||
</div> | |||
); | |||
} | |||
TabPanel.propTypes = { | |||
children: PropTypes.node, | |||
index: PropTypes.any.isRequired, | |||
value: PropTypes.any.isRequired | |||
}; | |||
function a11yProps(index) { | |||
return { | |||
id: `profile-tab-${index}`, | |||
'aria-controls': `profile-tabpanel-${index}` | |||
}; | |||
} | |||
// ==============================|| HEADER CONTENT - PROFILE ||============================== // | |||
const Profile = () => { | |||
const theme = useTheme(); | |||
const navigate = useNavigate() | |||
const dispatch = useDispatch() | |||
const handleLogout = async () => { | |||
dispatch(handleLogoutFunction()); | |||
//await handleLogoutFunction(); | |||
navigate('/login'); | |||
}; | |||
const userData = JSON.parse(localStorage.getItem("userData")); | |||
const anchorRef = useRef(null); | |||
const [open, setOpen] = useState(false); | |||
const handleToggle = () => { | |||
setOpen((prevOpen) => !prevOpen); | |||
}; | |||
const handleClose = (event) => { | |||
if (anchorRef.current && anchorRef.current.contains(event.target)) { | |||
return; | |||
} | |||
setOpen(false); | |||
}; | |||
const [value, setValue] = useState(0); | |||
const handleChange = (event, newValue) => { | |||
setValue(newValue); | |||
}; | |||
const iconBackColorOpen = 'grey.300'; | |||
return ( | |||
<Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
<ButtonBase | |||
sx={{ | |||
p: 0.25, | |||
bgcolor: open ? iconBackColorOpen : 'transparent', | |||
borderRadius: 1, | |||
'&:hover': { bgcolor: 'secondary.lighter' } | |||
}} | |||
aria-label="open profile" | |||
ref={anchorRef} | |||
aria-controls={open ? 'profile-grow' : undefined} | |||
aria-haspopup="true" | |||
onClick={handleToggle} | |||
> | |||
<Stack direction="row" alignItems="center" sx={{ p: 0.5 }}> | |||
<AccountCircleIcon style={{ color: 'black' }} alt="profile user" src={avatar1} sx={{ width: 32, height: 32 }} /> | |||
<Typography style={{ color: 'black',fontFamily: "微軟正黑體", fontSize: "1.2rem" }} variant="subtitle1">我的帳戶</Typography> | |||
</Stack> | |||
</ButtonBase> | |||
<Popper | |||
placement="bottom-end" | |||
open={open} | |||
anchorEl={anchorRef.current} | |||
role={undefined} | |||
transition | |||
disablePortal | |||
popperOptions={{ | |||
modifiers: [ | |||
{ | |||
name: 'offset', | |||
options: { | |||
offset: [0, 9] | |||
} | |||
} | |||
] | |||
}} | |||
> | |||
{({ TransitionProps }) => ( | |||
<Transitions type="fade" in={open} {...TransitionProps}> | |||
{open && ( | |||
<Paper | |||
sx={{ | |||
boxShadow: theme.customShadows.z1, | |||
width: 290, | |||
minWidth: 240, | |||
maxWidth: 290, | |||
[theme.breakpoints.down('md')]: { | |||
maxWidth: 250 | |||
} | |||
}} | |||
> | |||
<ClickAwayListener onClickAway={handleClose}> | |||
<MainCard elevation={0} border={false} content={false}> | |||
<CardContent sx={{ px: 2.5, pt: 3 }}> | |||
<Grid container justifyContent="space-between" alignItems="center"> | |||
<Grid item> | |||
<Stack direction="row" spacing={1.25} alignItems="center"> | |||
<AccountCircleIcon style={{ color: 'black' }} alt="profile user" src={avatar1} sx={{ width: 32, height: 32 }} /> | |||
<Stack> | |||
<Typography style={{ color: 'black',fontFamily: "微軟正黑體", fontSize: "1.2rem" }} variant="subtitle1">我的帳戶</Typography> | |||
<Typography variant="body2" color="textSecondary"> | |||
{userData == null ? "" : userData.fullName} | |||
</Typography> | |||
{/* <Typography variant="subtitle1">{userData == null ? "" : userData.fullName}</Typography> */} | |||
</Stack> | |||
</Stack> | |||
</Grid> | |||
<Grid item> | |||
<IconButton size="large" color="secondary" onClick={handleLogout}> | |||
<LogoutOutlined /> | |||
</IconButton> | |||
</Grid> | |||
</Grid> | |||
</CardContent> | |||
{open && ( | |||
<> | |||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}> | |||
<Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label="profile tabs"> | |||
<Tab | |||
sx={{ | |||
display: 'flex', | |||
flexDirection: 'row', | |||
justifyContent: 'center', | |||
alignItems: 'center', | |||
textTransform: 'capitalize' | |||
}} | |||
icon={<UserOutlined style={{ marginBottom: 0, marginRight: '10px' }} />} | |||
label="Profile" | |||
{...a11yProps(0)} | |||
/> | |||
<Tab | |||
sx={{ | |||
display: 'flex', | |||
flexDirection: 'row', | |||
justifyContent: 'center', | |||
alignItems: 'center', | |||
textTransform: 'capitalize' | |||
}} | |||
icon={<SettingOutlined style={{ marginBottom: 0, marginRight: '10px' }} />} | |||
label="Setting" | |||
{...a11yProps(1)} | |||
/> | |||
</Tabs> | |||
</Box> | |||
<TabPanel value={value} index={0} dir={theme.direction}> | |||
<ProfileTab handleLogout={handleLogout} /> | |||
</TabPanel> | |||
<TabPanel value={value} index={1} dir={theme.direction}> | |||
<SettingTab /> | |||
</TabPanel> | |||
</> | |||
)} | |||
</MainCard> | |||
</ClickAwayListener> | |||
</Paper> | |||
)} | |||
</Transitions> | |||
)} | |||
</Popper> | |||
</Box> | |||
); | |||
}; | |||
export default Profile; |
@@ -0,0 +1,30 @@ | |||
// material-ui | |||
import { Box, FormControl, InputAdornment, OutlinedInput } from '@mui/material'; | |||
// assets | |||
import { SearchOutlined } from '@ant-design/icons'; | |||
// ==============================|| HEADER CONTENT - SEARCH ||============================== // | |||
const Search = () => ( | |||
<Box sx={{ width: '100%', ml: { xs: 0, md: 1 } }}> | |||
<FormControl sx={{ width: { xs: '100%', md: 224 } }}> | |||
<OutlinedInput | |||
size="small" | |||
id="header-search" | |||
startAdornment={ | |||
<InputAdornment position="start" sx={{ mr: -0.5 }}> | |||
<SearchOutlined /> | |||
</InputAdornment> | |||
} | |||
aria-describedby="header-search-text" | |||
inputProps={{ | |||
'aria-label': 'weight' | |||
}} | |||
placeholder="Ctrl + K" | |||
/> | |||
</FormControl> | |||
</Box> | |||
); | |||
export default Search; |
@@ -0,0 +1,45 @@ | |||
// material-ui | |||
import { Button ,Box } from '@mui/material'; | |||
// import { Box, IconButton, Link, useMediaQuery } from '@mui/material'; | |||
// import { GithubOutlined } from '@ant-design/icons'; | |||
// project import | |||
// import Search from './Search'; | |||
import Profile from './Profile'; | |||
// import Notification from './Notification'; | |||
// import MobileSection from './MobileSection'; | |||
// import { useState } from 'react'; | |||
// ==============================|| HEADER - CONTENT ||============================== // | |||
// const pages = ['Products', 'Pricing', 'Blog']; | |||
const HeaderContent = () => { | |||
return ( | |||
<> | |||
{/* {!matchesXs && <Search />} | |||
{matchesXs && <Box sx={{ width: '100%', ml: 1 }} />} */} | |||
<Box> | |||
<Button size="large" sx={{color:"#000000"}}>主頁</Button> | |||
</Box> | |||
{/* <IconButton | |||
component={Link} | |||
href="https://github.com/codedthemes/mantis-free-react-admin-template" | |||
target="_blank" | |||
disableRipple | |||
color="secondary" | |||
title="Download Free Version" | |||
sx={{ color: 'text.primary', bgcolor: 'grey.100' }} | |||
> | |||
<GithubOutlined /> | |||
</IconButton> */} | |||
{/* <Notification /> */} | |||
<Profile /> | |||
{/* <MobileSection /> */} | |||
</> | |||
); | |||
}; | |||
export default HeaderContent; |
@@ -0,0 +1,151 @@ | |||
import PropTypes from 'prop-types'; | |||
import React | |||
,{useState} | |||
from 'react'; | |||
// material-ui | |||
// import { useTheme } from '@mui/material/styles'; | |||
import { | |||
AppBar, | |||
// Container, | |||
Typography, | |||
Box, | |||
Stack, | |||
// IconButton, | |||
// Menu, | |||
// MenuItem, | |||
// Button, | |||
// Tooltip, | |||
// Avatar, | |||
// Stack, | |||
Toolbar, | |||
Divider, | |||
// List, | |||
// ListItem, | |||
// ListItemButton, | |||
// ListItemText, | |||
IconButton, | |||
Drawer, | |||
// useMediaQuery | |||
} from '@mui/material'; | |||
import MenuIcon from '@mui/icons-material/Menu'; | |||
// project import | |||
// import AppBarStyled from './AppBarStyled'; | |||
// import HeaderContent from './HeaderContent'; | |||
import Logo from 'components/Logo'; | |||
import MobileLogo from 'components/MobileLogo'; | |||
import Profile from './HeaderContent/Profile'; | |||
import "assets/style/navbarStyles.css"; | |||
// assets | |||
// import { MenuFoldOutlined,MenuOutlined } from '@ant-design/icons'; | |||
// import { AppBar } from '../../../../node_modules/@mui/material/index'; | |||
import { Link } from "react-router-dom"; | |||
const drawerWidth = 240; | |||
// const navItems = ['Home', 'About', 'Contact']; | |||
// ==============================|| MAIN LAYOUT - HEADER ||============================== // | |||
function Header(props) { | |||
const { window } = props; | |||
const [mobileOpen, setMobileOpen] = useState(false); | |||
const handleDrawerToggle = () => { | |||
setMobileOpen((prevState) => !prevState); | |||
}; | |||
const drawer = ( | |||
<Box onClick={handleDrawerToggle} sx={{ textAlign: 'center' }}> | |||
<Typography variant="h6" sx={{ my: 2 }}> | |||
PNSPS | |||
</Typography> | |||
<Divider /> | |||
<ul id="sidebar"> | |||
<li> | |||
<Link className="login" to='/login'>登入</Link> | |||
</li> | |||
<li> | |||
<Link className="register" to='/register'>申請</Link> | |||
</li> | |||
</ul> | |||
<Divider /> | |||
<Profile /> | |||
</Box> | |||
); | |||
const container = window !== undefined ? () => window().document.body : undefined; | |||
return ( | |||
<Box> | |||
<AppBar component="nav"> | |||
<Toolbar id ="nav" width="100%"> | |||
<Stack | |||
direction="row" | |||
justifyContent="flex-start" | |||
alignItems="center" | |||
spacing={0} | |||
> | |||
<Box sx={{flexGrow: 1,display: { xs: 'none', sm: 'block' } }}> | |||
<Logo/> | |||
<span id="systemTitle" >公共啟事提交及繳費系統</span> | |||
</Box> | |||
<IconButton | |||
color="inherit" | |||
aria-label="open drawer" | |||
edge="start" | |||
onClick={handleDrawerToggle} | |||
sx={{ mr: 2, display: { sm: 'none' } }} | |||
> | |||
<MenuIcon style={{ color: '#0C489E' }} /> | |||
</IconButton> | |||
<Box sx={{mr: 2, display: { sm: 'none' } }}> | |||
<MobileLogo/> | |||
<span id="mobileTitle" >公共啟事提交及繳費系統</span> | |||
</Box> | |||
</Stack> | |||
<Box sx={{display: { xs: 'none', sm: 'block' } ,width:"75%"}}> | |||
<Stack | |||
direction="row" | |||
justifyContent="space-between" | |||
alignItems="center" | |||
spacing={1} | |||
> | |||
<ul id="navbar" width="100%" > | |||
<li> | |||
<Link className="login" to='/login'>登入</Link> | |||
</li> | |||
<li> | |||
<Link className="register" to='/register'>申請</Link> | |||
</li> | |||
</ul> | |||
<Profile /> | |||
</Stack> | |||
</Box> | |||
</Toolbar> | |||
</AppBar> | |||
<Box component="nav"> | |||
<Drawer | |||
container={container} | |||
variant="temporary" | |||
open={mobileOpen} | |||
onClose={handleDrawerToggle} | |||
ModalProps={{ | |||
keepMounted: true, // Better open performance on mobile. | |||
}} | |||
sx={{ | |||
display: { xs: 'block', sm: 'none' }, | |||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, | |||
}} | |||
> | |||
{drawer} | |||
</Drawer> | |||
</Box> | |||
</Box> | |||
); | |||
} | |||
Header.propTypes = { | |||
/** | |||
* Injected by the documentation to work in an iframe. | |||
* You won't need it on your project. | |||
*/ | |||
window: PropTypes.func, | |||
}; | |||
export default Header; |
@@ -0,0 +1,68 @@ | |||
import { useEffect, useState } from 'react'; | |||
import { Outlet } from 'react-router-dom'; | |||
import { useDispatch, useSelector } from 'react-redux'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Box, Toolbar, useMediaQuery } from '@mui/material'; | |||
// import { Toolbar, useMediaQuery } from '@mui/material'; | |||
// project import | |||
// import Drawer from './Drawer'; | |||
import Header from './Header'; | |||
import Footer from 'components/cards/AuthFooter'; | |||
// import navigation from 'menu-items'; | |||
// import Breadcrumbs from 'components/@extended/Breadcrumbs'; | |||
// types | |||
import { openDrawer } from 'store/reducers/menu'; | |||
import "assets/style/styles.css"; | |||
// ==============================|| MAIN LAYOUT ||============================== // | |||
const MainLayout = () => { | |||
const theme = useTheme(); | |||
const matchDownLG = useMediaQuery(theme.breakpoints.down('lg')); | |||
const dispatch = useDispatch(); | |||
const { drawerOpen } = useSelector((state) => state.menu); | |||
// drawer toggler | |||
const [open, setOpen] = useState(drawerOpen); | |||
// const handleDrawerToggle = () => { | |||
// setOpen(!open); | |||
// dispatch(openDrawer({ drawerOpen: !open })); | |||
// }; | |||
// set media wise responsive drawer | |||
useEffect(() => { | |||
setOpen(!matchDownLG); | |||
dispatch(openDrawer({ drawerOpen: !matchDownLG })); | |||
// eslint-disable-next-line react-hooks/exhaustive-deps | |||
}, [matchDownLG]); | |||
useEffect(() => { | |||
if (open !== drawerOpen) setOpen(drawerOpen); | |||
// eslint-disable-next-line react-hooks/exhaustive-deps | |||
}, [drawerOpen]); | |||
return ( | |||
<Box sx={{ display: 'flex', width: '100%', flexDirection: "column"}}> | |||
<Header/> | |||
{/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */} | |||
<Box component="main" sx={{ width: '100%', flexGrow: 1, p: { xs: 2, sm: 3 } }}> | |||
<Toolbar /> | |||
{/* <Breadcrumbs navigation={navigation} title /> */} | |||
<Outlet /> | |||
</Box> | |||
<Box sx={{borderTop: "3px solid #0C489E"}}> | |||
<Footer/> | |||
</Box> | |||
</Box> | |||
); | |||
}; | |||
export default MainLayout; |
@@ -0,0 +1,11 @@ | |||
import { Outlet } from 'react-router-dom'; | |||
// ==============================|| MINIMAL LAYOUT ||============================== // | |||
const MinimalLayout = () => ( | |||
<> | |||
<Outlet /> | |||
</> | |||
); | |||
export default MinimalLayout; |
@@ -0,0 +1,35 @@ | |||
// assets | |||
import { DashboardOutlined } from '@ant-design/icons'; | |||
// icons | |||
const icons = { | |||
DashboardOutlined | |||
}; | |||
// ==============================|| MENU ITEMS - DASHBOARD ||============================== // | |||
const dashboard = { | |||
id: 'group-dashboard', | |||
title: 'Navigation', | |||
type: 'group', | |||
children: [ | |||
{ | |||
id: 'dashboard', | |||
title: 'Dashboard', | |||
type: 'item', | |||
url: '/dashboard', | |||
icon: icons.DashboardOutlined, | |||
breadcrumbs: false | |||
}, | |||
// { | |||
// id: 'arsdashboard', | |||
// title: 'ARS Dashboard', | |||
// type: 'item', | |||
// url: '/arsDashboard', | |||
// icon: icons.DashboardOutlined, | |||
// breadcrumbs: false | |||
// } | |||
] | |||
}; | |||
export default dashboard; |
@@ -0,0 +1,14 @@ | |||
// project import | |||
import pages from './pages'; | |||
import dashboard from './dashboard'; | |||
import utilities from './utilities'; | |||
import support from './support'; | |||
// ==============================|| MENU ITEMS ||============================== // | |||
const menuItems = { | |||
items: [dashboard, pages, utilities, support] | |||
}; | |||
// pages, utilities, support | |||
export default menuItems; |
@@ -0,0 +1,36 @@ | |||
// assets | |||
import { LoginOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
// icons | |||
const icons = { | |||
LoginOutlined, | |||
ProfileOutlined | |||
}; | |||
// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== // | |||
const pages = { | |||
id: 'authentication', | |||
title: 'Authentication', | |||
type: 'group', | |||
children: [ | |||
{ | |||
id: 'login1', | |||
title: 'Login', | |||
type: 'item', | |||
url: '/login', | |||
icon: icons.LoginOutlined, | |||
// target: true | |||
}, | |||
{ | |||
id: 'register1', | |||
title: 'Register', | |||
type: 'item', | |||
url: '/register', | |||
icon: icons.ProfileOutlined, | |||
// target: true | |||
} | |||
] | |||
}; | |||
export default pages; |
@@ -0,0 +1,36 @@ | |||
// assets | |||
import { ChromeOutlined, QuestionOutlined } from '@ant-design/icons'; | |||
// icons | |||
const icons = { | |||
ChromeOutlined, | |||
QuestionOutlined | |||
}; | |||
// ==============================|| MENU ITEMS - SAMPLE PAGE & DOCUMENTATION ||============================== // | |||
const support = { | |||
id: 'support', | |||
title: 'Support', | |||
type: 'group', | |||
children: [ | |||
{ | |||
id: 'sample-page', | |||
title: 'Sample Page', | |||
type: 'item', | |||
url: '/sample-page', | |||
icon: icons.ChromeOutlined | |||
}, | |||
{ | |||
id: 'documentation', | |||
title: 'Documentation', | |||
type: 'item', | |||
url: 'https://codedthemes.gitbook.io/mantis/', | |||
icon: icons.QuestionOutlined, | |||
external: true, | |||
target: true | |||
} | |||
] | |||
}; | |||
export default support; |
@@ -0,0 +1,60 @@ | |||
// assets | |||
import { | |||
AppstoreAddOutlined, | |||
AntDesignOutlined, | |||
BarcodeOutlined, | |||
BgColorsOutlined, | |||
FontSizeOutlined, | |||
LoadingOutlined | |||
} from '@ant-design/icons'; | |||
// icons | |||
const icons = { | |||
FontSizeOutlined, | |||
BgColorsOutlined, | |||
BarcodeOutlined, | |||
AntDesignOutlined, | |||
LoadingOutlined, | |||
AppstoreAddOutlined | |||
}; | |||
// ==============================|| MENU ITEMS - UTILITIES ||============================== // | |||
const utilities = { | |||
id: 'utilities', | |||
title: 'Utilities', | |||
type: 'group', | |||
children: [ | |||
{ | |||
id: 'util-typography', | |||
title: 'Typography', | |||
type: 'item', | |||
url: '/typography', | |||
icon: icons.FontSizeOutlined | |||
}, | |||
{ | |||
id: 'util-color', | |||
title: 'Color', | |||
type: 'item', | |||
url: '/color', | |||
icon: icons.BgColorsOutlined | |||
}, | |||
{ | |||
id: 'util-shadow', | |||
title: 'Shadow', | |||
type: 'item', | |||
url: '/shadow', | |||
icon: icons.BarcodeOutlined | |||
}, | |||
{ | |||
id: 'ant-icons', | |||
title: 'Ant Icons', | |||
type: 'item', | |||
url: '/icons/ant', | |||
icon: icons.AntDesignOutlined, | |||
breadcrumbs: false | |||
} | |||
] | |||
}; | |||
export default utilities; |
@@ -0,0 +1,359 @@ | |||
import {useEffect, useState} from 'react'; | |||
// material-ui | |||
import { | |||
Avatar, | |||
AvatarGroup, | |||
Box, | |||
Button, | |||
Grid, | |||
List, | |||
ListItemAvatar, | |||
ListItemButton, | |||
ListItemSecondaryAction, | |||
ListItemText, | |||
MenuItem, | |||
Stack, | |||
TextField, | |||
Typography | |||
} from '@mui/material'; | |||
// project import | |||
import OrdersTable from 'pages/dashboard/OrdersTable'; | |||
import IncomeAreaChart from 'pages/dashboard/IncomeAreaChart'; | |||
import MonthlyBarChart from 'pages/dashboard/MonthlyBarChart'; | |||
import ReportAreaChart from 'pages/dashboard/ReportAreaChart'; | |||
import SalesColumnChart from 'pages/dashboard/SalesColumnChart'; | |||
import MainCard from 'components/MainCard'; | |||
import AnalyticEcommerce from 'components/cards/statistics/AnalyticEcommerce'; | |||
// assets | |||
import { GiftOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons'; | |||
import avatar1 from 'assets/images/users/avatar-1.png'; | |||
import avatar2 from 'assets/images/users/avatar-2.png'; | |||
import avatar3 from 'assets/images/users/avatar-3.png'; | |||
import avatar4 from 'assets/images/users/avatar-4.png'; | |||
import axios from "axios"; | |||
// avatar style | |||
const avatarSX = { | |||
width: 36, | |||
height: 36, | |||
fontSize: '1rem' | |||
}; | |||
// action style | |||
const actionSX = { | |||
mt: 0.75, | |||
ml: 1, | |||
top: 'auto', | |||
right: 'auto', | |||
alignSelf: 'flex-start', | |||
transform: 'none' | |||
}; | |||
// sales report status | |||
const status = [ | |||
{ | |||
value: 'today', | |||
label: 'Today' | |||
}, | |||
{ | |||
value: 'month', | |||
label: 'This Month' | |||
}, | |||
{ | |||
value: 'year', | |||
label: 'This Year' | |||
} | |||
]; | |||
// ==============================|| DASHBOARD - DEFAULT ||============================== // | |||
const ARSDashboard = () => { | |||
const [value, setValue] = useState('today'); | |||
const [slot, setSlot] = useState('week'); | |||
useEffect(() =>{ | |||
tryAPI(); | |||
},[]); | |||
const tryAPI = () =>{ | |||
axios.get('http://localhost:8090/api/test') | |||
.then((response) => { | |||
console.log(response) | |||
}) | |||
.catch(error => { | |||
console.error(error); | |||
}); | |||
} | |||
return ( | |||
<Grid container rowSpacing={4.5} columnSpacing={2.75}> | |||
{/* row 1 */} | |||
<Grid item xs={12} sx={{ mb: -2.25 }}> | |||
<Typography variant="h5">Event Overall Status</Typography> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4} lg={3}> | |||
<AnalyticEcommerce title="Total Page Views" count="4,42,236" percentage={59.3} extra="35,000" /> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4} lg={3}> | |||
<AnalyticEcommerce title="Total Users" count="78,250" percentage={70.5} extra="8,900" /> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4} lg={3}> | |||
<AnalyticEcommerce title="Total Order" count="18,800" percentage={27.4} isLoss color="warning" extra="1,943" /> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4} lg={3}> | |||
<AnalyticEcommerce title="Total Sales" count="$35,078" percentage={27.4} isLoss color="warning" extra="$20,395" /> | |||
</Grid> | |||
<Grid item md={8} sx={{ display: { sm: 'none', md: 'block', lg: 'none' } }} /> | |||
{/* row 2 */} | |||
<Grid item xs={12} md={7} lg={8}> | |||
<Grid container alignItems="center" justifyContent="space-between"> | |||
<Grid item> | |||
<Typography variant="h5">Application</Typography> | |||
</Grid> | |||
<Grid item> | |||
<Stack direction="row" alignItems="center" spacing={0}> | |||
<Button | |||
size="small" | |||
onClick={() => setSlot('month')} | |||
color={slot === 'month' ? 'primary' : 'secondary'} | |||
variant={slot === 'month' ? 'outlined' : 'text'} | |||
> | |||
Month | |||
</Button> | |||
<Button | |||
size="small" | |||
onClick={() => setSlot('week')} | |||
color={slot === 'week' ? 'primary' : 'secondary'} | |||
variant={slot === 'week' ? 'outlined' : 'text'} | |||
> | |||
Week | |||
</Button> | |||
</Stack> | |||
</Grid> | |||
</Grid> | |||
<MainCard content={false} sx={{ mt: 1.5 }}> | |||
<Box sx={{ pt: 1, pr: 2 }}> | |||
<IncomeAreaChart slot={slot} /> | |||
</Box> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} md={5} lg={4}> | |||
<Grid container alignItems="center" justifyContent="space-between"> | |||
<Grid item> | |||
<Typography variant="h5">Income Overview</Typography> | |||
</Grid> | |||
<Grid item /> | |||
</Grid> | |||
<MainCard sx={{ mt: 2 }} content={false}> | |||
<Box sx={{ p: 3, pb: 0 }}> | |||
<Stack spacing={2}> | |||
<Typography variant="h6" color="textSecondary"> | |||
This Week Statistics | |||
</Typography> | |||
<Typography variant="h3">$7,650</Typography> | |||
</Stack> | |||
</Box> | |||
<MonthlyBarChart /> | |||
</MainCard> | |||
</Grid> | |||
{/* row 3 */} | |||
<Grid item xs={12} md={7} lg={8}> | |||
<Grid container alignItems="center" justifyContent="space-between"> | |||
<Grid item> | |||
<Typography variant="h5">Recent Orders</Typography> | |||
</Grid> | |||
<Grid item /> | |||
</Grid> | |||
<MainCard sx={{ mt: 2 }} content={false}> | |||
<OrdersTable /> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} md={5} lg={4}> | |||
<Grid container alignItems="center" justifyContent="space-between"> | |||
<Grid item> | |||
<Typography variant="h5">Analytics Report</Typography> | |||
</Grid> | |||
<Grid item /> | |||
</Grid> | |||
<MainCard sx={{ mt: 2 }} content={false}> | |||
<List sx={{ p: 0, '& .MuiListItemButton-root': { py: 2 } }}> | |||
<ListItemButton divider> | |||
<ListItemText primary="Company Finance Growth" /> | |||
<Typography variant="h5">+45.14%</Typography> | |||
</ListItemButton> | |||
<ListItemButton divider> | |||
<ListItemText primary="Company Expenses Ratio" /> | |||
<Typography variant="h5">0.58%</Typography> | |||
</ListItemButton> | |||
<ListItemButton> | |||
<ListItemText primary="Business Risk Cases" /> | |||
<Typography variant="h5">Low</Typography> | |||
</ListItemButton> | |||
</List> | |||
<ReportAreaChart /> | |||
</MainCard> | |||
</Grid> | |||
{/* row 4 */} | |||
<Grid item xs={12} md={7} lg={8}> | |||
<Grid container alignItems="center" justifyContent="space-between"> | |||
<Grid item> | |||
<Typography variant="h5">Sales Report</Typography> | |||
</Grid> | |||
<Grid item> | |||
<TextField | |||
id="standard-select-currency" | |||
size="small" | |||
select | |||
value={value} | |||
onChange={(e) => setValue(e.target.value)} | |||
sx={{ '& .MuiInputBase-input': { py: 0.5, fontSize: '0.875rem' } }} | |||
> | |||
{status.map((option) => ( | |||
<MenuItem key={option.value} value={option.value}> | |||
{option.label} | |||
</MenuItem> | |||
))} | |||
</TextField> | |||
</Grid> | |||
</Grid> | |||
<MainCard sx={{ mt: 1.75 }}> | |||
<Stack spacing={1.5} sx={{ mb: -12 }}> | |||
<Typography variant="h6" color="secondary"> | |||
Net Profit | |||
</Typography> | |||
<Typography variant="h4">$1560</Typography> | |||
</Stack> | |||
<SalesColumnChart /> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} md={5} lg={4}> | |||
<Grid container alignItems="center" justifyContent="space-between"> | |||
<Grid item> | |||
<Typography variant="h5">Transaction History</Typography> | |||
</Grid> | |||
<Grid item /> | |||
</Grid> | |||
<MainCard sx={{ mt: 2 }} content={false}> | |||
<List | |||
component="nav" | |||
sx={{ | |||
px: 0, | |||
py: 0, | |||
'& .MuiListItemButton-root': { | |||
py: 1.5, | |||
'& .MuiAvatar-root': avatarSX, | |||
'& .MuiListItemSecondaryAction-root': { ...actionSX, position: 'relative' } | |||
} | |||
}} | |||
> | |||
<ListItemButton divider> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'success.main', | |||
bgcolor: 'success.lighter' | |||
}} | |||
> | |||
<GiftOutlined /> | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText primary={<Typography variant="subtitle1">Order #002434</Typography>} secondary="Today, 2:00 AM" /> | |||
<ListItemSecondaryAction> | |||
<Stack alignItems="flex-end"> | |||
<Typography variant="subtitle1" noWrap> | |||
+ $1,430 | |||
</Typography> | |||
<Typography variant="h6" color="secondary" noWrap> | |||
78% | |||
</Typography> | |||
</Stack> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
<ListItemButton divider> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'primary.main', | |||
bgcolor: 'primary.lighter' | |||
}} | |||
> | |||
<MessageOutlined /> | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText primary={<Typography variant="subtitle1">Order #984947</Typography>} secondary="5 August, 1:45 PM" /> | |||
<ListItemSecondaryAction> | |||
<Stack alignItems="flex-end"> | |||
<Typography variant="subtitle1" noWrap> | |||
+ $302 | |||
</Typography> | |||
<Typography variant="h6" color="secondary" noWrap> | |||
8% | |||
</Typography> | |||
</Stack> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
<ListItemButton> | |||
<ListItemAvatar> | |||
<Avatar | |||
sx={{ | |||
color: 'error.main', | |||
bgcolor: 'error.lighter' | |||
}} | |||
> | |||
<SettingOutlined /> | |||
</Avatar> | |||
</ListItemAvatar> | |||
<ListItemText primary={<Typography variant="subtitle1">Order #988784</Typography>} secondary="7 hours ago" /> | |||
<ListItemSecondaryAction> | |||
<Stack alignItems="flex-end"> | |||
<Typography variant="subtitle1" noWrap> | |||
+ $682 | |||
</Typography> | |||
<Typography variant="h6" color="secondary" noWrap> | |||
16% | |||
</Typography> | |||
</Stack> | |||
</ListItemSecondaryAction> | |||
</ListItemButton> | |||
</List> | |||
</MainCard> | |||
<MainCard sx={{ mt: 2 }}> | |||
<Stack spacing={3}> | |||
<Grid container justifyContent="space-between" alignItems="center"> | |||
<Grid item> | |||
<Stack> | |||
<Typography variant="h5" noWrap> | |||
Help & Support Chat | |||
</Typography> | |||
<Typography variant="caption" color="secondary" noWrap> | |||
Typical replay within 5 min | |||
</Typography> | |||
</Stack> | |||
</Grid> | |||
<Grid item> | |||
<AvatarGroup sx={{ '& .MuiAvatar-root': { width: 32, height: 32 } }}> | |||
<Avatar alt="Remy Sharp" src={avatar1} /> | |||
<Avatar alt="Travis Howard" src={avatar2} /> | |||
<Avatar alt="Cindy Baker" src={avatar3} /> | |||
<Avatar alt="Agnes Walker" src={avatar4} /> | |||
</AvatarGroup> | |||
</Grid> | |||
</Grid> | |||
<Button size="small" variant="contained" sx={{ textTransform: 'capitalize' }}> | |||
Need Help? | |||
</Button> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
</Grid> | |||
); | |||
}; | |||
export default ARSDashboard; |
@@ -0,0 +1,34 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box } from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
// ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== // | |||
const AuthCard = ({ children, ...other }) => ( | |||
<MainCard | |||
sx={{ | |||
maxWidth: { xs: 400, lg: 475 }, | |||
margin: { xs: 2.5, md: 3 }, | |||
'& > *': { | |||
flexGrow: 1, | |||
flexBasis: '50%' | |||
} | |||
}} | |||
content={false} | |||
{...other} | |||
border={false} | |||
boxShadow | |||
> | |||
<Box sx={{ p: { xs: 2, sm: 3, md: 4, xl: 5 } }}>{children}</Box> | |||
</MainCard> | |||
); | |||
AuthCard.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default AuthCard; |
@@ -0,0 +1,34 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box } from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
// ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== // | |||
const AuthCardCustom = ({ children, ...other }) => ( | |||
<MainCard | |||
sx={{ | |||
maxWidth: { xs: 400, lg: 1000 }, | |||
margin: { xs: 2.5, md: 3 }, | |||
'& > *': { | |||
flexGrow: 1, | |||
flexBasis: '50%' | |||
} | |||
}} | |||
content={false} | |||
{...other} | |||
border={false} | |||
boxShadow | |||
> | |||
<Box sx={{ p: { xs: 2, sm: 3, md: 4, xl: 5 } }}>{children}</Box> | |||
</MainCard> | |||
); | |||
AuthCardCustom.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default AuthCardCustom; |
@@ -0,0 +1,73 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box, Grid,Typography } from '@mui/material'; | |||
// project import | |||
import AuthCard from './AuthCard'; | |||
// import Logo from 'components/Logo'; | |||
// import AuthFooter from 'components/cards/AuthFooter'; | |||
// assets | |||
// import AuthBackground from 'assets/images/auth/AuthBackground'; | |||
// import backgroundImage from 'assets/images/hero-printing.png'; | |||
import backbroundImg from 'assets/images/bg_ml.jpg' | |||
import 'assets/style/loginStyles.css' | |||
import { Stack } from '../../../node_modules/@mui/material/index'; | |||
const BackgroundHead = { | |||
backgroundImage: `url(${backbroundImg})`, | |||
width: '100%', | |||
height: '100%', | |||
backgroundSize:'cover' | |||
} | |||
// ==============================|| AUTHENTICATION - WRAPPER ||============================== // | |||
const AuthWrapper = ({ children }) => ( | |||
<Box sx={{ minHeight: '100vh' }}> | |||
{/* <AuthBackground /> */} | |||
{/* <img src={banner} alt="banner" width="100%" /> */} | |||
<div style={BackgroundHead}> | |||
<Grid | |||
container | |||
direction="row" | |||
justifyContent="space-between" | |||
alignItems="center" | |||
sx={{ | |||
minHeight: '100vh' | |||
}} | |||
> | |||
<Grid item xs={12}> | |||
<Stack direction="row" | |||
justifyContent="space-between" | |||
alignItems="center" | |||
spacing={2}> | |||
<Grid item xs={12} sx={{ ml: 4, mt: 3 ,display: { xs: 'none', sm: 'block' }}}> | |||
<Typography style={{textAlign: "center",fontFamily: "微軟正黑體",fontSize: "1.6rem"}}>香港特別行政區政府</Typography> | |||
<Typography style={{textAlign: "center",fontFamily: "微軟正黑體",fontSize: "1.6rem",fontWeight:"bold"}}>憲報</Typography> | |||
</Grid> | |||
<Grid | |||
item | |||
xs={12} | |||
container | |||
justifyContent="right" | |||
alignItems="center" | |||
sx={{ minHeight: { xs: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' } }} | |||
> | |||
<Grid item> | |||
<AuthCard>{children}</AuthCard> | |||
</Grid> | |||
</Grid> | |||
</Stack> | |||
</Grid> | |||
</Grid> | |||
</div> | |||
{/* <AuthFooter/> */} | |||
</Box> | |||
); | |||
AuthWrapper.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default AuthWrapper; |
@@ -0,0 +1,55 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box, Grid } from '@mui/material'; | |||
// project import | |||
import AuthCard from './AuthCardCustom'; | |||
// import Logo from 'components/Logo'; | |||
// import AuthFooter from 'components/cards/AuthFooter'; | |||
// assets | |||
import AuthBackground from 'assets/images/auth/AuthBackground'; | |||
// ==============================|| AUTHENTICATION - WRAPPER ||============================== // | |||
const AuthWrapperCustom = ({ children }) => ( | |||
<Box sx={{ minHeight: '100vh' }}> | |||
<AuthBackground /> | |||
<Grid | |||
container | |||
direction="column" | |||
justifyContent="flex-end" | |||
sx={{ | |||
minHeight: '100vh' | |||
}} | |||
> | |||
{/* <Grid item xs={12} sx={{ ml: 3, mt: 3 }}> | |||
<Logo /> | |||
</Grid> */} | |||
<Grid item xs={12}> | |||
<Grid | |||
item | |||
xs={12} | |||
container | |||
justifyContent="center" | |||
alignItems="center" | |||
sx={{ minHeight: { xs: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' }}} | |||
> | |||
<Grid item> | |||
<AuthCard>{children}</AuthCard> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
<Grid item xs={12} sx={{ m: 3, mt: 1 }}> | |||
{/* <AuthFooter /> */} | |||
</Grid> | |||
</Grid> | |||
</Box> | |||
); | |||
AuthWrapperCustom.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default AuthWrapperCustom; |
@@ -0,0 +1,29 @@ | |||
//import { Link } from 'react-router-dom'; | |||
// material-ui | |||
import { Grid, Stack, Typography } from '@mui/material'; | |||
// project import | |||
import AuthLogin from './auth-forms/AuthLoginCustom'; | |||
import AuthWrapper from './AuthWrapper'; | |||
// ================================|| LOGIN ||================================ // | |||
const Login = () => ( | |||
<AuthWrapper> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}> | |||
<Typography variant="h3">登入</Typography> | |||
{/* <Typography component={Link} to="/register" variant="body1" sx={{ textDecoration: 'none' }} color="primary"> | |||
Don't have an account? | |||
</Typography> */} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<AuthLogin /> | |||
</Grid> | |||
</Grid> | |||
</AuthWrapper> | |||
); | |||
export default Login; |
@@ -0,0 +1,198 @@ | |||
// import { Link } from 'react-router-dom'; | |||
import React,{useState} from 'react'; | |||
// material-ui | |||
import { | |||
Stepper, | |||
Step, | |||
StepButton, | |||
// Grid, | |||
Stack, | |||
Typography, | |||
Button, | |||
} from '@mui/material'; | |||
import VisibilityIcon from '@mui/icons-material/Visibility'; | |||
// project import | |||
import CustomFormWizard from './auth-forms/CustomFormWizard'; | |||
import AuthWrapper from './AuthWrapperCustom'; | |||
// ================================|| REGISTER ||================================ // | |||
const stepStyle = { | |||
width:"80%", | |||
boxShadow: 2, | |||
backgroundColor: "rgba(0,0,0,0.1)", | |||
padding: 2, | |||
"& .Mui-active": { | |||
"&.MuiStepIcon-root": { | |||
color: "warning.main", | |||
fontSize: "2rem", | |||
}, | |||
"& .MuiStepConnector-line": { | |||
borderColor: "warning.main" | |||
} | |||
}, | |||
"& .Mui-completed": { | |||
"&.MuiStepIcon-root": { | |||
color: "secondary.main", | |||
fontSize: "2rem", | |||
}, | |||
"& .MuiStepConnector-line": { | |||
borderColor: "secondary.main" | |||
} | |||
} | |||
} | |||
const steps = ['個人資料', '預覽', '完成提交']; | |||
const Register = () => { | |||
const [activeStep, setActiveStep] = useState(0); | |||
const [completed, setCompleted] = useState([false]); | |||
const totalSteps = () => { | |||
return steps.length; | |||
}; | |||
const completedSteps = () => { | |||
return Object.keys(completed).length; | |||
}; | |||
const isLastStep = () => { | |||
return activeStep === totalSteps() - 1; | |||
}; | |||
const allStepsCompleted = () => { | |||
return completedSteps() === totalSteps(); | |||
}; | |||
const handleNext = () => { | |||
console.log("test 1") | |||
const newActiveStep = | |||
isLastStep() && !allStepsCompleted() | |||
? // It's the last step, but not all steps have been completed, | |||
// find the first step that has been completed | |||
steps.findIndex((step, i) => !(i in completed)) | |||
: activeStep + 1; | |||
setActiveStep(newActiveStep); | |||
console.log(newActiveStep) | |||
}; | |||
const handleBack = () => { | |||
setActiveStep((prevActiveStep) => prevActiveStep - 1); | |||
}; | |||
const handleStep = (step) => () => { | |||
setActiveStep(step); | |||
}; | |||
// const handleComplete = () => { | |||
// const newCompleted = completed; | |||
// newCompleted[activeStep] = true; | |||
// setCompleted(newCompleted); | |||
// handleNext(); | |||
// }; | |||
const handleReset = () => { | |||
setActiveStep(0); | |||
setCompleted({}); | |||
}; | |||
return( | |||
// <AuthWrapper> | |||
<Stack sx={{ width: '100%',fontSize: '2rem'}} alignItems="center"> | |||
<Stepper activeStep={activeStep} sx={stepStyle}> | |||
{steps.map((label, index) => ( | |||
<Step key={label} completed={completed[index]}> | |||
{ | |||
index < 2 ? | |||
(<StepButton onClick={handleStep(index)}> | |||
{label} | |||
</StepButton>) : | |||
(<StepButton | |||
sx={activeStep === 2 ? { "& .MuiSvgIcon-root": { color: "warning.main", fontSize: "2rem" } } : allStepsCompleted() ? { "& .MuiSvgIcon-root": { color: "secondary.main", fontSize: "2rem" } } : { color: "rgba(0, 0, 0, 0.38)" }} | |||
icon={<VisibilityIcon />} | |||
onClick={handleStep(index)} | |||
> | |||
{label} | |||
</StepButton>) | |||
} | |||
</Step> | |||
))} | |||
</Stepper> | |||
{allStepsCompleted() ? ( | |||
<React.Fragment> | |||
<Typography sx={{ mt: 2, mb: 1 }}> | |||
All steps completed - you're finished | |||
</Typography> | |||
<Stack direction="row" sx={{ pt: 2 }}> | |||
<Stack sx={{ flex: '1 1 auto' }} /> | |||
<Button onClick={handleReset}>Reset</Button> | |||
</Stack> | |||
</React.Fragment> | |||
) : ( | |||
<React.Fragment> | |||
<AuthWrapper> | |||
<CustomFormWizard step={activeStep} /> | |||
</AuthWrapper> | |||
<Stack direction="row" sx={{ pt: 2 }}> | |||
{ activeStep === totalSteps() - 1 ? ( | |||
<Button | |||
color="inherit" | |||
disabled={true} | |||
onClick={handleBack} | |||
sx={{ mr: 1 }} | |||
> | |||
返回 | |||
</Button> | |||
):( | |||
<Button | |||
color="inherit" | |||
disabled={activeStep === 0} | |||
onClick={handleBack} | |||
sx={{ mr: 1 }} | |||
> | |||
返回 | |||
</Button> | |||
) | |||
} | |||
<Stack sx={{ flex: '1 1 auto' }} /> | |||
{activeStep === totalSteps() - 2 ? | |||
( | |||
<Button onClick={handleNext} sx={{ mr: 1 }}> | |||
完成 | |||
</Button> | |||
) : ( activeStep === totalSteps() - 1 ? | |||
( | |||
<Button color="inherit" | |||
disabled={true} sx={{ mr: 1 }}> | |||
完成 | |||
</Button> | |||
): | |||
( | |||
<Button onClick={handleNext} sx={{ mr: 1 }}> | |||
繼續 | |||
</Button> | |||
) | |||
)} | |||
{/* {activeStep !== steps.length && | |||
(completed[activeStep] ? ( | |||
<Typography variant="caption" sx={{ display: 'inline-block' }}> | |||
Step {activeStep + 1} already completed | |||
</Typography> | |||
) : ( | |||
<Button onClick={handleComplete}> | |||
{completedSteps() === totalSteps() - 1 | |||
? 'Finish' | |||
: 'Complete Step'} | |||
</Button> | |||
))} */} | |||
</Stack> | |||
</React.Fragment> | |||
)} | |||
</Stack > | |||
// </AuthWrapper> | |||
); | |||
}; | |||
export default Register; |
@@ -0,0 +1,76 @@ | |||
// material-ui | |||
import {Link, Button, Card , Box, Grid } from '@mui/material'; | |||
import Typography from '@mui/material/Typography'; | |||
import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png'; | |||
import banner from 'assets/images/banner.jpg'; | |||
import { Stack } from '../../../node_modules/@mui/material/index'; | |||
// ================================|| LOGIN ||================================ // | |||
const RegisterCustom = () => ( | |||
<Stack justifyContent="center" sx={{ minHeight: '100vh' }}> | |||
<img src={banner} alt="banner" width="100%" /> | |||
<center> | |||
<Card | |||
sx={{ | |||
maxWidth: { xs: 1, lg: 1000 }, | |||
margin: { xs: 2.5, md: 3 }, | |||
'& > *': { | |||
flexGrow: 1, | |||
flexBasis: '50%' | |||
} | |||
}} | |||
> | |||
<Box alignItems="center"> | |||
<Typography mt={4} variant="h1">立即成為<strong style={{color: '#FF5733'}}>憲報刊登公告</strong>用戶</Typography> | |||
<Typography variant="body1">只需4-5分鐘</Typography> | |||
<Grid mt={5} mb={5} container > | |||
<Grid item xs={6} sx={{ borderRight: 1 , borderColor: 'grey.500' }}> | |||
<Typography mb={4} variant="h3">個人用戶</Typography> | |||
<Button variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}>以「智方便」繼續</Button> | |||
<Box mt={4} ml={2} mr={2} bgcolor="grey.100" p={1.5} > | |||
<Typography textAlign='justify' variant="body1" display="block" gutterBottom> | |||
你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動憲報刊登公告帳戶。 | |||
<br/>如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。 | |||
</Typography> | |||
<Link href="#">了解更多</Link> | |||
</Box> | |||
<Typography m={5}>或</Typography> | |||
<Button href="/registerFrom" variant="contained">申請個人用戶</Button> | |||
<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{fontWeight: 'bold'}} gutterBottom> | |||
需上載身份證明文件數碼檔案以進行網上申請。 | |||
<br/>如:香港身份證;護照;往來港澳通行證等 | |||
</Typography> | |||
</Grid> | |||
<Grid item xs={6} sx={{ borderLeft: 1 ,borderColor: 'grey.500' }}> | |||
<Typography mb={4} variant="h3">機構/公司用戶</Typography> | |||
<Button href="/registerFrom" variant="contained">申請機構/公司用戶</Button> | |||
<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{fontWeight: 'bold'}} gutterBottom> | |||
需上載以下任何一份證明文件以進行網上申請。 | |||
<br/>如:商業登記證;專業執業證書 | |||
</Typography> | |||
</Grid> | |||
</Grid> | |||
</Box> | |||
</Card > | |||
</center> | |||
</Stack> | |||
); | |||
export default RegisterCustom; |
@@ -0,0 +1,225 @@ | |||
import React, {useEffect, useState} from 'react'; | |||
import {useNavigate} from 'react-router-dom'; | |||
// material-ui | |||
import { | |||
Button, | |||
//Checkbox, | |||
//Divider, | |||
//FormControlLabel, | |||
FormHelperText, | |||
Grid, | |||
//Link, | |||
IconButton, | |||
InputAdornment, | |||
InputLabel, | |||
OutlinedInput, | |||
Stack, | |||
//Typography | |||
} from '@mui/material'; | |||
// third party | |||
import * as Yup from 'yup'; | |||
import { Formik } from 'formik'; | |||
// project import | |||
//import FirebaseSocial from './FirebaseSocial'; | |||
import AnimateButton from 'components/@extended/AnimateButton'; | |||
//import {AbilityContext} from "@src/utility/context/Can" | |||
// assets | |||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | |||
import axios from "axios"; | |||
import {useDispatch} from "react-redux"; | |||
import {handleLogin} from "auth/index"; | |||
// ============================|| FIREBASE - LOGIN ||============================ // | |||
const AuthLogin = () => { | |||
//const ability = useContext(AbilityContext) | |||
const dispatch = useDispatch() | |||
const navigate = useNavigate() | |||
//const [checked, setChecked] = useState(false); | |||
const [showPassword, setShowPassword] = useState(false); | |||
const handleClickShowPassword = () => { | |||
setShowPassword(!showPassword); | |||
}; | |||
let [posts, setPosts] = useState([]); | |||
let [userName, setUserName] = useState(""); | |||
let [userPassword, setUserPassword] = useState(""); | |||
useEffect(() => { | |||
//console.log("POST: " + posts.accessToken); | |||
},[posts]); | |||
const handleMouseDownPassword = (event) => { | |||
event.preventDefault(); | |||
}; | |||
const tryLogin = () => { | |||
axios.post('http://localhost:8090/api/login', | |||
{ | |||
"username": userName, | |||
"password": userPassword | |||
}) | |||
.then((response) => { | |||
//setPosts("12354") | |||
setPosts(response.data); | |||
const userData = { | |||
id: response.data.id, | |||
fullName: response.data.name, | |||
email: response.data.email, | |||
role: response.data.role, | |||
abilities: response.data.abilities, | |||
//avatar: require('src/assets/images/users/avatar-3.png').default, | |||
} | |||
// const abilities = response.data.abilities | |||
// ability.update(abilities) | |||
const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken} | |||
dispatch(handleLogin(data)) | |||
navigate('/dashboard'); | |||
//history.push(getHomeRouteForLoggedInUser("user")) | |||
}) | |||
.catch(error => { | |||
console.error(error); | |||
}); | |||
} | |||
const onUserNameChange = (event) => { | |||
setUserName(event.target.value); | |||
} | |||
const onPasswordChange = (event) => { | |||
setUserPassword(event.target.value); | |||
} | |||
return ( | |||
<> | |||
<Formik | |||
initialValues={{ | |||
email: '', | |||
password: '', | |||
submit: null | |||
}} | |||
validationSchema={Yup.object().shape({ | |||
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'), | |||
password: Yup.string().max(255).required('Password is required') | |||
})} | |||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => { | |||
try { | |||
setStatus({ success: false }); | |||
setSubmitting(false); | |||
} catch (err) { | |||
setStatus({ success: false }); | |||
setErrors({ submit: err.message }); | |||
setSubmitting(false); | |||
} | |||
}} | |||
> | |||
{({ errors, handleBlur, handleSubmit, isSubmitting, touched }) => ( | |||
<form noValidate onSubmit={handleSubmit}> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="email-login">User Name</InputLabel> | |||
<OutlinedInput | |||
id="username" | |||
name="username" | |||
onBlur={handleBlur} | |||
onChange={onUserNameChange} | |||
placeholder="Enter user name" | |||
fullWidth | |||
error={Boolean(touched.email && errors.email)} | |||
/> | |||
{touched.email && errors.email && ( | |||
<FormHelperText error id="standard-weight-helper-text-email-login"> | |||
{errors.email} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="password-login">Password</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.password && errors.password)} | |||
id="-password-login" | |||
type={showPassword ? 'text' : 'password'} | |||
name="password" | |||
onBlur={handleBlur} | |||
onChange={onPasswordChange} | |||
endAdornment={ | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowPassword} | |||
onMouseDown={handleMouseDownPassword} | |||
edge="end" | |||
size="large" | |||
> | |||
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />} | |||
</IconButton> | |||
</InputAdornment> | |||
} | |||
placeholder="Enter password" | |||
/> | |||
{touched.password && errors.password && ( | |||
<FormHelperText error id="standard-weight-helper-text-password-login"> | |||
{errors.password} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12} sx={{ mt: -1 }}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}> | |||
{/*<FormControlLabel*/} | |||
{/* control={*/} | |||
{/* <Checkbox*/} | |||
{/* checked={checked}*/} | |||
{/* onChange={(event) => setChecked(event.target.checked)}*/} | |||
{/* name="checked"*/} | |||
{/* color="primary"*/} | |||
{/* size="small"*/} | |||
{/* />*/} | |||
{/* }*/} | |||
{/* label={<Typography variant="h6">Keep me sign in</Typography>}*/} | |||
{/*/>*/} | |||
{/*<Link variant="h6" component={RouterLink} to="" color="text.primary">*/} | |||
{/* Forgot Password?*/} | |||
{/*</Link>*/} | |||
</Stack> | |||
</Grid> | |||
{errors.submit && ( | |||
<Grid item xs={12}> | |||
<FormHelperText error>{errors.submit}</FormHelperText> | |||
</Grid> | |||
)} | |||
<Grid item xs={12}> | |||
<AnimateButton> | |||
<Button disableElevation onClick={tryLogin} | |||
disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary"> | |||
登入 | |||
</Button> | |||
</AnimateButton> | |||
</Grid> | |||
{/*<Grid item xs={12}>*/} | |||
{/* <Divider>*/} | |||
{/* <Typography variant="caption"> Login with</Typography>*/} | |||
{/* </Divider>*/} | |||
{/*</Grid>*/} | |||
{/*<Grid item xs={12}>*/} | |||
{/* <FirebaseSocial />*/} | |||
{/*</Grid>*/} | |||
</Grid> | |||
</form> | |||
)} | |||
</Formik> | |||
</> | |||
); | |||
}; | |||
export default AuthLogin; |
@@ -0,0 +1,220 @@ | |||
import React, { | |||
// useEffect, | |||
useState} from 'react'; | |||
import { Link as RouterLink } from 'react-router-dom'; | |||
import {useNavigate} from 'react-router-dom'; | |||
// material-ui | |||
import { | |||
Button, | |||
//Checkbox, | |||
//Divider, | |||
//FormControlLabel, | |||
FormHelperText, | |||
Grid, | |||
Link, | |||
IconButton, | |||
InputAdornment, | |||
InputLabel, | |||
OutlinedInput, | |||
Stack, | |||
Typography | |||
} from '@mui/material'; | |||
// third party | |||
import * as Yup from 'yup'; | |||
import { Formik } from 'formik'; | |||
// project import | |||
//import FirebaseSocial from './FirebaseSocial'; | |||
import AnimateButton from 'components/@extended/AnimateButton'; | |||
// assets | |||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | |||
// import axios from "axios"; | |||
import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png'; | |||
import {useDispatch} from "react-redux"; | |||
import {handleLogin} from "auth/index"; | |||
import useJwt from "../../../auth/jwt/useJwt"; | |||
// ============================|| FIREBASE - LOGIN ||============================ // | |||
const AuthLoginCustom = () => { | |||
const dispatch = useDispatch() | |||
const navigate = useNavigate() | |||
const [showPassword, setShowPassword] = useState(false); | |||
const handleClickShowPassword = () => { | |||
setShowPassword(!showPassword); | |||
}; | |||
// let [posts, setPosts] = useState([]); | |||
let [userName, setUserName] = useState(null); | |||
let [userPassword, setUserPassword] = useState(null); | |||
// useEffect(() => { | |||
// // console.log("POST: " + posts.accessToken); | |||
// },[posts]); | |||
const handleMouseDownPassword = (event) => { | |||
event.preventDefault(); | |||
}; | |||
const tryLogin = () => { | |||
useJwt | |||
.login({username: userName, password: userPassword}) | |||
.then((response) => { | |||
const userData = { | |||
id: response.data.id, | |||
fullName: response.data.name, | |||
email: response.data.email, | |||
role: response.data.role, | |||
abilities: response.data.abilities, | |||
//avatar: require('src/assets/images/users/avatar-3.png').default, | |||
} | |||
const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken} | |||
dispatch(handleLogin(data)) | |||
//const abilities = response.data.abilities | |||
//ability.update(abilities) | |||
navigate('/dashboard'); | |||
}) | |||
.catch((error) => { | |||
console.error(error) | |||
}); | |||
} | |||
const onUserNameChange = (event) => { | |||
setUserName(event.target.value); | |||
} | |||
const onPasswordChange = (event) => { | |||
setUserPassword(event.target.value); | |||
} | |||
return ( | |||
<Formik | |||
initialValues={{ | |||
username: '', | |||
password: '', | |||
submit: null | |||
}} | |||
validationSchema={Yup.object().shape({ | |||
username: Yup.string().email('Must be a valid email').max(255).required('Email is required'), | |||
password: Yup.string().max(255).required('請輸入密碼'), | |||
})} | |||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => { | |||
try { | |||
setStatus({ success: false }); | |||
setSubmitting(false); | |||
} catch (err) { | |||
setStatus({ success: false }); | |||
setErrors({ submit: err.message }); | |||
setSubmitting(false); | |||
} | |||
}} | |||
> | |||
{({ errors, handleBlur, handleSubmit, isSubmitting, touched }) => ( | |||
<form noValidate onSubmit={handleSubmit}> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="email-login">用戶帳號或別名</InputLabel> | |||
<OutlinedInput | |||
id="username" | |||
name="username" | |||
onBlur={handleBlur} | |||
onChange={onUserNameChange} | |||
placeholder="" | |||
fullWidth | |||
error={Boolean(touched.email && errors.email)} | |||
/> | |||
{touched.email && errors.email && ( | |||
<FormHelperText error id="standard-weight-helper-text-email-login"> | |||
{errors.email} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="password-login">密碼</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.password && errors.password)} | |||
id="password-login" | |||
type={showPassword ? 'text' : 'password'} | |||
name="password" | |||
onBlur={handleBlur} | |||
onChange={onPasswordChange} | |||
endAdornment={ | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowPassword} | |||
onMouseDown={handleMouseDownPassword} | |||
edge="end" | |||
size="large" | |||
> | |||
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />} | |||
</IconButton> | |||
</InputAdornment> | |||
} | |||
placeholder="" | |||
/> | |||
{touched.password && errors.password && ( | |||
<FormHelperText error id="standard-weight-helper-text-password-login"> | |||
{errors.password} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<AnimateButton> | |||
<Button disableElevation onClick={tryLogin} | |||
disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary"> | |||
登錄 | |||
</Button> | |||
</AnimateButton> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}> | |||
<Link variant="h6" component={RouterLink} to="" color="text.primary"> | |||
<Typography align="center"> | |||
忘記密碼? | |||
</Typography> | |||
</Link> | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}> | |||
<Button fullWidth size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}>智方便登入</Button> | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}> | |||
<Link href="#">了解更多智方便</Link> | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}> | |||
<Button fullWidth size="large" variant="outlined" href="/register" >建立/重新啟動帳戶</Button> | |||
</Stack> | |||
</Grid> | |||
{/* <Grid item xs={12}>*/} | |||
{/* <Divider>*/} | |||
{/* <Typography variant="caption"> Login with</Typography>*/} | |||
{/* </Divider>*/} | |||
{/*</Grid>*/} | |||
{/*<Grid item xs={12}>*/} | |||
{/* <FirebaseSocial />*/} | |||
{/*</Grid> */} | |||
</Grid> | |||
</form> | |||
)} | |||
</Formik> | |||
); | |||
}; | |||
export default AuthLoginCustom; |
@@ -0,0 +1,263 @@ | |||
import { useEffect, useState } from 'react'; | |||
import { Link as RouterLink } from 'react-router-dom'; | |||
// material-ui | |||
import { | |||
Box, | |||
Button, | |||
Divider, | |||
FormControl, | |||
FormHelperText, | |||
Grid, | |||
Link, | |||
IconButton, | |||
InputAdornment, | |||
InputLabel, | |||
OutlinedInput, | |||
Stack, | |||
Typography | |||
} from '@mui/material'; | |||
// third party | |||
import * as Yup from 'yup'; | |||
import { Formik } from 'formik'; | |||
// project import | |||
import FirebaseSocial from './FirebaseSocial'; | |||
import AnimateButton from 'components/@extended/AnimateButton'; | |||
import { strengthColorChi, strengthIndicator } from 'utils/password-strength'; | |||
// assets | |||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | |||
// ============================|| FIREBASE - REGISTER ||============================ // | |||
const AuthRegister = () => { | |||
const [level, setLevel] = useState(); | |||
const [showPassword, setShowPassword] = useState(false); | |||
const handleClickShowPassword = () => { | |||
setShowPassword(!showPassword); | |||
}; | |||
const handleMouseDownPassword = (event) => { | |||
event.preventDefault(); | |||
}; | |||
const changePassword = (value) => { | |||
const temp = strengthIndicator(value); | |||
setLevel(strengthColorChi(temp)); | |||
}; | |||
useEffect(() => { | |||
changePassword(''); | |||
}, []); | |||
return ( | |||
<> | |||
<Formik | |||
initialValues={{ | |||
firstname: '', | |||
lastname: '', | |||
email: '', | |||
company: '', | |||
password: '', | |||
submit: null | |||
}} | |||
validationSchema={Yup.object().shape({ | |||
firstname: Yup.string().max(255).required('First Name is required'), | |||
lastname: Yup.string().max(255).required('Last Name is required'), | |||
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'), | |||
password: Yup.string().max(255).required('Password is required') | |||
})} | |||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => { | |||
try { | |||
setStatus({ success: false }); | |||
setSubmitting(false); | |||
} catch (err) { | |||
console.error(err); | |||
setStatus({ success: false }); | |||
setErrors({ submit: err.message }); | |||
setSubmitting(false); | |||
} | |||
}} | |||
> | |||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => ( | |||
<form noValidate onSubmit={handleSubmit}> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12} md={6}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="firstname-signup">First Name*</InputLabel> | |||
<OutlinedInput | |||
id="firstname-login" | |||
type="firstname" | |||
value={values.firstname} | |||
name="firstname" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="John" | |||
fullWidth | |||
error={Boolean(touched.firstname && errors.firstname)} | |||
/> | |||
{touched.firstname && errors.firstname && ( | |||
<FormHelperText error id="helper-text-firstname-signup"> | |||
{errors.firstname} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12} md={6}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="lastname-signup">Last Name*</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.lastname && errors.lastname)} | |||
id="lastname-signup" | |||
type="lastname" | |||
value={values.lastname} | |||
name="lastname" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="Doe" | |||
inputProps={{}} | |||
/> | |||
{touched.lastname && errors.lastname && ( | |||
<FormHelperText error id="helper-text-lastname-signup"> | |||
{errors.lastname} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="company-signup">Company</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.company && errors.company)} | |||
id="company-signup" | |||
value={values.company} | |||
name="company" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="Demo Inc." | |||
inputProps={{}} | |||
/> | |||
{touched.company && errors.company && ( | |||
<FormHelperText error id="helper-text-company-signup"> | |||
{errors.company} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="email-signup">Email Address*</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.email && errors.email)} | |||
id="email-login" | |||
type="email" | |||
value={values.email} | |||
name="email" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="[email protected]" | |||
inputProps={{}} | |||
/> | |||
{touched.email && errors.email && ( | |||
<FormHelperText error id="helper-text-email-signup"> | |||
{errors.email} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="password-signup">Password</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.password && errors.password)} | |||
id="password-signup" | |||
type={showPassword ? 'text' : 'password'} | |||
value={values.password} | |||
name="password" | |||
onBlur={handleBlur} | |||
onChange={(e) => { | |||
handleChange(e); | |||
changePassword(e.target.value); | |||
}} | |||
endAdornment={ | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowPassword} | |||
onMouseDown={handleMouseDownPassword} | |||
edge="end" | |||
size="large" | |||
> | |||
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />} | |||
</IconButton> | |||
</InputAdornment> | |||
} | |||
placeholder="******" | |||
inputProps={{}} | |||
/> | |||
{touched.password && errors.password && ( | |||
<FormHelperText error id="helper-text-password-signup"> | |||
{errors.password} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
<FormControl fullWidth sx={{ mt: 2 }}> | |||
<Grid container spacing={2} alignItems="center"> | |||
<Grid item> | |||
<Box sx={{ bgcolor: level?.color, width: 85, height: 8, borderRadius: '7px' }} /> | |||
</Grid> | |||
<Grid item> | |||
<Typography variant="subtitle1" fontSize="0.75rem"> | |||
{level?.label} | |||
</Typography> | |||
</Grid> | |||
</Grid> | |||
</FormControl> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Typography variant="body2"> | |||
By Signing up, you agree to our | |||
<Link variant="subtitle2" component={RouterLink} to="#"> | |||
Terms of Service | |||
</Link> | |||
and | |||
<Link variant="subtitle2" component={RouterLink} to="#"> | |||
Privacy Policy | |||
</Link> | |||
</Typography> | |||
</Grid> | |||
{errors.submit && ( | |||
<Grid item xs={12}> | |||
<FormHelperText error>{errors.submit}</FormHelperText> | |||
</Grid> | |||
)} | |||
<Grid item xs={12}> | |||
<AnimateButton> | |||
<Button disableElevation disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary"> | |||
Create Account | |||
</Button> | |||
</AnimateButton> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Divider> | |||
<Typography variant="caption">Sign up with</Typography> | |||
</Divider> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<FirebaseSocial /> | |||
</Grid> | |||
</Grid> | |||
</form> | |||
)} | |||
</Formik> | |||
</> | |||
); | |||
}; | |||
export default AuthRegister; |
@@ -0,0 +1,66 @@ | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { useMediaQuery, Button, Stack } from '@mui/material'; | |||
// assets | |||
import Google from 'assets/images/icons/google.svg'; | |||
import Twitter from 'assets/images/icons/twitter.svg'; | |||
import Facebook from 'assets/images/icons/facebook.svg'; | |||
// ==============================|| FIREBASE - SOCIAL BUTTON ||============================== // | |||
const FirebaseSocial = () => { | |||
const theme = useTheme(); | |||
const matchDownSM = useMediaQuery(theme.breakpoints.down('sm')); | |||
const googleHandler = async () => { | |||
// login || singup | |||
}; | |||
const twitterHandler = async () => { | |||
// login || singup | |||
}; | |||
const facebookHandler = async () => { | |||
// login || singup | |||
}; | |||
return ( | |||
<Stack | |||
direction="row" | |||
spacing={matchDownSM ? 1 : 2} | |||
justifyContent={matchDownSM ? 'space-around' : 'space-between'} | |||
sx={{ '& .MuiButton-startIcon': { mr: matchDownSM ? 0 : 1, ml: matchDownSM ? 0 : -0.5 } }} | |||
> | |||
<Button | |||
variant="outlined" | |||
color="secondary" | |||
fullWidth={!matchDownSM} | |||
startIcon={<img src={Google} alt="Google" />} | |||
onClick={googleHandler} | |||
> | |||
{!matchDownSM && 'Google'} | |||
</Button> | |||
<Button | |||
variant="outlined" | |||
color="secondary" | |||
fullWidth={!matchDownSM} | |||
startIcon={<img src={Twitter} alt="Twitter" />} | |||
onClick={twitterHandler} | |||
> | |||
{!matchDownSM && 'Twitter'} | |||
</Button> | |||
<Button | |||
variant="outlined" | |||
color="secondary" | |||
fullWidth={!matchDownSM} | |||
startIcon={<img src={Facebook} alt="Facebook" />} | |||
onClick={facebookHandler} | |||
> | |||
{!matchDownSM && 'Facebook'} | |||
</Button> | |||
</Stack> | |||
); | |||
}; | |||
export default FirebaseSocial; |
@@ -0,0 +1,24 @@ | |||
// material-ui | |||
import { styled } from '@mui/material/styles'; | |||
// project import | |||
import ComponentSkeleton from './ComponentSkeleton'; | |||
import MainCard from 'components/MainCard'; | |||
// styles | |||
const IFrameWrapper = styled('iframe')(() => ({ | |||
height: 'calc(100vh - 210px)', | |||
border: 'none' | |||
})); | |||
// ============================|| ANT ICONS ||============================ // | |||
const AntIcons = () => ( | |||
<ComponentSkeleton> | |||
<MainCard title="Ant Icons"> | |||
<IFrameWrapper title="Ant Icon" width="100%" src="https://ant.design/components/icon/" /> | |||
</MainCard> | |||
</ComponentSkeleton> | |||
); | |||
export default AntIcons; |
@@ -0,0 +1,141 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box, Card, Grid, Stack, Typography } from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
import ComponentSkeleton from './ComponentSkeleton'; | |||
// ===============================|| COLOR BOX ||=============================== // | |||
function ColorBox({ bgcolor, title, data, dark, main }) { | |||
return ( | |||
<> | |||
<Card sx={{ '&.MuiPaper-root': { borderRadius: '0px' } }}> | |||
<Box | |||
sx={{ | |||
display: 'flex', | |||
justifyContent: 'center', | |||
alignItems: 'center', | |||
py: 2.5, | |||
bgcolor, | |||
color: dark ? 'grey.800' : '#ffffff', | |||
border: main ? '1px dashed' : '1px solid transparent' | |||
}} | |||
> | |||
{title && ( | |||
<Grid container justifyContent="space-around" alignItems="center"> | |||
<Grid item> | |||
{data && ( | |||
<Stack spacing={0.75} alignItems="center"> | |||
<Typography variant="subtitle2">{data.label}</Typography> | |||
<Typography variant="subtitle1">{data.color}</Typography> | |||
</Stack> | |||
)} | |||
</Grid> | |||
<Grid item> | |||
<Typography variant="subtitle1" color="inherit"> | |||
{title} | |||
</Typography> | |||
</Grid> | |||
</Grid> | |||
)} | |||
</Box> | |||
</Card> | |||
</> | |||
); | |||
} | |||
ColorBox.propTypes = { | |||
bgcolor: PropTypes.string, | |||
title: PropTypes.string, | |||
data: PropTypes.object.isRequired, | |||
dark: PropTypes.bool, | |||
main: PropTypes.bool | |||
}; | |||
// ===============================|| COMPONENT - COLOR ||=============================== // | |||
const ComponentColor = () => ( | |||
<ComponentSkeleton> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12} sm={6} md={4}> | |||
<MainCard title="Primary Color" codeHighlight> | |||
<Stack> | |||
<ColorBox bgcolor="primary.lighter" data={{ label: 'Blue-1', color: '#e6f7ff' }} title="primary.lighter" dark /> | |||
<ColorBox bgcolor="primary.100" data={{ label: 'Blue-2', color: '#bae7ff' }} title="primary[100]" dark /> | |||
<ColorBox bgcolor="primary.200" data={{ label: 'Blue-3', color: '#91d5ff' }} title="primary[200]" dark /> | |||
<ColorBox bgcolor="primary.light" data={{ label: 'Blue-4', color: '#69c0ff' }} title="primary.light" dark /> | |||
<ColorBox bgcolor="primary.400" data={{ label: 'Blue-5', color: '#40a9ff' }} title="primary[400]" /> | |||
<ColorBox bgcolor="primary.main" data={{ label: 'Blue-6', color: '#1890ff' }} title="primary.main" main /> | |||
<ColorBox bgcolor="primary.dark" data={{ label: 'Blue-7', color: '#096dd9' }} title="primary.dark" /> | |||
<ColorBox bgcolor="primary.700" data={{ label: 'Blue-8', color: '#0050b3' }} title="primary[700]" /> | |||
<ColorBox bgcolor="primary.darker" data={{ label: 'Blue-9', color: '#003a8c' }} title="primary.darker" /> | |||
<ColorBox bgcolor="primary.900" data={{ label: 'Blue-10', color: '#002766' }} title="primary.900" /> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4}> | |||
<MainCard title="Secondary Color" codeHighlight> | |||
<Stack> | |||
<ColorBox bgcolor="secondary.lighter" data={{ label: 'Grey-1', color: '#fafafa' }} title="secondary.lighter" dark /> | |||
<ColorBox bgcolor="secondary.100" data={{ label: 'Grey-2', color: '#f5f5f5' }} title="secondary[100]" dark /> | |||
<ColorBox bgcolor="secondary.200" data={{ label: 'Grey-3', color: '#f0f0f0' }} title="secondary[200]" dark /> | |||
<ColorBox bgcolor="secondary.light" data={{ label: 'Grey-4', color: '#d9d9d9' }} title="secondary.light" dark /> | |||
<ColorBox bgcolor="secondary.400" data={{ label: 'Grey-5', color: '#bfbfbf' }} title="secondary[400]" dark /> | |||
<ColorBox bgcolor="secondary.main" data={{ label: 'Grey-6', color: '#8c8c8c' }} title="secondary.main" main /> | |||
<ColorBox bgcolor="secondary.600" data={{ label: 'Grey-7', color: '#595959' }} title="secondary.600" /> | |||
<ColorBox bgcolor="secondary.dark" data={{ label: 'Grey-8', color: '#262626' }} title="secondary.dark" /> | |||
<ColorBox bgcolor="secondary.800" data={{ label: 'Grey-9', color: '#141414' }} title="secondary[800]" /> | |||
<ColorBox bgcolor="secondary.darker" data={{ label: 'Grey-10', color: '#000000' }} title="secondary.darker" /> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4}> | |||
<MainCard title="Other Color" codeHighlight> | |||
<Stack> | |||
<ColorBox bgcolor="secondary.A100" data={{ label: 'Grey-A1', color: '#ffffff' }} title="secondary.A100" dark /> | |||
<ColorBox bgcolor="secondary.A200" data={{ label: 'Grey-A2', color: '#434343' }} title="secondary.A200" /> | |||
<ColorBox bgcolor="secondary.A300" data={{ label: 'Grey-A3', color: '#1f1f1f' }} title="secondary.A300" /> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4}> | |||
<MainCard title="Success Color" codeHighlight> | |||
<Stack> | |||
<ColorBox bgcolor="success.lighter" data={{ label: 'Green-1', color: '#f6ffed' }} title="success.lighter" dark /> | |||
<ColorBox bgcolor="success.light" data={{ label: 'Green-4', color: '#95de64' }} title="success.light" dark /> | |||
<ColorBox bgcolor="success.main" data={{ label: 'Green-6', color: '#52c41a' }} title="success.main" main /> | |||
<ColorBox bgcolor="success.dark" data={{ label: 'Green-8', color: '#237804' }} title="success.dark" /> | |||
<ColorBox bgcolor="success.darker" data={{ label: 'Green-10', color: '#092b00' }} title="success.darker" /> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4}> | |||
<MainCard title="Error Color" codeHighlight> | |||
<Stack> | |||
<ColorBox bgcolor="error.lighter" data={{ label: 'Red-1', color: '#fff1f0' }} title="error.lighter" dark /> | |||
<ColorBox bgcolor="error.light" data={{ label: 'Red-4', color: '#ff7875' }} title="error.light" dark /> | |||
<ColorBox bgcolor="error.main" data={{ label: 'Red-6', color: '#f5222d' }} title="error.main" main /> | |||
<ColorBox bgcolor="error.dark" data={{ label: 'Red-8', color: '#a8071a' }} title="error.dark" /> | |||
<ColorBox bgcolor="error.darker" data={{ label: 'Red-10', color: '#5c0011' }} title="error.darker" /> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
<Grid item xs={12} sm={6} md={4}> | |||
<MainCard title="Warning Color" codeHighlight> | |||
<Stack> | |||
<ColorBox bgcolor="warning.lighter" data={{ label: 'Gold-1', color: '#fffbe6' }} title="warning.lighter" dark /> | |||
<ColorBox bgcolor="warning.light" data={{ label: 'Gold-4', color: '#ffd666' }} title="warning.light" dark /> | |||
<ColorBox bgcolor="warning.main" data={{ label: 'Gold-6', color: '#faad14' }} title="warning.main" main /> | |||
<ColorBox bgcolor="warning.dark" data={{ label: 'Gold-8', color: '#ad6800' }} title="warning.dark" /> | |||
<ColorBox bgcolor="warning.darker" data={{ label: 'Gold-10', color: '#613400' }} title="warning.darker" /> | |||
</Stack> | |||
</MainCard> | |||
</Grid> | |||
</Grid> | |||
</ComponentSkeleton> | |||
); | |||
export default ComponentColor; |
@@ -0,0 +1,59 @@ | |||
import PropTypes from 'prop-types'; | |||
import { useEffect, useState } from 'react'; | |||
// material-ui | |||
import { Grid, Skeleton, Stack } from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
// ===============================|| COMPONENT - SKELETON ||=============================== // | |||
const ComponentSkeleton = ({ children }) => { | |||
const [isLoading, setLoading] = useState(true); | |||
useEffect(() => { | |||
setLoading(false); | |||
}, []); | |||
const skeletonCard = ( | |||
<MainCard | |||
title={<Skeleton sx={{ width: { xs: 120, md: 180 } }} />} | |||
secondary={<Skeleton animation="wave" variant="circular" width={24} height={24} />} | |||
> | |||
<Stack spacing={1}> | |||
<Skeleton /> | |||
<Skeleton sx={{ height: 64 }} animation="wave" variant="rectangular" /> | |||
<Skeleton /> | |||
<Skeleton /> | |||
</Stack> | |||
</MainCard> | |||
); | |||
return ( | |||
<> | |||
{isLoading && ( | |||
<Grid container spacing={3}> | |||
<Grid item xs={12} md={6}> | |||
{skeletonCard} | |||
</Grid> | |||
<Grid item xs={12} md={6}> | |||
{skeletonCard} | |||
</Grid> | |||
<Grid item xs={12} md={6}> | |||
{skeletonCard} | |||
</Grid> | |||
<Grid item xs={12} md={6}> | |||
{skeletonCard} | |||
</Grid> | |||
</Grid> | |||
)} | |||
{!isLoading && children} | |||
</> | |||
); | |||
}; | |||
ComponentSkeleton.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default ComponentSkeleton; |