@@ -0,0 +1,2 @@ | |||
REACT_APP_VERSION = v1.0.1 | |||
GENERATE_SOURCEMAP = false |
@@ -0,0 +1,42 @@ | |||
{ | |||
"development": { | |||
"REACT_APP_ENV": "development", | |||
"REACT_APP_URL": "http://localhost:3000/", | |||
"REACT_APP_BACKEND_PROTOCOL": "http", | |||
"REACT_APP_BACKEND_HOST": "localhost", | |||
"REACT_APP_BACKEND_PORT": "8090", | |||
"REACT_APP_BACKEND_API_PATH": "/api" | |||
}, | |||
"2fi-uat": { | |||
"REACT_APP_ENV": "2fi-uat", | |||
"REACT_APP_URL": "http://192.168.1.82:80/", | |||
"REACT_APP_BACKEND_PROTOCOL": "http", | |||
"REACT_APP_BACKEND_HOST": "192.168.1.82", | |||
"REACT_APP_BACKEND_PORT": "80", | |||
"REACT_APP_BACKEND_API_PATH": "/api" | |||
}, | |||
"2fi-production": { | |||
"REACT_APP_ENV": "2fi-production", | |||
"REACT_APP_URL": "http://192.168.1.80:80/", | |||
"REACT_APP_BACKEND_PROTOCOL": "http", | |||
"REACT_APP_BACKEND_HOST": "192.168.1.80", | |||
"REACT_APP_BACKEND_PORT": "80", | |||
"REACT_APP_BACKEND_API_PATH": "/api" | |||
}, | |||
"emsd-uat": { | |||
"REACT_APP_ENV": "emsd-uat", | |||
"REACT_APP_URL": "https://ccsdawardrs-uat.emsd.hksarg/", | |||
"REACT_APP_BACKEND_PROTOCOL": "https", | |||
"REACT_APP_BACKEND_HOST": "ccsdawardrs-uat.emsd.hksarg", | |||
"REACT_APP_BACKEND_PORT": "443", | |||
"REACT_APP_BACKEND_API_PATH": "/api" | |||
}, | |||
"emsd-production": { | |||
"REACT_APP_ENV": "emsd-production", | |||
"REACT_APP_URL": "https://ccsdawardrs.emsd.hksarg/", | |||
"REACT_APP_BACKEND_PROTOCOL": "https", | |||
"REACT_APP_BACKEND_HOST": "ccsdawardrs.emsd.hksarg", | |||
"REACT_APP_BACKEND_PORT": "443", | |||
"REACT_APP_BACKEND_API_PATH": "/api" | |||
} | |||
} |
@@ -0,0 +1,91 @@ | |||
{ | |||
"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": [ | |||
"off", | |||
{ | |||
"patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"] | |||
} | |||
], | |||
"no-unused-vars": [ | |||
// "error", | |||
"off", | |||
{ | |||
"ignoreRestSiblings": false | |||
} | |||
], | |||
"prettier/prettier": [ | |||
"off", | |||
{ | |||
"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,13 @@ | |||
# 2Fi LIONER Frontend Setup | |||
## 1. Install NPM Modules | |||
- Run the following command in terminal | |||
``` | |||
npm install | |||
``` | |||
## 2. Run the application | |||
- Start the application by the following command: | |||
``` | |||
npm start | |||
``` |
@@ -0,0 +1,12 @@ | |||
{ | |||
"compilerOptions": { | |||
"target": "esnext", | |||
"module": "esnext", | |||
"baseUrl": "src", | |||
"paths": { | |||
"@assets/*": ["assets/*"] | |||
} | |||
}, | |||
"include": ["src/**/*"], | |||
"exclude": ["node_modules"] | |||
} |
@@ -0,0 +1,130 @@ | |||
{ | |||
"name": "ars-react", | |||
"version": "1.1.2", | |||
"private": true, | |||
"homepage": "", | |||
"dependencies": { | |||
"@ant-design/colors": "^6.0.0", | |||
"@ant-design/icons": "^4.7.0", | |||
"@casl/ability": "^6.5.0", | |||
"@casl/react": "^3.1.0", | |||
"@emotion/cache": "^11.10.3", | |||
"@emotion/react": "^11.10.4", | |||
"@emotion/styled": "^11.10.4", | |||
"@mantine/core": "^7.0.2", | |||
"@material-ui/pickers": "^3.3.10", | |||
"@mui/icons-material": "^5.14.1", | |||
"@mui/lab": "^5.0.0-alpha.139", | |||
"@mui/material": "^5.14.11", | |||
"@mui/styles": "^5.14.15", | |||
"@mui/system": "^5.14.11", | |||
"@mui/x-data-grid": "^6.11.1", | |||
"@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", | |||
"@uppy/core": "^3.5.0", | |||
"@uppy/dashboard": "^3.5.2", | |||
"@uppy/drag-drop": "^3.0.3", | |||
"@uppy/file-input": "^3.0.3", | |||
"@uppy/progress-bar": "^3.0.3", | |||
"@uppy/react": "^3.1.3", | |||
"@uppy/thumbnail-generator": "^3.0.4", | |||
"@uppy/tus": "^3.2.0", | |||
"@uppy/webcam": "^3.3.2", | |||
"@uppy/xhr-upload": "^3.4.0", | |||
"apexcharts": "^3.37.3", | |||
"axios": "^1.4.0", | |||
"buffer": "^6.0.3", | |||
"date-fns": "^2.30.0", | |||
"dayjs": "^1.11.10", | |||
"env-cmd": "^10.1.0", | |||
"formik": "^2.2.9", | |||
"framer-motion": "^7.3.6", | |||
"history": "^5.3.0", | |||
"i18next": "^23.5.1", | |||
"jwt-decode": "^3.1.2", | |||
"lodash": "^4.17.21", | |||
"moment": "^2.29.4", | |||
"mui-file-input": "^3.0.2", | |||
"mui-image": "^1.0.7", | |||
"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-hook-form": "^7.45.4", | |||
"react-i18next": "^13.2.2", | |||
"react-idle-timer": "^5.7.2", | |||
"react-intl": "^6.4.7", | |||
"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-toastify": "^9.1.3", | |||
"react-window": "^1.8.7", | |||
"redux": "^4.2.0", | |||
"simplebar": "^5.3.8", | |||
"simplebar-react": "^2.4.1", | |||
"stream": "0.0.2", | |||
"timers": "^0.1.1", | |||
"typescript": "4.8.3", | |||
"web-vitals": "^3.0.2", | |||
"xlsx": "^0.18.5", | |||
"xml2js": "^0.6.2", | |||
"yup": "^0.32.11" | |||
}, | |||
"scripts": { | |||
"start": "env-cmd --environments development react-scripts start", | |||
"production": "env-cmd --production production 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", | |||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", | |||
"@mui/core": "^5.0.0-alpha.54", | |||
"@mui/x-date-pickers": "^6.18.0", | |||
"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="Lioner System" /> | |||
<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="Lioner System" /> | |||
<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="Lioner System" /> | |||
<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>Lioner System</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,88 @@ | |||
// project import | |||
import Routes from 'routes'; | |||
import ThemeCustomization from 'themes'; | |||
import ScrollTop from 'components/ScrollTop'; | |||
import {ToastContainer} from "react-toastify"; | |||
import React from "react"; | |||
import 'react-toastify/dist/ReactToastify.css'; | |||
import {useEffect} from "react"; | |||
import {useDispatch} from "react-redux"; | |||
import { | |||
SetupAxiosInterceptors | |||
} from "./auth"; | |||
import { useNavigate } from 'react-router-dom'; | |||
//import {isUserLoggedIn} from 'utils/Utils'; | |||
//import {DefaultRoute} from 'routes/index' | |||
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== // | |||
const App = () => { | |||
const dispatch = useDispatch(); | |||
const navigate = useNavigate(); | |||
// useEffect(() => { | |||
// const handleBeforeUnload = () => { | |||
// let url = new URL(window.location.href); | |||
// let pathname = url.pathname; | |||
// const lastPath = localStorage.getItem('lastVisitedPath'); | |||
// if ( | |||
// pathname !== '/login' && | |||
// pathname !== '/lionerDashboard' && | |||
// pathname !== lastPath && | |||
// !isRefresh.current && isUserClick.current) { | |||
// //for all page auto logout | |||
// dispatch(handleLogoutFunction()); | |||
// window.location.reload(); | |||
// } | |||
// else if (!isRefresh.current && !isUserClick.current) { | |||
// //for dashboard and login page auto logout | |||
// dispatch(handleLogoutFunction()); | |||
// window.location.reload(); | |||
// } | |||
// }; | |||
// const handleRefresh = () => { | |||
// let url = new URL(window.location.href); | |||
// let pathname = url.pathname; | |||
// if(pathname !== '/lionerDashboard'){ | |||
// //skip checking for unload dashboard | |||
// isUserClick.current = true; | |||
// } | |||
// if (event.key === 'F5' || | |||
// (event.ctrlKey && event.key === 'r') || | |||
// (event.ctrlKey && event.shiftKey && event.key === 'R') | |||
// ) { | |||
// // User pressed F5 or Ctrl+R, do not trigger logout | |||
// isRefresh.current = true; | |||
// } | |||
// }; | |||
// window.addEventListener('beforeunload', handleBeforeUnload); | |||
// window.addEventListener('keydown', handleRefresh); | |||
// return () => { | |||
// window.removeEventListener('beforeunload', handleBeforeUnload); | |||
// window.removeEventListener('keydown', handleRefresh); | |||
// }; | |||
// }, [dispatch]); | |||
useEffect(() => { | |||
let url = new URL(window.location.href); | |||
let pathname = url.pathname; | |||
if (pathname !== '/login'){ | |||
SetupAxiosInterceptors(dispatch, navigate); | |||
} | |||
}, []) | |||
return ( | |||
<ThemeCustomization> | |||
<ScrollTop> | |||
<Routes> | |||
</Routes> | |||
</ScrollTop> | |||
<ToastContainer/> | |||
</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,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,39 @@ | |||
.container { | |||
position: relative; | |||
width: 100%; | |||
} | |||
.image { | |||
display: block; | |||
width: 100%; | |||
height: auto; | |||
} | |||
.overlay { | |||
position: absolute; | |||
top: 0; | |||
bottom: 0; | |||
left: 0; | |||
right: 0; | |||
height: 100%; | |||
width: 100%; | |||
opacity: 0; | |||
transition: .8s ease; | |||
background-color: #222222; | |||
} | |||
.container:hover .overlay { | |||
opacity: 0.7; | |||
} | |||
.text { | |||
color: white; | |||
font-size: 20px; | |||
position: absolute; | |||
top: 50%; | |||
left: 50%; | |||
-webkit-transform: translate(-50%, -50%); | |||
-ms-transform: translate(-50%, -50%); | |||
transform: translate(-50%, -50%); | |||
text-align: center; | |||
} |
@@ -0,0 +1,125 @@ | |||
.rank-board{ | |||
padding-top: 50px; | |||
} | |||
.number-one{ | |||
font-size: 20px; | |||
font-weight: bold; | |||
} | |||
.number-two{ | |||
font-size: 18px; | |||
font-weight: bold; | |||
} | |||
.number-three{ | |||
font-size: 16px; | |||
font-weight: bold; | |||
} | |||
.number-default{ | |||
font-size: 14px; | |||
font-weight: bold; | |||
} | |||
.MuiTableCell-root { | |||
border-bottom: 1px solid #78909C; | |||
} | |||
.rank-card-wave { | |||
display: block; | |||
position: relative; | |||
color: white; | |||
height: 60px; | |||
width: 100%; | |||
transform: scale(1, 1); | |||
} | |||
.rank-card-wave-blue { | |||
background: #448df2; | |||
} | |||
.rank-card-wave-green { | |||
background: #4bb641; | |||
} | |||
.rank-card-wave-orange { | |||
background: #efb142; | |||
} | |||
.rank-card-wave-red { | |||
background: #e03c04; | |||
} | |||
.rank-card-wave span{ | |||
font-size: calc(1em + 1vw); | |||
} | |||
.rank-card-no-wave { | |||
vertical-align: middle; | |||
position: relative; | |||
color: white; | |||
height: 50px; | |||
width: 100%; | |||
transform: scale(1, 1); | |||
} | |||
.rank-card-no-wave span{ | |||
font-size: calc(1em + 0.5vw); | |||
} | |||
.rank-card-no-wave-orange { | |||
background: #efb142; | |||
} | |||
.rank-card-no-wave-green { | |||
background: #4bb641; | |||
} | |||
.rank-card-no-wave-blue { | |||
background: #448df2; | |||
} | |||
.rank-card-no-wave-red { | |||
background: #e03c04; | |||
} | |||
.rank-card-wave:before { | |||
content: ""; | |||
display: block; | |||
position: absolute; | |||
border-radius: 100%; | |||
width: 100%; | |||
height: 300px; | |||
background-color: white; | |||
right: -25%; | |||
top: 20px; | |||
z-index: -1; | |||
} | |||
.rank-card-wave:after { | |||
content: ""; | |||
display: block; | |||
position: absolute; | |||
border-radius: 100%; | |||
width: 100%; | |||
height: 300px; | |||
left: -34%; | |||
top: -220px; | |||
clip-path: ellipse(100% 15% at -15% 100%); | |||
z-index: -1; | |||
} | |||
.rank-card-wave-blue:after { | |||
background: #448df2; | |||
} | |||
.rank-card-wave-green:after { | |||
background: #4bb641; | |||
} | |||
.rank-card-wave-orange:after { | |||
background: #efb142; | |||
} | |||
.rank-card-wave-red:after { | |||
background: #e03c04; | |||
} |
@@ -0,0 +1,4 @@ | |||
.apexcharts-legend-series .apexcharts-legend-marker { | |||
left: -4px !important; | |||
top: 2px !important; | |||
} |
@@ -0,0 +1,75 @@ | |||
html, body { | |||
background : #dddedf; | |||
font-family: Arial,Helvetica,sans-serif; | |||
height: 100%; | |||
width: 100%; | |||
padding: 20px 0; | |||
} | |||
* { | |||
box-sizing: border-box; | |||
} | |||
.container { | |||
width: 675px; | |||
padding: 40px; | |||
border-top: 5px solid #f4a807; | |||
margin: 0 auto; | |||
background: #1c2227; | |||
color: #fff; | |||
min-height: 100%; | |||
overflow: visible; | |||
} | |||
.logo { | |||
padding: 10px 0; | |||
} | |||
h1 { | |||
font-size: 18px; | |||
font-weight: 900; | |||
letter-spacing: 0.35em; | |||
margin: 15px 0 10px; | |||
padding: 20px 0 0; | |||
text-transform: uppercase; | |||
} | |||
h2 { | |||
font-size: 1.4em; | |||
margin: 15px 0 10px; | |||
padding: 0; | |||
text-align: left; | |||
} | |||
p { | |||
font-size: 13px; | |||
line-height: 24px; | |||
margin: 0 0 12px; | |||
padding: 0; | |||
text-align: left; | |||
} | |||
p.intro { | |||
margin-bottom: 20px; | |||
} | |||
p.read-more { | |||
text-align: center; | |||
margin-bottom: 40px; | |||
} | |||
.progressBar { | |||
height: 40px; | |||
} | |||
ul { | |||
clear: both; | |||
margin: 0; | |||
padding: 0; | |||
list-style: none; | |||
} | |||
ul li { | |||
display: inline-block; | |||
width: 33%; | |||
padding: 1%; | |||
} | |||
ul li img { | |||
width: 98%; | |||
} |
@@ -0,0 +1,190 @@ | |||
// ** 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 { apiPath } from './utils'; | |||
export const refreshIntervalName = 'refreshInterval'; | |||
export const predictProductionQty = 'predictProductionQty'; | |||
export const predictUsageCount = 'predictUsageCount'; | |||
export const windowCount = 'windowCount'; | |||
import { REFRESH_TOKEN } from '../utils/ApiPathConst'; | |||
//import {useContext} from "react"; | |||
//import UploadContext from "../components/UploadProvider"; | |||
// ** Handle User Login | |||
export const handleLogin = (data) => { | |||
return (dispatch) => { | |||
dispatch({ | |||
type: 'LOGIN', | |||
data, | |||
jwtApplicationConfig, | |||
accessToken: data['accessToken'], | |||
refreshToken: data['refreshToken'], | |||
subDivisionId: data['subDivisionId'] | |||
}); | |||
// ** 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('abilities', data.abilities); | |||
localStorage.setItem('subDivisionId', data.subDivisionId); | |||
localStorage.setItem('lotusNotesUser', data.lotusNotesUser); | |||
//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'); | |||
localStorage.setItem('checkPasswordExpired', false); | |||
}; | |||
}; | |||
// ** Handle User Logout | |||
export const handleLogoutFunction = () => { | |||
return (dispatch) => { | |||
dispatch({ | |||
type: 'LOGOUT', | |||
accessToken: null, | |||
refreshToken: null | |||
}); | |||
// ** Remove user, accessToken & refreshToken from localStorage | |||
localStorage.removeItem('userData'); | |||
localStorage.removeItem('accessToken'); | |||
localStorage.removeItem('refreshToken'); | |||
localStorage.removeItem('abilities'); | |||
localStorage.removeItem('lastVisitedPath'); | |||
//localStorage.removeItem(config.storageUserRoleKeyName) | |||
localStorage.removeItem(refreshIntervalName); | |||
localStorage.removeItem(windowCount); | |||
localStorage.removeItem(predictProductionQty); | |||
localStorage.removeItem(predictUsageCount); | |||
}; | |||
}; | |||
export const isLocalTokenValid = () => { | |||
axios | |||
.get(`${apiPath}/test`) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
}) | |||
.catch((error) => { | |||
console.log(error); | |||
return false; | |||
}); | |||
return false; | |||
}; | |||
export const isTokenValid = () => { | |||
if (localStorage.getItem('accessToken') !== null && localStorage.getItem('accessToken') !== 'null') { | |||
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; | |||
} else { | |||
return false; | |||
} | |||
}; | |||
// ** Handle axios token | |||
export const SetupAxiosInterceptors = (dispatch, navigate) => { | |||
let isRefreshToken= false; | |||
//const { setIsUploading } = useContext(UploadContext); | |||
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) | |||
); | |||
axios.interceptors.response.use( | |||
(response) => { | |||
//updateLastRequestTime(Date.now()); | |||
return response; | |||
}, | |||
async (error) => { | |||
// ** const { config, response: { status } } = error | |||
//const {response} = error | |||
// if (error.response.status === 401) { | |||
// dispatch(handleLogoutFunction()); | |||
// navigate('/login'); | |||
// } | |||
// | |||
// // ** if (status === 401) { | |||
// if (response.status === 401) { | |||
// dispatch(handleLogoutFunction()); | |||
// navigate('/login'); | |||
// } | |||
// | |||
// if (response && response.status === 401) { | |||
// dispatch(handleLogoutFunction()); | |||
// navigate('/login'); | |||
// } | |||
if (error.response.status === 401 && error.config.url !== apiPath + REFRESH_TOKEN) { | |||
// Make a request to refresh the access token | |||
const refreshToken = localStorage.getItem('refreshToken'); | |||
if (isRefreshToken) { | |||
return; | |||
} | |||
isRefreshToken = true; | |||
return axios | |||
.post(`${apiPath}${REFRESH_TOKEN}`, { | |||
refreshToken: refreshToken // Replace with your refresh token | |||
}) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
const newAccessToken = response.data.accessToken; | |||
const newRefreshToken = response.data.refreshToken; | |||
localStorage.setItem('accessToken', newAccessToken); | |||
localStorage.setItem('refreshToken', newRefreshToken); | |||
isRefreshToken = false; | |||
window.location.reload(); | |||
} | |||
}) | |||
.catch((refreshError) => { | |||
dispatch(handleLogoutFunction()); | |||
navigate('/login'); | |||
isRefreshToken = false; | |||
window.location.reload(); | |||
throw refreshError; | |||
}); | |||
} else { | |||
if (error.response.status === 401) { | |||
await dispatch(handleLogoutFunction()); | |||
await navigate('/login'); | |||
await window.location.reload(); | |||
} | |||
if (error.response.status === 500){ | |||
//setIsUploading(false); | |||
} | |||
} | |||
return 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,7 @@ | |||
// ** Core JWT Import | |||
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 = process.env.REACT_APP_BACKEND_HOST | |||
const hostPort = process.env.REACT_APP_BACKEND_PORT | |||
export const hostPath = `${process.env.REACT_APP_BACKEND_PROTOCOL}://${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,17 @@ | |||
import React, {createContext} from 'react'; | |||
import {createUserAbility} from "../utils/UserAbilityUtils"; | |||
import {getUserAuthList} from "../utils/CommonFunction"; | |||
const AbilityContext = createContext(); | |||
export const AbilityProvider = ({ children }) => { | |||
const ability = createUserAbility(getUserAuthList()); | |||
return ( | |||
<AbilityContext.Provider value={ability}> | |||
{children} | |||
</AbilityContext.Provider> | |||
); | |||
}; | |||
export default AbilityContext; | |||
@@ -0,0 +1,149 @@ | |||
import React, { createContext, useState, useEffect } from 'react'; | |||
import {handleLogoutFunction} from "../auth"; | |||
import {dispatch} from "../store"; | |||
import {useNavigate} from "react-router-dom"; | |||
import axios from "axios"; | |||
import {apiPath, getUserData} from "../auth/utils"; | |||
import {GET_IDLE_LOGOUT_TIME, GET_USER_PASSWORD_DURATION} from "../utils/ApiPathConst"; | |||
import {isObjEmpty} from "../utils/Utils"; | |||
import {useIdleTimer} from "react-idle-timer"; | |||
import {LIONER_FORM_THEME} from "../themes/themeConst"; | |||
import {ChangePasswordWindow} from "../layout/MainLayout/Header/HeaderContent/Profile/ChangePasswordWindow"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
const TimerContext = createContext(); | |||
const AutoLogoutProvider = ({ children }) => { | |||
const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | |||
const navigate = useNavigate(); | |||
const [logoutInterval, setLogoutInterval] = useState(30); | |||
const [state, setState] = useState('Active'); | |||
const [isTempWindowOpen, setIsTempWindowOpen] = useState(false); | |||
const [forceChangePassword, setForceChangePassword] = useState(false); | |||
useEffect(() => { | |||
const userData = getUserData(); | |||
const checked = localStorage.getItem("checkPasswordExpired"); | |||
if(userData !== null){ | |||
if(!userData.lotusNotesUser){ | |||
//system user | |||
if(checked === "false"){ | |||
axios.get(`${apiPath}${GET_USER_PASSWORD_DURATION}`,{ | |||
params:{ | |||
id: userData.id | |||
} | |||
}) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
setForceChangePassword(response.data.expired); | |||
if(!response.data.expired){ | |||
localStorage.setItem("checkPasswordExpired",true); | |||
} | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return false; | |||
}); | |||
} | |||
} | |||
} | |||
}, []); | |||
const onIdle = () => { | |||
setLastRequestTime(Date.now()); | |||
setState('Idle') | |||
} | |||
const onActive = () => { | |||
setLastRequestTime(Date.now()); | |||
setState('Active') | |||
} | |||
const { | |||
getRemainingTime, | |||
//getTabId, | |||
isLastActiveTab, | |||
} = useIdleTimer({ | |||
onIdle, | |||
onActive, | |||
timeout: 10_000, | |||
throttle: 500, | |||
crossTab: true, | |||
syncTimers: 200, | |||
}) | |||
const lastActiveTab = isLastActiveTab() === null ? 'loading' : isLastActiveTab() | |||
//const tabId = getTabId() === null ? 'loading' : getTabId().toString() | |||
useEffect(() => { | |||
const userData = getUserData(); | |||
if(!isObjEmpty(userData)){ | |||
axios.get(`${apiPath}${GET_IDLE_LOGOUT_TIME}`, | |||
) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
setLastRequestTime(Date.now()); | |||
setLogoutInterval(parseInt(response.data.data)); | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return false; | |||
}); | |||
} | |||
else{ | |||
//navigate('/login'); | |||
} | |||
}, []); | |||
useEffect(() => { | |||
const interval = setInterval(async () => { | |||
const currentTime = Date.now(); | |||
getRemainingTime(); | |||
if(state !== "Active" && lastActiveTab){ | |||
const timeElapsed = currentTime - lastRequestTime; | |||
if (timeElapsed >= logoutInterval * 60 * 1000) { | |||
await dispatch(handleLogoutFunction()); | |||
await navigate('/login'); | |||
await window.location.reload(); | |||
} | |||
} | |||
}, 1000); // Check every second | |||
return () => { | |||
clearInterval(interval); | |||
}; | |||
}, [lastRequestTime,logoutInterval]); | |||
useEffect(() => { | |||
//if user data from parent are not null | |||
if(forceChangePassword){ | |||
setIsTempWindowOpen(true); | |||
} | |||
}, [forceChangePassword]); | |||
const handleTempClose = (event, reason) => { | |||
if (reason && reason === "backdropClick") | |||
return; | |||
setIsTempWindowOpen(false); | |||
}; | |||
return ( | |||
<TimerContext.Provider value={{lastRequestTime,setLastRequestTime}}> | |||
{children} | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
<ChangePasswordWindow | |||
isWindowOpen={isTempWindowOpen} | |||
title={"Change Password"} | |||
onNormalClose={handleTempClose} | |||
onConfirmClose={handleTempClose} | |||
isForce={true} | |||
/> | |||
</ThemeProvider> | |||
</TimerContext.Provider> | |||
); | |||
}; | |||
export { TimerContext, AutoLogoutProvider }; |
@@ -0,0 +1,49 @@ | |||
import React, {useState, useEffect, createContext} from 'react'; | |||
import { IntlProvider } from 'react-intl'; | |||
import enMessages from '../translations/en.json'; | |||
import cnMessages from '../translations/zh-CN.json'; | |||
import hkMessages from '../translations/zh-HK.json'; | |||
const LocaleContext = createContext(); | |||
export const I18nProvider = ({ children }) => { | |||
const systemMessages = { | |||
"en": enMessages, | |||
"zh": hkMessages, | |||
"zh-HK": hkMessages, | |||
"zh-CN": cnMessages | |||
}; | |||
const [locale, setLocale] = useState('en'); // Default locale, you can change this as per your requirement | |||
const [isAdvanceDisplay, setIsAdvanceDisplay] = useState(true); // Default locale, you can change this as per your requirement | |||
const [messages, setMessages] = useState(systemMessages[locale]); | |||
useEffect(() => { | |||
if(localStorage.getItem('locale') === null){ | |||
//no locale case | |||
localStorage.setItem('locale','en'); | |||
} | |||
else{ | |||
setLocale(localStorage.getItem('locale')); | |||
} | |||
}, []); | |||
useEffect(() => { | |||
// Load the messages for the selected locale | |||
const fetchMessages = async () => { | |||
setMessages(systemMessages[locale]); | |||
}; | |||
fetchMessages(); | |||
}, [locale]); | |||
return ( | |||
<LocaleContext.Provider value={{ locale, setLocale, isAdvanceDisplay, setIsAdvanceDisplay }} > | |||
<IntlProvider locale={locale} messages={messages}> | |||
{children} | |||
</IntlProvider> | |||
</LocaleContext.Provider> | |||
); | |||
} | |||
export default LocaleContext; |
@@ -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,49 @@ | |||
// 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="170" height="60" viewBox="0 0 180 60" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<path | |||
// d="M0 11.50L3.40 11.50L3.40 45.50Q4.35 45.50 6.40 45.20L14.70 43.95L14.70 46.50L0 46.50L0 11.50ZM23.05 46.50L23.05 11.50L26.45 11.50L26.45 46.50L23.05 46.50ZM36.95 29Q36.95 25.15 38.33 21.82Q39.70 18.50 42.18 16.02Q44.65 13.55 47.98 12.17Q51.30 10.80 55.15 10.80Q59 10.80 62.33 12.17Q65.65 13.55 68.13 16.02Q70.60 18.50 71.98 21.82Q73.35 25.15 73.35 29Q73.35 32.85 71.98 36.17Q70.60 39.50 68.13 41.98Q65.65 44.45 62.33 45.83Q59 47.20 55.15 47.20Q51.30 47.20 47.98 45.83Q44.65 44.45 42.18 41.98Q39.70 39.50 38.33 36.17Q36.95 32.85 36.95 29M40.75 29Q40.75 34.10 42.55 37.92Q44.35 41.75 47.60 43.88Q50.85 46 55.15 46Q59.45 46 62.70 43.88Q65.95 41.75 67.75 37.90Q69.55 34.05 69.55 29Q69.55 23.95 67.75 20.13Q65.95 16.30 62.70 14.15Q59.45 12 55.15 12Q50.85 12 47.63 14.13Q44.40 16.25 42.58 20.07Q40.75 23.90 40.75 29ZM83.10 46.50L84.35 10.80L84.95 10.80L110.10 41.20L110.40 41.20L109.30 11.50L112.70 11.50L111.45 47.20L110.85 47.20L85.70 16.80L85.40 16.80L86.50 46.50L83.10 46.50ZM124.95 46.50L124.95 11.50L140.20 11.50L140.20 13.95L131.45 12.80Q129.15 12.50 128.35 12.50L128.35 27.75Q129.00 27.75 131.45 27.60L139.65 27.10L139.65 29.40L131.45 28.90Q129.00 28.75 128.35 28.75L128.35 45.50Q129.25 45.50 131.50 45.20L140.35 44.05L140.35 46.50L124.95 46.50ZM150.85 11.50L157.55 11.50Q162.85 11.50 165.78 13.73Q168.70 15.95 168.70 19.95Q168.70 23.05 166.88 25.10Q165.05 27.15 161.60 27.85L161.60 28.15Q164.90 28.80 167.00 31.25Q169.10 33.70 170.15 37.30Q171.10 40.60 172.03 42.42Q172.95 44.25 174.08 45.05Q175.20 45.85 176.75 46.20L176.75 46.50L174.70 46.50Q172.50 46.50 171.08 45.70Q169.65 44.90 168.65 43Q167.65 41.10 166.75 37.75Q165.30 32.60 162.90 30.45Q160.50 28.30 157.05 28.30L156.75 28.30L156.75 27.40L157.05 27.40Q165.10 27.40 165.10 20Q165.10 12.70 157.55 12.70L154.25 12.70L154.25 46.50L150.85 46.50L150.85 11.50Z" | |||
// d="M0 12.95L4.20 12.95L4.20 43.05L19.70 43.05L19.70 47.05L0 47.05L0 12.95ZM24.95 12.95L29.15 12.95L29.15 47.05L24.95 47.05L24.95 12.95ZM36.10 30.00Q36.10 25.15 37.63 21.80Q39.15 18.45 41.53 16.40Q43.90 14.35 46.83 13.42Q49.75 12.50 52.55 12.50Q55.35 12.50 58.28 13.27Q61.20 14.05 63.58 16.02Q65.95 18.00 67.48 21.40Q69 24.80 69 30.05Q69 34.95 67.48 38.30Q65.95 41.65 63.58 43.75Q61.20 45.85 58.28 46.77Q55.35 47.70 52.55 47.70Q49.75 47.70 46.83 46.80Q43.90 45.90 41.53 43.85Q39.15 41.80 37.63 38.40Q36.10 35 36.10 30.00M64.25 30.25Q64.25 23.50 61.13 20.00Q58.00 16.50 52.75 16.50Q50.10 16.50 47.93 17.32Q45.75 18.15 44.18 19.80Q42.60 21.45 41.73 23.92Q40.85 26.40 40.85 29.75Q40.85 36.50 43.98 40.10Q47.10 43.70 52.35 43.70Q54.95 43.70 57.15 42.88Q59.35 42.05 60.93 40.35Q62.50 38.65 63.38 36.15Q64.25 33.65 64.25 30.25ZM75.95 12.95L79.95 12.95L97.85 40L97.95 40Q97.85 38.55 97.80 36.90Q97.70 35.50 97.68 33.67Q97.65 31.85 97.65 29.95L97.65 12.95L101.65 12.95L101.65 47.05L97.65 47.05L79.75 19.90L79.65 19.90Q79.70 21.40 79.80 23.05Q79.85 24.50 79.90 26.30Q79.95 28.10 79.95 30.00L79.95 47.05L75.95 47.05L75.95 12.95ZM110.45 12.95L131.35 12.95L131.35 16.80L114.65 16.80L114.65 27.70L130.15 27.70L130.15 31.45L114.65 31.45L114.65 43.20L131.85 43.20L131.85 47.05L110.45 47.05L110.45 12.95ZM138.65 12.95L147.05 12.95Q149.80 12.95 152.07 13.42Q154.35 13.90 155.95 15.02Q157.55 16.15 158.40 18.00Q159.25 19.85 159.25 22.65Q159.20 25.75 158.20 27.67Q157.20 29.60 155.75 30.77Q154.30 31.95 152.40 32.50L152.40 32.60Q152.60 32.80 153.03 33.32Q153.45 33.85 153.75 34.25L163.40 47.05L158.45 47.05L148.55 33.50L142.85 33.50L142.85 47.05L138.65 47.05L138.65 12.95M155 23.35Q155 21.25 154.47 19.92Q153.95 18.60 152.93 17.90Q151.90 17.20 150.40 16.95Q148.90 16.70 147 16.70L142.85 16.70L142.85 29.75L147.80 29.75Q149.75 29.75 151.10 29.25Q152.45 28.75 153.30 27.87Q154.15 27.00 154.55 25.85Q154.95 24.70 155 23.35Z" | |||
d="M2.15 13.70Q2.15 11.70 1.68 10.68Q1.20 9.65 0.60 9.30Q0 8.95 0 9.05L7.95 9.05Q7.95 8.95 7.35 9.30Q6.75 9.65 6.25 10.68Q5.75 11.70 5.75 13.70L5.75 41.45L13.95 41.45Q16.20 41.45 17.83 40.53Q19.45 39.60 20.15 38.15L19.25 43.45L0 43.45Q0 43.60 0.60 43.23Q1.20 42.85 1.68 41.73Q2.15 40.60 2.15 38.45L2.15 13.70ZM25.25 43.45Q25.25 43.60 25.85 43.23Q26.45 42.85 26.95 41.83Q27.45 40.80 27.45 38.80L27.45 13.70Q27.45 11.70 26.98 10.68Q26.50 9.65 25.90 9.30Q25.30 8.95 25.30 9.05L33.25 9.05Q33.25 8.95 32.65 9.30Q32.05 9.65 31.55 10.68Q31.05 11.70 31.05 13.70L31.05 38.80Q31.05 40.80 31.55 41.83Q32.05 42.85 32.65 43.20Q33.25 43.55 33.25 43.45L25.25 43.45ZM39.85 26.65Q39.85 21.35 42.13 17.20Q44.40 13.05 48.53 10.75Q52.65 8.45 58.05 8.45Q62.55 8.45 66.48 10.43Q70.40 12.40 72.78 16.35Q75.15 20.30 75.15 25.85Q75.15 31.15 72.88 35.30Q70.60 39.45 66.48 41.75Q62.35 44.05 56.95 44.05Q52.45 44.05 48.53 42.08Q44.60 40.10 42.23 36.15Q39.85 32.20 39.85 26.65M71.15 26.60Q71.15 22.05 69.28 18.38Q67.40 14.70 64.13 12.63Q60.85 10.55 56.75 10.55Q53.00 10.55 50.08 12.45Q47.15 14.35 45.50 17.83Q43.85 21.30 43.85 25.90Q43.85 30.45 45.73 34.13Q47.60 37.80 50.88 39.88Q54.15 41.95 58.25 41.95Q62.00 41.95 64.93 40.05Q67.85 38.15 69.50 34.68Q71.15 31.20 71.15 26.60ZM114.00 9.05Q113.30 9.20 112.63 10.18Q111.95 11.15 111.95 13.60L111.95 44.85L86.35 15L86.35 38.95Q86.35 41.35 87.20 42.33Q88.05 43.30 88.85 43.45L81.80 43.45Q82.50 43.30 83.23 42.33Q83.95 41.35 83.95 38.90L83.95 13.90Q83.95 12.20 82.80 10.80Q81.65 9.40 79.90 9.05L86.10 9.05L109.55 36.40L109.55 13.60Q109.55 9.75 106.95 9.05L114.00 9.05ZM121.80 43.45Q121.80 43.60 122.40 43.23Q123.00 42.85 123.48 41.73Q123.95 40.60 123.95 38.45L123.95 13.90Q123.95 11.85 123.48 10.77Q123.00 9.70 122.40 9.30Q121.80 8.90 121.80 9.05L139.90 9.05L139.90 14.05Q139 11.05 134.50 11.05L127.55 11.05L127.55 25.25L136.15 25.25Q137.15 25.25 137.85 24.93Q138.55 24.60 138.90 24.25Q139.25 23.90 139.30 23.80L139.30 28.65Q139.25 28.55 138.90 28.20Q138.55 27.85 137.88 27.55Q137.20 27.25 136.20 27.25L127.55 27.25L127.55 41.45L135.35 41.45Q137.60 41.45 139.23 40.53Q140.85 39.60 141.55 38.15L140.65 43.45L121.80 43.45ZM158.25 26.15Q160.55 26.15 162.25 25.08Q163.95 24 164.83 22.28Q165.70 20.55 165.70 18.65Q165.70 16.70 164.88 14.98Q164.05 13.25 162.35 12.15Q160.65 11.05 158.20 11.05L153.95 11.05L153.95 38.85Q153.95 40.85 154.45 41.88Q154.95 42.90 155.55 43.23Q156.15 43.55 156.15 43.45L148.20 43.45Q148.20 43.55 148.80 43.20Q149.40 42.85 149.88 41.85Q150.35 40.85 150.35 38.85L150.35 13.85Q150.35 11.80 149.88 10.73Q149.40 9.65 148.80 9.27Q148.20 8.90 148.20 9.05L158.10 9.05Q162.00 9.05 164.53 10.33Q167.05 11.60 168.18 13.63Q169.30 15.65 169.30 18.05Q169.30 20.05 168.35 21.98Q167.40 23.90 165.63 25.33Q163.85 26.75 161.50 27.30L160.70 27.45L169.50 38.65Q171.25 40.60 172.40 41.65Q173.55 42.70 174.08 43.05Q174.60 43.40 174.70 43.45L168.85 43.45L155.25 26.15L158.25 26.15Z" | |||
fill="#b35e27" | |||
stroke="#b35e27" | |||
fillOpacity="1.0" | |||
/> | |||
<defs> | |||
<linearGradient id="paint0_linear" x1="8.62526" y1="14.0888" x2="5.56709" y2="17.1469" gradientUnits="userSpaceOnUse"> | |||
<stop stopColor={theme.palette.primary.darker} /> | |||
<stop offset="0.9637" stopColor={theme.palette.primary.dark} stopOpacity="0" /> | |||
</linearGradient> | |||
<linearGradient id="paint1_linear" x1="26.2675" y1="14.1279" x2="28.7404" y2="16.938" gradientUnits="userSpaceOnUse"> | |||
<stop stopColor={theme.palette.primary.darker} /> | |||
<stop offset="1" stopColor={theme.palette.primary.dark} stopOpacity="0" /> | |||
</linearGradient> | |||
</defs> | |||
</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,105 @@ | |||
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, | |||
titleClass, | |||
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 className={titleClass} sx={headerSX} titleTypographyProps={{ variant: 'subtitle1' }} title={title} action={secondary} /> | |||
)} | |||
{darkTitle && title && <CardHeader className={titleClass} 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.any, | |||
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]), | |||
titleClass: PropTypes.string, | |||
codeHighlight: PropTypes.bool, | |||
content: PropTypes.bool, | |||
children: PropTypes.node | |||
}; | |||
export default MainCard; |
@@ -0,0 +1,70 @@ | |||
import { createContext, useEffect, useRef, useCallback } from 'react'; | |||
import { apiPath } from '../auth/utils'; | |||
import { REFRESH_TOKEN } from '../utils/ApiPathConst'; | |||
import axios from 'axios'; | |||
const RefreshTokenContext = createContext(); | |||
const RefreshTokenProvider = ({ children }) => { | |||
const token = useRef(localStorage.getItem('accessToken')); | |||
const isRefresh = useRef(false); | |||
// handle Refresh Token Logic | |||
const handleRefreshToken = useCallback(() => { | |||
if (!isRefresh.current) { | |||
const refreshToken = localStorage.getItem('refreshToken'); | |||
isRefresh.current = true; | |||
axios | |||
.post(`${apiPath}${REFRESH_TOKEN}`, { | |||
refreshToken: refreshToken | |||
}) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
const newAccessToken = response.data.accessToken; | |||
const newRefreshToken = response.data.refreshToken; | |||
localStorage.setItem('accessToken', newAccessToken); | |||
localStorage.setItem('refreshToken', newRefreshToken); | |||
token.current = newAccessToken; | |||
isRefresh.current = false; | |||
} else { | |||
token.current = null; | |||
isRefresh.current = false; | |||
} | |||
}) | |||
.catch((refreshError) => { | |||
console.log('Failed to refresh token'); | |||
console.log(refreshError) | |||
token.current = null | |||
isRefresh.current = false; | |||
}); | |||
} | |||
}, []); | |||
// Function to check token expiry | |||
const checkTokenExpiry = useCallback(() => { | |||
// Check if token is present and its expiry time | |||
if (token.current) { | |||
const tokenExp = JSON.parse(atob(token.current.split('.')[1])).exp; | |||
const currentTime = Math.floor(Date.now() / 1000); | |||
const expiryTime = tokenExp - 30; // Refresh 30 seconds before expiry | |||
// console.log("check refresh Token"); | |||
// console.log(currentTime); | |||
// console.log(expiryTime); | |||
// console.log('accessToken: ' + localStorage.getItem('accessToken')); | |||
// console.log('refreshToken: ' + localStorage.getItem('refreshToken')); | |||
if (currentTime >= expiryTime) { | |||
handleRefreshToken(); | |||
} | |||
} | |||
}, [token]); | |||
// Start the timer on component mount | |||
useEffect(() => { | |||
const timer = setInterval(checkTokenExpiry, 10000); // Check every 10 second | |||
return () => clearInterval(timer); // Cleanup timer on unmount | |||
}, [checkTokenExpiry]); | |||
return <RefreshTokenContext.Provider value={{}}>{children}</RefreshTokenContext.Provider>; | |||
}; | |||
export { RefreshTokenContext, RefreshTokenProvider }; |
@@ -0,0 +1,27 @@ | |||
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,46 @@ | |||
import React, { createContext, useState } from 'react'; | |||
import {CircularProgress} from "@mui/material"; | |||
const UploadContext = createContext(); | |||
export const UploadProvider = ({ children }) => { | |||
const [isUploading, setIsUploading] = useState(false); | |||
return ( | |||
<UploadContext.Provider value={{ isUploading, setIsUploading }}> | |||
{children} | |||
{isUploading && ( | |||
<div | |||
style={{ | |||
position: 'fixed', | |||
top: 0, | |||
left: 0, | |||
width: '100%', | |||
height: '100%', | |||
backgroundColor: 'rgba(0, 0, 0, 0.5)', | |||
zIndex: 9999, | |||
display: 'flex', | |||
justifyContent: 'center', | |||
alignItems: 'center', | |||
}} | |||
> | |||
<div | |||
style={{ | |||
display: 'flex', | |||
justifyContent: 'center', | |||
alignItems: 'center', | |||
width: 75, | |||
height: 75, | |||
borderRadius: '50%', | |||
backgroundColor: 'white', | |||
}} | |||
> | |||
<CircularProgress /> | |||
</div> | |||
</div> | |||
)} | |||
</UploadContext.Provider> | |||
); | |||
}; | |||
export default UploadContext; |
@@ -0,0 +1,61 @@ | |||
// material-ui | |||
import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material'; | |||
// ==============================|| FOOTER - AUTHENTICATION ||============================== // | |||
const AuthFooter = () => { | |||
const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); | |||
return ( | |||
<Container maxWidth="xl"> | |||
<Stack | |||
direction={matchDownSM ? 'column' : 'row'} | |||
justifyContent={matchDownSM ? 'center' : 'space-between'} | |||
spacing={2} | |||
textAlign={matchDownSM ? 'center' : 'inherit'} | |||
> | |||
<Typography variant="subtitle2" color="secondary" component="span"> | |||
© Mantis React Dashboard Template By | |||
<Typography component={Link} variant="subtitle2" href="https://codedthemes.com" target="_blank" underline="hover"> | |||
CodedThemes | |||
</Typography> | |||
</Typography> | |||
<Stack direction={matchDownSM ? 'column' : 'row'} spacing={matchDownSM ? 1 : 3} textAlign={matchDownSM ? 'center' : 'inherit'}> | |||
<Typography | |||
variant="subtitle2" | |||
color="secondary" | |||
component={Link} | |||
href="https://material-ui.com/store/contributors/codedthemes/" | |||
target="_blank" | |||
underline="hover" | |||
> | |||
MUI Templates | |||
</Typography> | |||
<Typography | |||
variant="subtitle2" | |||
color="secondary" | |||
component={Link} | |||
href="https://codedthemes.com" | |||
target="_blank" | |||
underline="hover" | |||
> | |||
Privacy Policy | |||
</Typography> | |||
<Typography | |||
variant="subtitle2" | |||
color="secondary" | |||
component={Link} | |||
href="https://codedthemes.support-hub.io/" | |||
target="_blank" | |||
underline="hover" | |||
> | |||
Support | |||
</Typography> | |||
</Stack> | |||
</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: '/lionerDashboard', | |||
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,52 @@ | |||
import React, {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'; | |||
import {UploadProvider} from "./components/UploadProvider"; | |||
import {AbilityProvider} from "./components/AbilityProvider"; | |||
import {I18nProvider} from "./components/I18nProvider"; | |||
import {AutoLogoutProvider} from "./components/AutoLogoutProvider"; | |||
import {RefreshTokenProvider} from "./components/RefreshTokenProvider"; | |||
// ==============================|| 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}> | |||
<I18nProvider> | |||
<AbilityProvider> | |||
<UploadProvider> | |||
<BrowserRouter basename="/"> | |||
<RefreshTokenProvider> | |||
<AutoLogoutProvider> | |||
<App /> | |||
</AutoLogoutProvider> | |||
</RefreshTokenProvider> | |||
</BrowserRouter> | |||
</UploadProvider> | |||
</AbilityProvider> | |||
</I18nProvider> | |||
</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,97 @@ | |||
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'; | |||
import {useContext} from "react"; | |||
import AbilityContext from "../../../../../components/AbilityProvider"; | |||
import {LIONER_NAV_THEME} from "../../../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import {GENERAL_INFO_COLOR} from "../../../../../themes/colorConst"; | |||
// ==============================|| NAVIGATION - LIST GROUP ||============================== // | |||
const NavGroup = ({ item }) => { | |||
const ability = useContext(AbilityContext); | |||
const menu = useSelector((state) => state.menu); | |||
const { drawerOpen } = menu; | |||
let availableChildren = item.children.length; | |||
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': | |||
if(menuItem.ability !== undefined){ | |||
if(ability.can(menuItem.ability[0], menuItem.ability[1])){ | |||
return <NavItem key={menuItem.id} item={menuItem} level={1} />; | |||
} | |||
else{ | |||
availableChildren--; | |||
return undefined; | |||
} | |||
} | |||
else{ | |||
return <NavItem key={menuItem.id} item={menuItem} level={1} />; | |||
} | |||
default: | |||
if(menuItem.ability !== undefined){ | |||
if(ability.can(menuItem.ability[0], menuItem.ability[1])){ | |||
return ( | |||
<Typography key={menuItem.id} variant="h6" color="error" align="center"> | |||
Fix - Group Collapse or Items | |||
</Typography> | |||
); | |||
} | |||
else{ | |||
availableChildren--; | |||
return undefined; | |||
} | |||
} | |||
else{ | |||
return ( | |||
<Typography key={menuItem.id} variant="h6" color="error" align="center"> | |||
Fix - Group Collapse or Items | |||
</Typography> | |||
); | |||
} | |||
} | |||
}); | |||
return ( | |||
<List | |||
subheader={ | |||
item.title && | |||
drawerOpen && ( | |||
availableChildren > 0 ? | |||
<Box sx={{ pl: 2, mb: 0.5 }}> | |||
<ThemeProvider theme={LIONER_NAV_THEME}> | |||
<Typography color={GENERAL_INFO_COLOR}> | |||
{item.title} | |||
</Typography> | |||
</ThemeProvider> | |||
{/* only available in paid version */} | |||
</Box> | |||
: | |||
undefined | |||
) | |||
} | |||
sx={{ mb: drawerOpen ? 1.5 : 0, py: 0, zIndex: 0 }} | |||
> | |||
{navCollapse} | |||
</List> | |||
); | |||
}; | |||
NavGroup.propTypes = { | |||
item: PropTypes.object | |||
}; | |||
export default NavGroup; |
@@ -0,0 +1,148 @@ | |||
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'; | |||
import {LIONER_NAV_THEME} from "../../../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
// ==============================|| 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, | |||
mt:-0.25, mb:-0.25, | |||
pl: drawerOpen ? `${level * 28}px` : 1.5, | |||
py: !drawerOpen && level === 1 ? 1.25 : 1, | |||
height: "40px", | |||
...(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)) && ( | |||
<ThemeProvider theme={LIONER_NAV_THEME}> | |||
<ListItemText | |||
primary={ | |||
<Typography sx={{ color: isSelected ? iconSelectedColor : textColor }}> | |||
{item.title} | |||
</Typography> | |||
} | |||
/> | |||
</ThemeProvider> | |||
)} | |||
{(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,54 @@ | |||
// material-ui | |||
import { Box, Typography } from '@mui/material'; | |||
// project import | |||
import NavGroup from './NavGroup'; | |||
import menuItem from 'menu-items'; | |||
import AbilityContext from "../../../../../components/AbilityProvider"; | |||
import {useContext} from "react"; | |||
// ==============================|| DRAWER CONTENT - NAVIGATION ||============================== // | |||
const Navigation = () => { | |||
const ability = useContext(AbilityContext); | |||
const navGroups = menuItem.items.map((item) => { | |||
switch (item.type) { | |||
case 'group': | |||
if(item.ability !== undefined){ | |||
if(ability.can(item.ability[0], item.ability[1])){ | |||
return <NavGroup key={item.id} item={item} />; | |||
} | |||
else{ | |||
return undefined; | |||
} | |||
} | |||
else{ | |||
return <NavGroup key={item.id} item={item} />; | |||
} | |||
default: | |||
if(item.ability !== undefined){ | |||
if(ability.can(item.ability[0], item.ability[1])){ | |||
return ( | |||
<Typography key={item.id} variant="h6" color="error" align="center"> | |||
Fix - Navigation Group | |||
</Typography> | |||
); | |||
} | |||
else{ | |||
return undefined; | |||
} | |||
} | |||
else{ | |||
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, Chip } 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,26 @@ | |||
// material-ui | |||
import { styled } from '@mui/material/styles'; | |||
import AppBar from '@mui/material/AppBar'; | |||
// project import | |||
import { drawerWidth } from 'config'; | |||
// ==============================|| HEADER - APP BAR STYLED ||============================== // | |||
const AppBarStyled = styled(AppBar, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ | |||
zIndex: theme.zIndex.drawer + 1, | |||
transition: theme.transitions.create(['width', 'margin'], { | |||
easing: theme.transitions.easing.sharp, | |||
duration: theme.transitions.duration.leavingScreen | |||
}), | |||
...(open && { | |||
marginLeft: drawerWidth, | |||
width: `calc(100% - ${drawerWidth}px)`, | |||
transition: theme.transitions.create(['width', 'margin'], { | |||
easing: theme.transitions.easing.sharp, | |||
duration: theme.transitions.duration.enteringScreen | |||
}) | |||
}) | |||
})); | |||
export default AppBarStyled; |
@@ -0,0 +1,142 @@ | |||
import {useContext, useRef, useState} from 'react'; | |||
import ListItem from '@mui/material/ListItem'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { | |||
Box, | |||
ClickAwayListener, | |||
IconButton, | |||
List, | |||
ListItemButton, | |||
ListItemText, | |||
Paper, | |||
Popper, | |||
useMediaQuery | |||
} from '@mui/material'; | |||
import Transitions from 'components/@extended/Transitions'; | |||
import LanguageIcon from '@mui/icons-material/Language'; | |||
import {FormattedMessage} from "react-intl"; | |||
import * as React from "react"; | |||
import LocaleContext from "../../../../components/I18nProvider"; | |||
// ==============================|| HEADER CONTENT - NOTIFICATION ||============================== // | |||
const LocaleSelector = () => { | |||
const theme = useTheme(); | |||
const matchesXs = useMediaQuery(theme.breakpoints.down('md')); | |||
const { setLocale } = useContext(LocaleContext); | |||
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} | |||
> | |||
<LanguageIcon /> | |||
</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}> | |||
<List | |||
component="nav" | |||
> | |||
<ListItem disablePadding> | |||
<ListItemButton | |||
onClick={() => { | |||
setLocale("en") | |||
localStorage.setItem('locale','en'); | |||
}} | |||
> | |||
<ListItemText | |||
primary= <FormattedMessage id="en"/> | |||
/> | |||
</ListItemButton> | |||
</ListItem> | |||
<ListItem disablePadding> | |||
<ListItemButton | |||
onClick={() => { | |||
setLocale("zh-HK") | |||
localStorage.setItem('locale','zh-HK'); | |||
}} | |||
> | |||
<ListItemText | |||
primary= <FormattedMessage id="zh-HK"/> | |||
/> | |||
</ListItemButton> | |||
</ListItem> | |||
<ListItem disablePadding> | |||
<ListItemButton | |||
onClick={() => { | |||
setLocale("zh-CN") | |||
localStorage.setItem('locale','zh-CN'); | |||
}} | |||
> | |||
<ListItemText | |||
primary= <FormattedMessage id="zh-CN"/> | |||
/> | |||
</ListItemButton> | |||
</ListItem> | |||
</List> | |||
</ClickAwayListener> | |||
</Paper> | |||
</Transitions> | |||
)} | |||
</Popper> | |||
</Box> | |||
); | |||
}; | |||
export default LocaleSelector; |
@@ -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,304 @@ | |||
import {useState} from "react"; | |||
import Dialog from "@mui/material/Dialog"; | |||
import DialogTitle from "@mui/material/DialogTitle"; | |||
import DialogContent from "@mui/material/DialogContent"; | |||
import {Button, FormControl, Grid, IconButton, InputAdornment, TextField, Typography, InputLabel} from "@mui/material"; | |||
import {GENERAL_RED_COLOR} from "../../../../../themes/colorConst"; | |||
import DialogActions from "@mui/material/DialogActions"; | |||
import * as React from "react"; | |||
import {useForm} from "react-hook-form"; | |||
import {isObjEmpty} from "../../../../../utils/Utils"; | |||
import {CHANGE_PASSWORD_PATH} from "../../../../../utils/ApiPathConst"; | |||
import axios from "axios"; | |||
import {apiPath} from "../../../../../auth/utils"; | |||
import {notifySaveSuccess} from "../../../../../utils/CommonFunction"; | |||
import VisibilityOff from "@mui/icons-material/VisibilityOff"; | |||
import Visibility from "@mui/icons-material/Visibility"; | |||
import {LIONER_FORM_THEME} from "../../../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
export function ChangePasswordWindow({ | |||
isWindowOpen, | |||
title, | |||
onNormalClose, | |||
onConfirmClose, | |||
isForce | |||
}){ | |||
const [errors, setErrors] = useState({}); | |||
const {reset, register, getValues} = useForm(); | |||
const [showPassword, setShowPassword] = React.useState(false); | |||
const [showPassword2, setShowPassword2] = React.useState(false); | |||
const [showPassword3, setShowPassword3] = React.useState(false); | |||
const handleClickShowPassword = () => setShowPassword((show) => !show); | |||
const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
const handleClickShowPassword2 = () => setShowPassword2((show) => !show); | |||
const handleMouseDownPassword2 = () => setShowPassword2(!showPassword2); | |||
const handleClickShowPassword3 = () => setShowPassword3((show) => !show); | |||
const handleMouseDownPassword3 = () => setShowPassword3(!showPassword3); | |||
function updatePassword(){ | |||
const values = getValues(); | |||
axios.patch(`${apiPath}${CHANGE_PASSWORD_PATH}`, | |||
{ | |||
"password": values.oldPassword, | |||
"newPassword": values.newPassword, | |||
}) | |||
.then((response) => { | |||
if(response.status === 204){ | |||
notifySaveSuccess(); | |||
if(isForce){ | |||
localStorage.setItem('checkPasswordExpired', true); | |||
} | |||
reset(); | |||
onConfirmClose(); | |||
} | |||
}) | |||
.catch(error => { | |||
if (error.response.status === 422) { | |||
const formErrors = {}; | |||
if(error.response.data.error === "Incorrect existing password"){ | |||
formErrors.oldPassword = error.response.data.error; | |||
} | |||
else{ | |||
formErrors.confirmPassword = error.response.data.error; | |||
} | |||
setErrors(formErrors); | |||
} | |||
//console.log(error); | |||
return true; | |||
}); | |||
} | |||
function validate(){ | |||
const values = getValues(); | |||
const formErrors = {}; | |||
console.log(values); | |||
if (isObjEmpty(values.oldPassword)) { | |||
formErrors.oldPassword = 'Existing password must not be null'; | |||
} | |||
if (isObjEmpty(values.newPassword)) { | |||
formErrors.newPassword = 'New password must not be null'; | |||
} | |||
if (isObjEmpty(values.confirmPassword)) { | |||
formErrors.confirmPassword = 'Confirm password must not be null'; | |||
} | |||
if(values.newPassword !== values.confirmPassword){ | |||
formErrors.confirmPassword = 'Password not match'; | |||
} | |||
setErrors(formErrors); | |||
if (Object.keys(formErrors).length === 0) { | |||
updatePassword(); | |||
} | |||
} | |||
return ( | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
<Dialog | |||
PaperProps={{ | |||
sx: { | |||
minWidth:"40vw", | |||
maxWidth: "70vw", | |||
maxHeight: '70vh' | |||
} | |||
}} | |||
open={isWindowOpen} | |||
onClose={onNormalClose} | |||
disableEscapeKeyDown={isForce} | |||
aria-labelledby="alert-dialog-title" | |||
aria-describedby="alert-dialog-description" | |||
> | |||
<DialogTitle id="alert-dialog-title"> | |||
<InputLabel> | |||
{title} | |||
</InputLabel> | |||
</DialogTitle> | |||
<DialogContent> | |||
<form> | |||
<Grid container> | |||
<Grid item xs={12} s={12} md={12} lg={12} sx={{ml: 3, mr: 3, mb: 1}}> | |||
<Grid container alignItems={"flex-start"}> | |||
{ | |||
isForce ? | |||
<Grid item xs={12} s={12} md={12} lg={12} sx={{mb:1}}> | |||
<div> | |||
<Typography variant="lionerSize" sx={{ color: GENERAL_RED_COLOR, ml:1 }} component="span"> | |||
The password has expired. Please update your password before using the system. | |||
</Typography> | |||
</div> | |||
</Grid> | |||
: | |||
undefined | |||
} | |||
<Grid item xs={4} s={4} md={4} lg={4} | |||
sx={{ml: 1, mr: 3, mt:1.5, display: 'flex', alignItems: 'flex-start'}}> | |||
<InputLabel> | |||
Existing Password | |||
</InputLabel> | |||
<Typography sx={{ color: GENERAL_RED_COLOR }} component="span">*</Typography> | |||
</Grid> | |||
<Grid item xs={7} s={7} md={7} lg={7}> | |||
<FormControl fullWidth required> | |||
<TextField | |||
fullWidth | |||
type={showPassword ? 'text' : 'password'} | |||
{...register("oldPassword")} | |||
inputProps={{maxLength: 60}} | |||
id='oldPassword' | |||
size="small" | |||
error={!!errors.oldPassword} | |||
helperText={ | |||
errors.oldPassword ? ( | |||
<Typography component="div" color="error"> | |||
{errors.oldPassword.split("\n").map((line, index) => { | |||
return ( | |||
<Typography variant="lionerSize" key={index} > | |||
{line} | |||
</Typography> | |||
) | |||
})} | |||
</Typography> | |||
) : null | |||
} | |||
InputProps={{ | |||
endAdornment: ( | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowPassword} | |||
onMouseDown={handleMouseDownPassword} | |||
edge="end" | |||
> | |||
{showPassword ? <Visibility />: <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
) | |||
}} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
</Grid> | |||
<Grid container alignItems={"flex-start"} sx={{mt:3}}> | |||
<Grid item xs={4} s={4} md={4} lg={4} | |||
sx={{ml: 1, mr: 3, mt:1.5, display: 'flex', alignItems: 'flex-start'}}> | |||
<InputLabel> | |||
New Password | |||
</InputLabel> | |||
<Typography sx={{ color: GENERAL_RED_COLOR }} component="span">*</Typography> | |||
</Grid> | |||
<Grid item xs={7} s={7} md={7} lg={7}> | |||
<FormControl fullWidth required> | |||
<TextField | |||
fullWidth | |||
type={showPassword2 ? 'text' : 'password'} | |||
{...register("newPassword")} | |||
inputProps={{maxLength: 60}} | |||
id='newPassword' | |||
size="small" | |||
error={!!errors.newPassword} | |||
helperText={ | |||
errors.newPassword ? ( | |||
<Typography component="div" color="error"> | |||
{errors.newPassword.split("\n").map((line, index) => { | |||
return ( | |||
<Typography variant="lionerSize" key={index} > | |||
{line} | |||
</Typography> | |||
) | |||
})} | |||
</Typography> | |||
) : null | |||
} | |||
InputProps={{ | |||
endAdornment: ( | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowPassword2} | |||
onMouseDown={handleMouseDownPassword2} | |||
edge="end" | |||
> | |||
{showPassword2 ?<Visibility />: <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
) | |||
}} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
</Grid> | |||
<Grid container alignItems={"flex-start"} sx={{mt:3}}> | |||
<Grid item xs={4} s={4} md={4} lg={4} | |||
sx={{ml: 1, mr: 3, mt:1.5, display: 'flex', alignItems: 'flex-start'}}> | |||
<InputLabel> | |||
Confirm Password | |||
</InputLabel> | |||
<Typography sx={{ color: GENERAL_RED_COLOR }} component="span">*</Typography> | |||
</Grid> | |||
<Grid item xs={7} s={7} md={7} lg={7}> | |||
<FormControl fullWidth required> | |||
<TextField | |||
fullWidth | |||
type={showPassword3 ? 'text' : 'password'} | |||
{...register("confirmPassword")} | |||
inputProps={{maxLength: 60}} | |||
id='confirmPassword' | |||
size="small" | |||
error={!!errors.confirmPassword} | |||
helperText={ | |||
errors.confirmPassword ? ( | |||
<Typography component="div" color="error" variant="lionerSize"> | |||
{errors.confirmPassword.split("\n").map((line, index) => { | |||
return ( | |||
<div key={index} > | |||
{line} | |||
</div> | |||
) | |||
})} | |||
</Typography> | |||
) : null | |||
} | |||
InputProps={{ | |||
endAdornment: ( | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowPassword3} | |||
onMouseDown={handleMouseDownPassword3} | |||
edge="end" | |||
> | |||
{showPassword3 ? <Visibility /> : <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
) | |||
}} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
</form> | |||
</DialogContent> | |||
<DialogActions> | |||
<Button onClick={onNormalClose} disabled={isForce}>Cancel</Button> | |||
<Button onClick={validate} autoFocus> | |||
Confirm | |||
</Button> | |||
</DialogActions> | |||
</Dialog> | |||
</ThemeProvider> | |||
) | |||
} |
@@ -0,0 +1,146 @@ | |||
import PropTypes from 'prop-types'; | |||
import {useEffect, useState} from 'react'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { | |||
Box, | |||
List, | |||
ListItemButton, | |||
ListItemIcon, | |||
ListItem, | |||
ListItemText, | |||
Typography, | |||
ListItemSecondaryAction, | |||
Switch | |||
} from '@mui/material'; | |||
// assets | |||
import SmsOutlinedIcon from '@mui/icons-material/SmsOutlined'; | |||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; | |||
//import {POST_SEARCH_TEMPLATE_PATH, VALIDATE_TEMPLATE_NAME_PATH} from "../../../../../utils/ApiPathConst"; | |||
import * as React from "react"; | |||
import {ChangePasswordWindow} from "./ChangePasswordWindow"; | |||
import axios from "axios"; | |||
import {apiPath, getUserData} from "../../../../../auth/utils"; | |||
import {GET_USER_REMINDER_FLAG, SET_USER_REMINDER_FLAG} from "../../../../../utils/ApiPathConst"; | |||
import {notifySaveSuccess} from "../../../../../utils/CommonFunction"; | |||
import {LIONER_FORM_THEME, LIONER_NAV_THEME} from "../../../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import {isObjEmpty} from "../../../../../utils/Utils"; | |||
import {useNavigate} from "react-router-dom"; | |||
//import axios from "axios"; | |||
//import UploadContext from "../../../../../components/UploadProvider"; | |||
// ==============================|| HEADER PROFILE - PROFILE TAB ||============================== // | |||
const ProfileTab = () => { | |||
// ==============================|| NEW TEMPLATE WINDOW RELATED ||============================== // | |||
const [isTempWindowOpen, setIsTempWindowOpen] = React.useState(false); | |||
//const { setIsUploading } = useContext(UploadContext); | |||
const handleTempClose = (event, reason) => { | |||
console.log(reason); | |||
//if (reason && reason === "backdropClick") | |||
// return; | |||
setIsTempWindowOpen(false); | |||
}; | |||
// ==============================|| NEW TEMPLATE WINDOW RELATED ||============================== // | |||
const theme = useTheme(); | |||
const navigate = useNavigate() | |||
const [isReceiveNotification, setIsReceiveNotification] = useState(false); | |||
const isLotusUser = localStorage.getItem('lotusNotesUser') === "true"; | |||
const [selectedIndex, setSelectedIndex] = useState(null); | |||
const handleListItemClick = (event, index) => { | |||
if(index === 0){ | |||
setIsTempWindowOpen(true); | |||
} | |||
setSelectedIndex(index); | |||
}; | |||
useEffect(() => { | |||
//if state data are ready and assign to different field | |||
const userData = getUserData(); | |||
if(isObjEmpty(userData)){ | |||
navigate('/login'); | |||
} | |||
axios.get(`${apiPath}${GET_USER_REMINDER_FLAG}/${userData.id}`) | |||
.then((response) => { | |||
if (response.status === 200) { | |||
setIsReceiveNotification(response.data.isReminder); | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return false; | |||
}); | |||
}, []); | |||
const handleSwitchToggle = () => { | |||
updateIsReceiveNotification(!isReceiveNotification); | |||
}; | |||
const updateIsReceiveNotification = (flag) =>{ | |||
setIsReceiveNotification(flag); | |||
const userData = getUserData(); | |||
axios.post(`${apiPath}${SET_USER_REMINDER_FLAG}/${userData.id}/${flag}`) | |||
.then((response) => { | |||
if (response.status === 204) { | |||
notifySaveSuccess(); | |||
} | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return false; | |||
}); | |||
} | |||
return ( | |||
<Box> | |||
<ThemeProvider theme={LIONER_NAV_THEME}> | |||
<Typography variant="subtitle2" color="textSecondary" sx={{ml:2, mt:1,mb:1}}> | |||
Account Settings | |||
</Typography> | |||
<List component="nav" sx={{ p: 0, '& .MuiListItemIcon-root': { minWidth: 32, color: theme.palette.grey[500] } }}> | |||
{ | |||
isLotusUser ? | |||
<Box/> : | |||
<ListItemButton selected={selectedIndex === 0} onClick={(event) => handleListItemClick(event, 0)}> | |||
<ListItemIcon> | |||
<LockOutlinedIcon /> | |||
</ListItemIcon> | |||
<ListItemText primary="Change Password" /> | |||
</ListItemButton> | |||
} | |||
<ListItem selected={selectedIndex === 1} onClick={(event) => handleListItemClick(event, 1)}> | |||
<ListItemIcon> | |||
<SmsOutlinedIcon /> | |||
</ListItemIcon> | |||
<ListItemText primary="Reminder" /> | |||
<ListItemSecondaryAction> | |||
<Switch checked={isReceiveNotification} onChange={handleSwitchToggle} /> | |||
</ListItemSecondaryAction> | |||
</ListItem> | |||
</List> | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
<ChangePasswordWindow | |||
isWindowOpen={isTempWindowOpen} | |||
title={"Change Password"} | |||
onNormalClose={handleTempClose} | |||
onConfirmClose={handleTempClose} | |||
isForce={false} | |||
/> | |||
</ThemeProvider> | |||
</ThemeProvider> | |||
</Box> | |||
); | |||
}; | |||
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,169 @@ | |||
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, | |||
Typography | |||
} from '@mui/material'; | |||
// project import | |||
import MainCard from 'components/MainCard'; | |||
import Transitions from 'components/@extended/Transitions'; | |||
import ProfileTab from './ProfileTab'; | |||
// assets | |||
import defaultAvatar from 'assets/images/users/empty-avatar.png'; | |||
import { LogoutOutlined} from '@ant-design/icons'; | |||
import { handleLogoutFunction } from 'auth/index'; | |||
import {useNavigate} from "react-router-dom"; | |||
import {useDispatch} from "react-redux"; | |||
// 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 | |||
}; | |||
// ==============================|| HEADER CONTENT - PROFILE ||============================== // | |||
const Profile = () => { | |||
const theme = useTheme(); | |||
const navigate = useNavigate() | |||
const dispatch = useDispatch() | |||
const handleLogout = async () => { | |||
//dispatch(handleLogoutFunction()); | |||
await dispatch(handleLogoutFunction()); | |||
navigate('/login'); | |||
window.location.reload(); | |||
}; | |||
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 iconBackColorOpen = 'grey.300'; | |||
return ( | |||
<Box sx={{ flexShrink: 0, ml: 0.75 }}> | |||
<ButtonBase | |||
sx={{ | |||
p: 0.25, | |||
bgcolor: open ? iconBackColorOpen : 'transparent', | |||
borderRadius: 1, | |||
'&:hover': { bgcolor: 'secondary.lighter' } | |||
}} | |||
aria-label="open profile" | |||
ref={anchorRef} | |||
aria-controls={open ? 'profile-grow' : undefined} | |||
aria-haspopup="true" | |||
onClick={handleToggle} | |||
> | |||
<Stack direction="row" spacing={2} alignItems="center" sx={{ p: 0.5 }}> | |||
<Avatar alt="profile user" src={defaultAvatar} sx={{ width: 32, height: 32 }} /> | |||
<Typography variant="subtitle1">{userData == null ? "" : userData.fullName}</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 > | |||
<Grid container justifyContent="space-between" alignItems="center"> | |||
<Grid item> | |||
<Stack direction="row" spacing={1.25} alignItems="center"> | |||
{/*<Avatar alt="profile user" src={defaultAvatar} sx={{ width: 32, height: 32 }} />*/} | |||
<Stack> | |||
<Typography variant="h6" sx={{ml:3, fontWeight: 'bold'}}>{userData == null ? "" : userData.fullName}</Typography> | |||
{/*<Typography variant="body2" color="textSecondary">*/} | |||
{/* UI/UX Designer*/} | |||
{/*</Typography>*/} | |||
</Stack> | |||
</Stack> | |||
</Grid> | |||
<Grid item sx={{mt:-3, mb:-3}}> | |||
<IconButton size="large" color="secondary" onClick={handleLogout}> | |||
<LogoutOutlined /> | |||
</IconButton> | |||
</Grid> | |||
</Grid> | |||
</CardContent> | |||
{open && ( | |||
<> | |||
<ProfileTab/> | |||
</> | |||
)} | |||
</MainCard> | |||
</ClickAwayListener> | |||
</Paper> | |||
)} | |||
</Transitions> | |||
)} | |||
</Popper> | |||
</Box> | |||
); | |||
}; | |||
export default Profile; |
@@ -0,0 +1,139 @@ | |||
// material-ui | |||
import { | |||
Box, Button, | |||
FormControl, | |||
TextField | |||
} from '@mui/material'; | |||
// assets | |||
import {MODULE_COMBO} from "../../../../utils/ComboConst"; | |||
import Autocomplete from "@mui/material/Autocomplete"; | |||
import * as React from "react"; | |||
import {useContext, useEffect, useState} from "react"; | |||
import {LIONER_BUTTON_THEME } from "../../../../themes/colorConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
import {useForm} from "react-hook-form"; | |||
import {useNavigate} from "react-router"; | |||
import 'assets/style/rankStyle.css'; | |||
import {LIONER_FORM_THEME} from "../../../../themes/themeConst"; | |||
import {useLocation} from "react-router-dom"; | |||
import {Grid} from "@mui/material"; | |||
import LocaleContext from "../../../../components/I18nProvider"; | |||
// ==============================|| HEADER CONTENT - SEARCH ||============================== // | |||
const Search = () => { | |||
const [selectedModule, setSelectedModule] = useState(null); | |||
const { register, handleSubmit, setValue} = useForm() | |||
const [errors, setErrors] = useState({}); | |||
const navigate = useNavigate(); | |||
const location = useLocation(); | |||
const [isAdvanceSearch, setIsAdvanceSearch] = useState(false); | |||
const { isAdvanceDisplay } = useContext(LocaleContext); | |||
useEffect(()=>{ | |||
const path = location.pathname.split('/')[1]; | |||
if(path === "advance"){ | |||
setIsAdvanceSearch(true); | |||
} | |||
else{ | |||
setIsAdvanceSearch(false); | |||
} | |||
},[location]); | |||
const onSubmit = (data) => { | |||
const formErrors = {}; | |||
if (!data.keyword) { | |||
formErrors.keyword = 'Keyword cannot be null'; | |||
} | |||
if (selectedModule == null){ | |||
formErrors.module = 'Module is required'; | |||
} | |||
setErrors(formErrors); | |||
if (Object.keys(formErrors).length === 0) { | |||
navigate(`/advance/${selectedModule.label}/${data.keyword}`); | |||
setSelectedModule(null); | |||
setValue("keyword",null); | |||
} | |||
}; | |||
return( | |||
<Box sx={{width: '100%', ml: {xs: 0, md: 1}}}> | |||
{ | |||
!isAdvanceDisplay? | |||
<Grid item/>: | |||
<ThemeProvider theme={LIONER_FORM_THEME}> | |||
<form onSubmit={handleSubmit(onSubmit)}> | |||
<FormControl sx={{ mt:{xs:1.5, lg:0.02}, ml:3, width: {xs: '40vw', md: 250}}}> | |||
<Box | |||
sx={{ | |||
display: 'flex', | |||
flexDirection: 'column', | |||
}} | |||
> | |||
<TextField | |||
size="small" | |||
fullWidth | |||
{...register("keyword")} | |||
inputProps={{maxLength: 255}} | |||
id='keyword' | |||
label="Advanced Search" | |||
error={!!errors.keyword} | |||
helperText={errors.keyword} | |||
disabled={isAdvanceSearch} | |||
autoComplete="off" | |||
/> | |||
</Box> | |||
</FormControl> | |||
<FormControl sx={{ mt:{xs:1.5, lg:0.02}, ml:3, width: {xs: '30vw', md: 175}}}> | |||
<Autocomplete | |||
id="module-combo" | |||
size="small" | |||
value={selectedModule === null ? null : selectedModule} | |||
options={MODULE_COMBO} | |||
disabled={isAdvanceSearch} | |||
onChange={(event, newValue) => { | |||
setSelectedModule(newValue); | |||
}} | |||
renderInput={(params) => | |||
<TextField | |||
{...params} | |||
label="Module" | |||
error={!!errors.module} | |||
helperText={errors.module} | |||
disabled={isAdvanceSearch} | |||
/> | |||
} | |||
/> | |||
</FormControl> | |||
<FormControl xs={4} s={4} md={2.5} sx={{ mt:{xs:1.5, lg:0.02}, ml:3, mr:3,width: {xs: '25vw', md: 100}}}> | |||
<ThemeProvider theme={LIONER_BUTTON_THEME}> | |||
<Button | |||
size="medium" | |||
variant="contained" | |||
type="submit" | |||
color="save" | |||
style={{ zIndex: '10' }} | |||
disabled={isAdvanceSearch} | |||
> | |||
Search | |||
</Button> | |||
</ThemeProvider> | |||
</FormControl> | |||
</form> | |||
</ThemeProvider> | |||
} | |||
</Box> | |||
); | |||
}; | |||
export default Search; |
@@ -0,0 +1,30 @@ | |||
// material-ui | |||
import { Box, useMediaQuery } from '@mui/material'; | |||
// project import | |||
import Search from './Search'; | |||
import Profile from './Profile'; | |||
//import Notification from './Notification'; | |||
import MobileSection from './MobileSection'; | |||
//import LocaleSelector from "./LocaleSelector"; | |||
// ==============================|| HEADER - CONTENT ||============================== // | |||
const HeaderContent = () => { | |||
const matchesXs = useMediaQuery((theme) => theme.breakpoints.down('md')); | |||
return ( | |||
<> | |||
{!matchesXs && <Box sx={{width: '100%', ml: {xs: 0, md: 1}}} />} | |||
{matchesXs && <Box sx={{ width: '100%', ml: 1 }} />} | |||
{/*<LocaleSelector/>*/} | |||
{/*<Notification />*/} | |||
{!matchesXs && <Profile />} | |||
{matchesXs && <MobileSection />} | |||
</> | |||
); | |||
}; | |||
export default HeaderContent; |
@@ -0,0 +1,69 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { AppBar, IconButton, Toolbar, useMediaQuery } from '@mui/material'; | |||
// project import | |||
import AppBarStyled from './AppBarStyled'; | |||
import HeaderContent from './HeaderContent'; | |||
// assets | |||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; | |||
// ==============================|| MAIN LAYOUT - HEADER ||============================== // | |||
const Header = ({ open, handleDrawerToggle }) => { | |||
const theme = useTheme(); | |||
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); | |||
const iconBackColor = 'grey.100'; | |||
const iconBackColorOpen = 'grey.200'; | |||
// common header | |||
const mainHeader = ( | |||
<Toolbar> | |||
<IconButton | |||
disableRipple | |||
aria-label="open drawer" | |||
onClick={handleDrawerToggle} | |||
edge="start" | |||
color="secondary" | |||
sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor, ml: { xs: 0, lg: -2 } }} | |||
> | |||
{!open ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} | |||
</IconButton> | |||
<HeaderContent /> | |||
</Toolbar> | |||
); | |||
// app-bar params | |||
const appBar = { | |||
position: 'fixed', | |||
color: 'inherit', | |||
elevation: 0, | |||
sx: { | |||
borderBottom: `1px solid ${theme.palette.divider}` | |||
// boxShadow: theme.customShadows.z1 | |||
} | |||
}; | |||
return ( | |||
<> | |||
{!matchDownMD ? ( | |||
<AppBarStyled open={open} {...appBar}> | |||
{mainHeader} | |||
</AppBarStyled> | |||
) : ( | |||
<AppBar {...appBar}>{mainHeader}</AppBar> | |||
)} | |||
</> | |||
); | |||
}; | |||
Header.propTypes = { | |||
open: PropTypes.bool, | |||
handleDrawerToggle: PropTypes.func | |||
}; | |||
export default Header; |
@@ -0,0 +1,60 @@ | |||
import { useEffect, useState } from 'react'; | |||
import { Outlet } from 'react-router-dom'; | |||
import { useDispatch, useSelector } from 'react-redux'; | |||
// material-ui | |||
import { useTheme } from '@mui/material/styles'; | |||
import { Box, Toolbar, useMediaQuery } from '@mui/material'; | |||
// project import | |||
import Drawer from './Drawer'; | |||
import Header from './Header'; | |||
import navigation from 'menu-items'; | |||
import Breadcrumbs from 'components/@extended/Breadcrumbs'; | |||
// types | |||
import { openDrawer } from 'store/reducers/menu'; | |||
// ==============================|| MAIN LAYOUT ||============================== // | |||
const MainLayout = () => { | |||
const theme = useTheme(); | |||
const matchDownLG = useMediaQuery(theme.breakpoints.down('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%' }}> | |||
<Header open={open} handleDrawerToggle={handleDrawerToggle} /> | |||
<Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> | |||
<Box component="main" sx={{ width: '100%', flexGrow: 1, p: { xs: 2, sm: 3 } }}> | |||
<Toolbar /> | |||
<Breadcrumbs navigation={navigation} title /> | |||
<Outlet /> | |||
</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,39 @@ | |||
// assets | |||
import ThumbUpOffAltIcon from '@mui/icons-material/ThumbUpOffAlt'; | |||
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'; | |||
// icons | |||
const icons = { | |||
ThumbUpOffAltIcon, | |||
DescriptionOutlinedIcon | |||
}; | |||
// ==============================|| MENU ITEMS - DASHBOARD ||============================== // | |||
const appreciation = { | |||
id: 'appreciation-registration', | |||
title: 'Appreciation Registration', | |||
type: 'group', | |||
//ability:['SUPPRESS','REMINDER'], | |||
children: [ | |||
{ | |||
id: 'appreciation', | |||
title: 'Appreciation Record', | |||
type: 'item', | |||
url: '/appreciation', | |||
icon: icons.ThumbUpOffAltIcon, | |||
breadcrumbs: false, | |||
ability:['VIEW','APPRECIATION'] | |||
}, | |||
{ | |||
id: 'report', | |||
title: 'Reports', | |||
type: 'item', | |||
url: '/appreciation/report', | |||
icon: icons.DescriptionOutlinedIcon, | |||
breadcrumbs: false, | |||
ability:['GENERATE','REPORTS'] | |||
}, | |||
] | |||
}; | |||
export default appreciation; |
@@ -0,0 +1,51 @@ | |||
// assets | |||
import { DashboardOutlined } from '@ant-design/icons'; | |||
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; | |||
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; | |||
// icons | |||
const icons = { | |||
DashboardOutlined, | |||
CalendarMonthIcon, | |||
EmojiEventsIcon | |||
}; | |||
// ==============================|| MENU ITEMS - DASHBOARD ||============================== // | |||
const award = { | |||
id: 'award-management', | |||
title: 'Award Management', | |||
type: 'group', | |||
//ability:['SUPPRESS','REMINDER'], | |||
children: [ | |||
{ | |||
id: 'event', | |||
title: 'Event', | |||
type: 'item', | |||
url: '/event', | |||
icon: icons.CalendarMonthIcon, | |||
breadcrumbs: false, | |||
ability:['VIEW','EVENT'] | |||
}, | |||
{ | |||
id: 'application', | |||
title: 'Application', | |||
type: 'item', | |||
url: '/application', | |||
icon: icons.DashboardOutlined, | |||
breadcrumbs: false, | |||
ability:['VIEW','APPLICATION'] | |||
}, | |||
{ | |||
id: 'award', | |||
title: 'Award', | |||
type: 'item', | |||
url: '/award', | |||
icon: icons.EmojiEventsIcon, | |||
breadcrumbs: false, | |||
ability:['VIEW','AWARD'] | |||
}, | |||
] | |||
}; | |||
export default award; |
@@ -0,0 +1,33 @@ | |||
// assets | |||
import BoyIcon from '@mui/icons-material/Boy'; | |||
// icons | |||
const ClientIcon = () => { | |||
return ( | |||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginLeft: '-4px' }}> | |||
<BoyIcon fontSize="medium"/> | |||
</div> | |||
); | |||
}; | |||
// ==============================|| MENU ITEMS - DASHBOARD ||============================== // | |||
const client = { | |||
id: 'client-management', | |||
title: 'Client Management', | |||
type: 'group', | |||
//ability:['SUPPRESS','REMINDER'], | |||
children: [ | |||
{ | |||
id: 'Client', | |||
title: 'Client', | |||
type: 'item', | |||
url: '/client', | |||
icon: ClientIcon, | |||
breadcrumbs: false, | |||
ability:['VIEW','DASHBOARD'] | |||
}, | |||
] | |||
}; | |||
export default client; |
@@ -0,0 +1,61 @@ | |||
// assets | |||
import { SearchOutlined, DashboardOutlined, ContainerOutlined } from '@ant-design/icons'; | |||
import DescriptionIcon from '@mui/icons-material/Description'; | |||
import NotificationsActiveOutlinedIcon from '@mui/icons-material/NotificationsActiveOutlined'; | |||
import SpeedIcon from '@mui/icons-material/Speed'; | |||
import {FormattedMessage} from "react-intl"; | |||
// icons | |||
const icons = { | |||
SpeedIcon, | |||
DashboardOutlined, | |||
SearchOutlined, | |||
ContainerOutlined, | |||
DescriptionIcon, | |||
NotificationsActiveOutlinedIcon | |||
}; | |||
// ==============================|| MENU ITEMS - DASHBOARD ||============================== // | |||
const dashboard = { | |||
id: 'group-dashboard', | |||
type: 'group', | |||
children: [ | |||
/*{ | |||
id: 'dashboard', | |||
title: 'Dashboard', | |||
type: 'item', | |||
url: '/dashboard', | |||
icon: icons.DashboardOutlined, | |||
breadcrumbs: false | |||
},*/ | |||
{ | |||
id: 'lionerdashboard', | |||
title: <FormattedMessage id="Dashboard"/>, | |||
type: 'item', | |||
url: '/lionerDashboard', | |||
icon: icons.SpeedIcon, | |||
breadcrumbs: false, | |||
ability:['VIEW','DASHBOARD'] | |||
}, | |||
// { | |||
// id: 'maintainPage', | |||
// title: 'Reminder', | |||
// type: 'item', | |||
// url: '/reminder', | |||
// icon: icons.NotificationsActiveOutlinedIcon, | |||
// breadcrumbs: false, | |||
// ability:['VIEW','REMINDER'] | |||
// }, | |||
// { | |||
// id: 'searchPage', | |||
// title: 'Search Template', | |||
// type: 'item', | |||
// url: '/template', | |||
// icon: icons.DescriptionIcon, | |||
// breadcrumbs: false, | |||
// ability:['MAINTAIN','SEARCH_TEMPLATE'] | |||
// }, | |||
] | |||
}; | |||
export default dashboard; |
@@ -0,0 +1,19 @@ | |||
// project import | |||
//import pages from './pages'; | |||
import dashboard from './dashboard'; | |||
import setting from "./setting"; | |||
//import misc from "./misc"; | |||
import award from "./award"; | |||
import client from "./client"; | |||
import appreciation from "./appreciation"; | |||
//import utilities from './utilities'; | |||
//import support from './support'; | |||
// ==============================|| MENU ITEMS ||============================== // | |||
const menuItems = { | |||
items: [dashboard, client, setting] | |||
}; | |||
// pages, utilities, support, misc | |||
export default menuItems; |
@@ -0,0 +1,29 @@ | |||
// assets | |||
import { SettingOutlined, LoginOutlined, ProfileOutlined } from '@ant-design/icons'; | |||
// icons | |||
const icons = { | |||
SettingOutlined, | |||
LoginOutlined, | |||
ProfileOutlined | |||
}; | |||
// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== // | |||
const misc = { | |||
id: 'misc', | |||
title: 'Miscellaneous', | |||
type: 'group', | |||
children: [ | |||
{ | |||
id: 'logout', | |||
title: 'Logout', | |||
type: 'item', | |||
url: '/logout', | |||
icon: icons.LoginOutlined, | |||
breadcrumbs: false | |||
} | |||
] | |||
}; | |||
export default misc; |
@@ -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,164 @@ | |||
// assets | |||
import { | |||
SafetyCertificateOutlined, | |||
SettingOutlined, | |||
LoginOutlined, | |||
ProfileOutlined, | |||
UserOutlined, | |||
UsergroupAddOutlined, | |||
AppstoreOutlined, | |||
NotificationOutlined, | |||
TagOutlined, | |||
DatabaseOutlined, | |||
MenuUnfoldOutlined, | |||
FileSearchOutlined, | |||
MailOutlined, | |||
ApartmentOutlined | |||
} from '@ant-design/icons'; | |||
// icons | |||
const icons = { | |||
SettingOutlined, | |||
LoginOutlined, | |||
ProfileOutlined, | |||
SafetyCertificateOutlined, | |||
UserOutlined, | |||
UsergroupAddOutlined, | |||
AppstoreOutlined, | |||
NotificationOutlined, | |||
TagOutlined, | |||
DatabaseOutlined, | |||
MenuUnfoldOutlined, | |||
FileSearchOutlined, | |||
MailOutlined, | |||
ApartmentOutlined | |||
}; | |||
// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== // | |||
const setting = { | |||
id: 'settingMenu', | |||
title: 'System Administration', | |||
type: 'group', | |||
children: [ | |||
// { | |||
// id: 'category', | |||
// title: 'Category', | |||
// type: 'item', | |||
// url: '/category', | |||
// icon: icons.AppstoreOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MAINTAIN','CATEGORY'] | |||
// }, | |||
// { | |||
// id: 'tag', | |||
// title: 'Tag', | |||
// type: 'item', | |||
// url: '/tag', | |||
// icon: icons.TagOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MAINTAIN','TAG'] | |||
// }, | |||
// { | |||
// id: 'promotionChannel', | |||
// title: 'Promotion Channel', | |||
// type: 'item', | |||
// url: '/promotionChannel', | |||
// icon: icons.NotificationOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MAINTAIN','PROMOTION_CHANNEL'] | |||
// }, | |||
// { | |||
// id: 'division', | |||
// title: 'SBU / Division', | |||
// type: 'item', | |||
// url: '/division', | |||
// icon: icons.DatabaseOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MAINTAIN','DIVISION'] | |||
// }, | |||
// { | |||
// id: 'clientDepartment', | |||
// title: 'Client Department', | |||
// type: 'item', | |||
// url: '/clientDepartment', | |||
// icon: icons.ApartmentOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MAINTAIN','CLIENT_DEPARTMENT'] | |||
// }, | |||
{ | |||
id: 'userGroup', | |||
title: 'User Group', | |||
type: 'item', | |||
url: '/usergroupSearchview', | |||
icon: icons.UsergroupAddOutlined, | |||
breadcrumbs: false, | |||
ability:['MAINTAIN','USER_GROUP'] | |||
}, | |||
{ | |||
id: 'user', | |||
title: 'User', | |||
type: 'item', | |||
url: '/userSearchview', | |||
icon: icons.UserOutlined, | |||
breadcrumbs: false, | |||
ability:['MAINTAIN','USER'] | |||
}, | |||
// { | |||
// id: 'auditLog', | |||
// title: 'Audit Log', | |||
// type: 'item', | |||
// url: '/auditLog', | |||
// icon: icons.FileSearchOutlined, | |||
// breadcrumbs: false, | |||
// ability:['VIEW','AUDIT_LOG'] | |||
// }, | |||
{ | |||
id: 'loginLog', | |||
title: 'Login Log', | |||
type: 'item', | |||
url: '/loginLog', | |||
icon: icons.MenuUnfoldOutlined, | |||
breadcrumbs: false, | |||
ability:['VIEW','LOGIN_LOG'] | |||
}, | |||
{ | |||
id: 'passwordPolicy', | |||
title: 'Password Policy', | |||
type: 'item', | |||
url: '/passwordpolicy', | |||
icon: icons.SafetyCertificateOutlined, | |||
breadcrumbs: false, | |||
ability:['MANAGE','PASSWORD_POLICY'] | |||
}, | |||
{ | |||
id: 'setting', | |||
title: 'System Configuration', | |||
type: 'item', | |||
url: '/setting', | |||
icon: icons.SettingOutlined, | |||
breadcrumbs: false, | |||
ability:['MANAGE','SYSTEM_CONFIGURATION'] | |||
}, | |||
// { | |||
// id: 'generateReminder', | |||
// title: 'Generate Reminder', | |||
// type: 'item', | |||
// url: '/generateReminder', | |||
// icon: icons.SettingOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MANAGE','SYSTEM_CONFIGURATION'] | |||
// }, | |||
// { | |||
// id: 'emailConfig', | |||
// title: 'Email Configuration', | |||
// type: 'item', | |||
// url: '/emailConfig', | |||
// icon: icons.MailOutlined, | |||
// breadcrumbs: false, | |||
// ability:['MANAGE','SYSTEM_CONFIGURATION'] | |||
// }, | |||
] | |||
}; | |||
export default setting; |
@@ -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,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,51 @@ | |||
import PropTypes from 'prop-types'; | |||
// material-ui | |||
import { Box, Grid } from '@mui/material'; | |||
// project import | |||
import AuthCard from './AuthCard'; | |||
import Logo from 'components/Logo'; | |||
// assets | |||
import AuthBackground from 'assets/images/auth/AuthBackground'; | |||
// ==============================|| AUTHENTICATION - WRAPPER ||============================== // | |||
const AuthWrapper = ({ children }) => ( | |||
<Box sx={{ minHeight: '100vh' }}> | |||
<AuthBackground /> | |||
<Grid | |||
container | |||
direction="column" | |||
justifyContent="flex-end" | |||
sx={{ | |||
minHeight: '100vh' | |||
}} | |||
> | |||
<Grid item xs={12} sx={{ ml: 3, mt: 3 }}> | |||
<Logo /> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Grid | |||
item | |||
xs={12} | |||
container | |||
justifyContent="center" | |||
alignItems="center" | |||
sx={{ minHeight: { xs: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' } }} | |||
> | |||
<Grid item> | |||
<AuthCard>{children}</AuthCard> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
</Grid> | |||
</Box> | |||
); | |||
AuthWrapper.propTypes = { | |||
children: PropTypes.node | |||
}; | |||
export default AuthWrapper; |
@@ -0,0 +1,30 @@ | |||
//import { Link } from 'react-router-dom'; | |||
// material-ui | |||
import { Grid, Stack, Typography } from '@mui/material'; | |||
// project import | |||
import AuthLogin from './auth-forms/AuthLogin'; | |||
import AuthWrapper from './AuthWrapper'; | |||
// ================================|| LOGIN ||================================ // | |||
const Login = () => ( | |||
<AuthWrapper> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}> | |||
<Typography variant="h3">Login</Typography> | |||
{/*<Typography component={Link} to="/register" variant="body1" sx={{ textDecoration: 'none' }} color="primary">*/} | |||
{/* Don't have an account?*/} | |||
{/*</Typography>*/} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<AuthLogin /> | |||
</Grid> | |||
</Grid> | |||
</AuthWrapper> | |||
); | |||
export default Login; |
@@ -0,0 +1,30 @@ | |||
import { Link } from 'react-router-dom'; | |||
// material-ui | |||
import { Grid, Stack, Typography } from '@mui/material'; | |||
// project import | |||
import FirebaseRegister from './auth-forms/AuthRegister'; | |||
import AuthWrapper from './AuthWrapper'; | |||
// ================================|| REGISTER ||================================ // | |||
const Register = () => ( | |||
<AuthWrapper> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12}> | |||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}> | |||
<Typography variant="h3">Sign up</Typography> | |||
<Typography component={Link} to="/login" variant="body1" sx={{ textDecoration: 'none' }} color="primary"> | |||
Already have an account? | |||
</Typography> | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<FirebaseRegister /> | |||
</Grid> | |||
</Grid> | |||
</AuthWrapper> | |||
); | |||
export default Register; |
@@ -0,0 +1,282 @@ | |||
import React, {useState} from 'react'; | |||
import {useNavigate} from 'react-router-dom'; | |||
// material-ui | |||
import { | |||
Button, | |||
//Checkbox, | |||
//Divider, | |||
//FormControlLabel, | |||
FormHelperText, | |||
Grid, | |||
//Link, | |||
IconButton, | |||
InputAdornment, | |||
InputLabel, | |||
Stack, TextField, | |||
//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 {useDispatch} from "react-redux"; | |||
import {handleLogin} from "auth/index"; | |||
//import useJwt from "../../../auth/jwt/useJwt"; | |||
import axios from "axios"; | |||
import {apiPath} from "../../../auth/utils"; | |||
import {LOGIN_PATH} from "../../../utils/ApiPathConst"; | |||
import {LIONER_LOGIN_THEME} from "../../../themes/themeConst"; | |||
import {ThemeProvider} from "@emotion/react"; | |||
//import {AbilityContext} from "utils/context/Can" | |||
// ============================|| 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 [errors, setErrors] = useState({}); | |||
const handleClickShowPassword = () => { | |||
setShowPassword(!showPassword); | |||
}; | |||
const [userName, setUserName] = useState(""); | |||
const [userPassword, setUserPassword] = useState(""); | |||
const handleMouseDownPassword = (event) => { | |||
event.preventDefault(); | |||
}; | |||
const tryLogin = () => { | |||
const formErrors = {}; | |||
if (userName.length===0) { | |||
formErrors.loginError = 'Username are required.'; | |||
} | |||
if (userPassword.length===0){ | |||
formErrors.passwordError = 'Password are required.'; | |||
} | |||
setErrors(formErrors); | |||
if (Object.keys(formErrors).length === 0) { | |||
axios.post(`${apiPath}${LOGIN_PATH}`, | |||
{username: userName, password: userPassword}, | |||
) | |||
.then(async (response) => { | |||
const userData = { | |||
id: response.data.id, | |||
fullName: response.data.name, | |||
email: response.data.email, | |||
role: response.data.role, | |||
abilities: response.data.abilities, | |||
subDivisionId: response.data.subDivisionId, | |||
lotusNotesUser: response.data.lotusNotesUser | |||
//avatar: require('src/assets/images/users/avatar-3.png').default, | |||
} | |||
const data = { | |||
...userData, | |||
accessToken: response.data.accessToken, | |||
refreshToken: response.data.refreshToken | |||
} | |||
await dispatch(handleLogin(data)) | |||
//const abilities = response.data.abilities | |||
//ability.update(abilities) | |||
const lastPath = localStorage.getItem('lastVisitedPath'); | |||
await navigate(lastPath === null ? '/lionerDashboard' : lastPath); | |||
await window.location.reload(); | |||
await localStorage.removeItem('lastVisitedPath'); | |||
}) | |||
.catch(error => { | |||
const formErrors = {}; | |||
formErrors.loginError = " "; | |||
formErrors.passwordError = error.response.data.message; | |||
setErrors(formErrors); | |||
return false; | |||
}); | |||
/*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('/lionerDashboard'); | |||
}) | |||
.catch(error => { | |||
console.log(error); | |||
return Promise.reject(error); | |||
});*/ | |||
} | |||
} | |||
const onUserNameChange = (event) => { | |||
setUserName(event.target.value); | |||
} | |||
const onPasswordChange = (event) => { | |||
setUserPassword(event.target.value); | |||
} | |||
return ( | |||
<> | |||
<Formik | |||
initialValues={{ | |||
userName: 'test', | |||
email: '[email protected]', | |||
password: '123456', | |||
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); | |||
} | |||
}} | |||
> | |||
{({ handleBlur, handleSubmit, isSubmitting /*touched*/ }) => ( | |||
<form onSubmit={handleSubmit}> | |||
<ThemeProvider theme={LIONER_LOGIN_THEME}> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="username">User Name</InputLabel> | |||
<TextField | |||
id="username" | |||
name="username" | |||
onBlur={handleBlur} | |||
onChange={onUserNameChange} | |||
placeholder="Enter user name" | |||
fullWidth | |||
variant="outlined" | |||
error={!!errors.loginError} | |||
helperText={errors.loginError} | |||
/> | |||
{/*{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> | |||
<TextField | |||
fullWidth | |||
variant="outlined" | |||
error={!!errors.passwordError} | |||
helperText={errors.passwordError} | |||
id="-password-login" | |||
type={showPassword ? 'text' : 'password'} | |||
name="password" | |||
onBlur={handleBlur} | |||
onChange={onPasswordChange} | |||
InputProps={{ | |||
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} | |||
sx={{height: '50px'}} | |||
disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary"> | |||
Login | |||
</Button> | |||
</AnimateButton> | |||
</Grid> | |||
{/*<Grid item xs={12}>*/} | |||
{/* <Divider>*/} | |||
{/* <Typography variant="caption"> Login with</Typography>*/} | |||
{/* </Divider>*/} | |||
{/*</Grid>*/} | |||
{/*<Grid item xs={12}>*/} | |||
{/* <FirebaseSocial />*/} | |||
{/*</Grid>*/} | |||
</Grid> | |||
</ThemeProvider> | |||
</form> | |||
)} | |||
</Formik> | |||
</> | |||
); | |||
}; | |||
export default AuthLogin; |
@@ -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 { strengthColor, strengthIndicator } from 'utils/password-strength'; | |||
// assets | |||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | |||
// ============================|| FIREBASE - REGISTER ||============================ // | |||
const AuthRegister = () => { | |||
const [level, setLevel] = useState(); | |||
const [showPassword, setShowPassword] = useState(false); | |||
const handleClickShowPassword = () => { | |||
setShowPassword(!showPassword); | |||
}; | |||
const handleMouseDownPassword = (event) => { | |||
event.preventDefault(); | |||
}; | |||
const changePassword = (value) => { | |||
const temp = strengthIndicator(value); | |||
setLevel(strengthColor(temp)); | |||
}; | |||
useEffect(() => { | |||
changePassword(''); | |||
}, []); | |||
return ( | |||
<> | |||
<Formik | |||
initialValues={{ | |||
firstname: '', | |||
lastname: '', | |||
email: '', | |||
company: '', | |||
password: '', | |||
submit: null | |||
}} | |||
validationSchema={Yup.object().shape({ | |||
firstname: Yup.string().max(255).required('First Name is required'), | |||
lastname: Yup.string().max(255).required('Last Name is required'), | |||
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'), | |||
password: Yup.string().max(255).required('Password is required') | |||
})} | |||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => { | |||
try { | |||
setStatus({ success: false }); | |||
setSubmitting(false); | |||
} catch (err) { | |||
console.error(err); | |||
setStatus({ success: false }); | |||
setErrors({ submit: err.message }); | |||
setSubmitting(false); | |||
} | |||
}} | |||
> | |||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => ( | |||
<form noValidate onSubmit={handleSubmit}> | |||
<Grid container spacing={3}> | |||
<Grid item xs={12} md={6}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="firstname-signup">First Name*</InputLabel> | |||
<OutlinedInput | |||
id="firstname-login" | |||
type="firstname" | |||
value={values.firstname} | |||
name="firstname" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="John" | |||
fullWidth | |||
error={Boolean(touched.firstname && errors.firstname)} | |||
/> | |||
{touched.firstname && errors.firstname && ( | |||
<FormHelperText error id="helper-text-firstname-signup"> | |||
{errors.firstname} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12} md={6}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="lastname-signup">Last Name*</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.lastname && errors.lastname)} | |||
id="lastname-signup" | |||
type="lastname" | |||
value={values.lastname} | |||
name="lastname" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="Doe" | |||
inputProps={{}} | |||
/> | |||
{touched.lastname && errors.lastname && ( | |||
<FormHelperText error id="helper-text-lastname-signup"> | |||
{errors.lastname} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="company-signup">Company</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.company && errors.company)} | |||
id="company-signup" | |||
value={values.company} | |||
name="company" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="Demo Inc." | |||
inputProps={{}} | |||
/> | |||
{touched.company && errors.company && ( | |||
<FormHelperText error id="helper-text-company-signup"> | |||
{errors.company} | |||
</FormHelperText> | |||
)} | |||
</Stack> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<Stack spacing={1}> | |||
<InputLabel htmlFor="email-signup">Email Address*</InputLabel> | |||
<OutlinedInput | |||
fullWidth | |||
error={Boolean(touched.email && errors.email)} | |||
id="email-login" | |||
type="email" | |||
value={values.email} | |||
name="email" | |||
onBlur={handleBlur} | |||
onChange={handleChange} | |||
placeholder="[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,122 @@ | |||
// material-ui | |||
import * as React from 'react'; | |||
import { | |||
DataGrid, | |||
GridActionsCellItem, | |||
} from "@mui/x-data-grid"; | |||
import EditIcon from '@mui/icons-material/Edit'; | |||
import {useContext, useEffect, useRef} from "react"; | |||
import {useNavigate} from "react-router-dom"; | |||
import {CustomNoRowsOverlay} from "../../../utils/CommonFunction"; | |||
import AbilityContext from "../../../components/AbilityProvider"; | |||
// ==============================|| EVENT TABLE ||============================== // | |||
export default function ApplicationTable({recordList}) { | |||
const [rows, setRows] = React.useState(recordList); | |||
const [rowModesModel] = React.useState({}); | |||
const ability = useContext(AbilityContext); | |||
const navigate = useNavigate() | |||
const gridRef = useRef(null); | |||
useEffect(() => { | |||
setRows(recordList); | |||
}, [recordList]); | |||
const handleEditClick = (id) => () => { | |||
navigate('/application/maintain/'+ id); | |||
}; | |||
const columns = [ | |||
{ | |||
field: 'actions', | |||
type: 'actions', | |||
headerName: 'Actions', | |||
width: 100, | |||
cellClassName: 'actions', | |||
getActions: ({id}) => { | |||
return [ | |||
<GridActionsCellItem | |||
key="OutSave" | |||
icon={<EditIcon sx={{fontSize: 25}}/>} | |||
label="Edit" | |||
className="textPrimary" | |||
onClick={handleEditClick(id)} | |||
color="warning" | |||
disabled={!ability.can('VIEW','APPLICATION')} | |||
/>] | |||
}, | |||
}, | |||
{ | |||
id: 'applicationName', | |||
field: 'applicationName', | |||
headerName: 'Application', | |||
flex: 1, | |||
renderCell: (params) => { | |||
return ( | |||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||
{params.value} | |||
</div> | |||
); | |||
} | |||
}, | |||
{ | |||
id: 'subDivisions', | |||
field: 'subDivisions', | |||
//type: 'date', | |||
//sortable: false, | |||
headerName: 'Sub-Divisions', | |||
flex: 1, | |||
renderCell: (params) => { | |||
return ( | |||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', whiteSpace: 'normal', wordBreak: 'break-word'}}> | |||
{params.value} | |||
</div> | |||
); | |||
} | |||
}, | |||
{ | |||
id: 'tags', | |||
field: 'tags', | |||
//type: 'date', | |||
//sortable: false, | |||
headerName: 'Tags', | |||
flex: 0.8, | |||
}, | |||
{ | |||
id: 'responsibleOfficer', | |||
field: 'responsibleOfficer', | |||
headerName: 'Responsible Officer', | |||
flex: 0.7, | |||
}, | |||
]; | |||
return ( | |||
<div style={{/*height: 400,*/ width: '100%', }}> | |||
<DataGrid | |||
disableColumnMenu | |||
ref={gridRef} | |||
rows={rows} | |||
columns={columns} | |||
columnHeaderHeight={45} | |||
editMode="row" | |||
rowModesModel={rowModesModel} | |||
getRowHeight={() => 'auto'} | |||
initialState={{ | |||
pagination: { | |||
paginationModel: {page: 0, pageSize: 5}, | |||
}, | |||
}} | |||
slots={{ | |||
noRowsOverlay: () => ( | |||
CustomNoRowsOverlay() | |||
) | |||
}} | |||
pageSizeOptions={[5]} | |||
autoHeight | |||
/> | |||
</div> | |||
); | |||
} |