kelvinsuen преди 2 месеца
ревизия
cea45503eb
променени са 100 файла, в които са добавени 29668 реда и са изтрити 0 реда
  1. +2
    -0
      .env
  2. +42
    -0
      .env-cmdrc
  3. +91
    -0
      .eslintrc
  4. +111
    -0
      .gitignore
  5. +8
    -0
      .prettierrc
  6. +128
    -0
      CODE_OF_CONDUCT.md
  7. +21
    -0
      LICENSE
  8. +13
    -0
      README.md
  9. +12
    -0
      jsconfig.json
  10. +23581
    -0
      package-lock.json
  11. +130
    -0
      package.json
  12. +16
    -0
      public/favicon.svg
  13. +72
    -0
      public/index.html
  14. +88
    -0
      src/App.js
  15. +9
    -0
      src/App.test.js
  16. +69
    -0
      src/assets/images/auth/AuthBackground.js
  17. Двоични данни
      src/assets/images/display/celebration.png
  18. Двоични данни
      src/assets/images/display/doc.png
  19. +3
    -0
      src/assets/images/icons/facebook.svg
  20. +6
    -0
      src/assets/images/icons/google.svg
  21. +3
    -0
      src/assets/images/icons/twitter.svg
  22. Двоични данни
      src/assets/images/users/avatar-1.png
  23. Двоични данни
      src/assets/images/users/avatar-2.png
  24. Двоични данни
      src/assets/images/users/avatar-3.png
  25. Двоични данни
      src/assets/images/users/avatar-4.png
  26. Двоични данни
      src/assets/images/users/avatar-group.png
  27. Двоични данни
      src/assets/images/users/empty-avatar.png
  28. +39
    -0
      src/assets/style/imageOverlay.css
  29. +125
    -0
      src/assets/style/rankStyle.css
  30. +4
    -0
      src/assets/third-party/apex-chart.css
  31. +75
    -0
      src/assets/third-party/scrmhub.css
  32. +190
    -0
      src/auth/index.js
  33. +11
    -0
      src/auth/jwt/coreUseJwt.js
  34. +15
    -0
      src/auth/jwt/jwtDefaultConfig.js
  35. +111
    -0
      src/auth/jwt/jwtService.js
  36. +7
    -0
      src/auth/jwt/useJwt.js
  37. +17
    -0
      src/auth/jwtApplicationConfig.js
  38. +17
    -0
      src/auth/jwtConfig.js
  39. +33
    -0
      src/auth/utils.js
  40. +29
    -0
      src/components/@extended/AnimateButton.js
  41. +106
    -0
      src/components/@extended/Breadcrumbs.js
  42. +48
    -0
      src/components/@extended/Dot.js
  43. +62
    -0
      src/components/@extended/Transitions.js
  44. +17
    -0
      src/components/AbilityProvider.js
  45. +149
    -0
      src/components/AutoLogoutProvider.js
  46. +49
    -0
      src/components/I18nProvider.js
  47. +15
    -0
      src/components/Loadable.js
  48. +25
    -0
      src/components/Loader.js
  49. +49
    -0
      src/components/Logo/Logo.js
  50. +36
    -0
      src/components/Logo/index.js
  51. +105
    -0
      src/components/MainCard.js
  52. +70
    -0
      src/components/RefreshTokenProvider.js
  53. +27
    -0
      src/components/ScrollTop.js
  54. +46
    -0
      src/components/UploadProvider.js
  55. +61
    -0
      src/components/cards/AuthFooter.js
  56. +70
    -0
      src/components/cards/statistics/AnalyticEcommerce.js
  57. +65
    -0
      src/components/third-party/Highlighter.js
  58. +62
    -0
      src/components/third-party/SimpleBar.js
  59. +19
    -0
      src/config.js
  60. +52
    -0
      src/index.js
  61. +32
    -0
      src/layout/MainLayout/Drawer/DrawerContent/NavCard.js
  62. +97
    -0
      src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavGroup.js
  63. +148
    -0
      src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js
  64. +54
    -0
      src/layout/MainLayout/Drawer/DrawerContent/Navigation/index.js
  65. +21
    -0
      src/layout/MainLayout/Drawer/DrawerContent/index.js
  66. +15
    -0
      src/layout/MainLayout/Drawer/DrawerHeader/DrawerHeaderStyled.js
  67. +38
    -0
      src/layout/MainLayout/Drawer/DrawerHeader/index.js
  68. +47
    -0
      src/layout/MainLayout/Drawer/MiniDrawerStyled.js
  69. +66
    -0
      src/layout/MainLayout/Drawer/index.js
  70. +26
    -0
      src/layout/MainLayout/Header/AppBarStyled.js
  71. +142
    -0
      src/layout/MainLayout/Header/HeaderContent/LocaleSelector.js
  72. +102
    -0
      src/layout/MainLayout/Header/HeaderContent/MobileSection.js
  73. +279
    -0
      src/layout/MainLayout/Header/HeaderContent/Notification.js
  74. +304
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/ChangePasswordWindow.js
  75. +146
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js
  76. +56
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/SettingTab.js
  77. +169
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/index.js
  78. +139
    -0
      src/layout/MainLayout/Header/HeaderContent/Search.js
  79. +30
    -0
      src/layout/MainLayout/Header/HeaderContent/index.js
  80. +69
    -0
      src/layout/MainLayout/Header/index.js
  81. +60
    -0
      src/layout/MainLayout/index.js
  82. +11
    -0
      src/layout/MinimalLayout/index.js
  83. +39
    -0
      src/menu-items/appreciation.js
  84. +51
    -0
      src/menu-items/award.js
  85. +33
    -0
      src/menu-items/client.js
  86. +61
    -0
      src/menu-items/dashboard.js
  87. +19
    -0
      src/menu-items/index.js
  88. +29
    -0
      src/menu-items/misc.js
  89. +36
    -0
      src/menu-items/pages.js
  90. +164
    -0
      src/menu-items/setting.js
  91. +36
    -0
      src/menu-items/support.js
  92. +60
    -0
      src/menu-items/utilities.js
  93. +34
    -0
      src/pages/authentication/AuthCard.js
  94. +51
    -0
      src/pages/authentication/AuthWrapper.js
  95. +30
    -0
      src/pages/authentication/Login.js
  96. +30
    -0
      src/pages/authentication/Register.js
  97. +282
    -0
      src/pages/authentication/auth-forms/AuthLogin.js
  98. +263
    -0
      src/pages/authentication/auth-forms/AuthRegister.js
  99. +66
    -0
      src/pages/authentication/auth-forms/FirebaseSocial.js
  100. +122
    -0
      src/pages/client/ClientMaintainPage/ApplicationTable.js

+ 2
- 0
.env Целия файл

@@ -0,0 +1,2 @@
REACT_APP_VERSION = v1.0.1
GENERATE_SOURCEMAP = false

+ 42
- 0
.env-cmdrc Целия файл

@@ -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"
}
}

+ 91
- 0
.eslintrc Целия файл

@@ -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"
}
]
}
}

+ 111
- 0
.gitignore Целия файл

@@ -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

+ 8
- 0
.prettierrc Целия файл

@@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"printWidth": 140,
"singleQuote": true,
"trailingComma": "none",
"tabWidth": 2,
"useTabs": false
}

+ 128
- 0
CODE_OF_CONDUCT.md Целия файл

@@ -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.

+ 21
- 0
LICENSE Целия файл

@@ -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.

+ 13
- 0
README.md Целия файл

@@ -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
```

+ 12
- 0
jsconfig.json Целия файл

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"baseUrl": "src",
"paths": {
"@assets/*": ["assets/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

+ 23581
- 0
package-lock.json
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 130
- 0
package.json Целия файл

@@ -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"
}
}

+ 16
- 0
public/favicon.svg Целия файл

@@ -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>

+ 72
- 0
public/index.html Целия файл

@@ -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>

+ 88
- 0
src/App.js Целия файл

@@ -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;

+ 9
- 0
src/App.test.js Целия файл

@@ -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();
});

+ 69
- 0
src/assets/images/auth/AuthBackground.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


Двоични данни
src/assets/images/display/celebration.png Целия файл

Преди След
Ширина: 1200  |  Височина: 1200  |  Големина: 194 KiB

Двоични данни
src/assets/images/display/doc.png Целия файл

Преди След
Ширина: 64  |  Височина: 64  |  Големина: 858 B

+ 3
- 0
src/assets/images/icons/facebook.svg Целия файл

@@ -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>

+ 6
- 0
src/assets/images/icons/google.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>

+ 3
- 0
src/assets/images/icons/twitter.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>

Двоични данни
src/assets/images/users/avatar-1.png Целия файл

Преди След
Ширина: 64  |  Височина: 64  |  Големина: 9.3 KiB

Двоични данни
src/assets/images/users/avatar-2.png Целия файл

Преди След
Ширина: 64  |  Височина: 64  |  Големина: 9.4 KiB

Двоични данни
src/assets/images/users/avatar-3.png Целия файл

Преди След
Ширина: 64  |  Височина: 64  |  Големина: 6.9 KiB

Двоични данни
src/assets/images/users/avatar-4.png Целия файл

Преди След
Ширина: 64  |  Височина: 64  |  Големина: 9.0 KiB

Двоични данни
src/assets/images/users/avatar-group.png Целия файл

Преди След
Ширина: 113  |  Височина: 94  |  Големина: 8.6 KiB

Двоични данни
src/assets/images/users/empty-avatar.png Целия файл

Преди След
Ширина: 1280  |  Височина: 1280  |  Големина: 35 KiB

+ 39
- 0
src/assets/style/imageOverlay.css Целия файл

@@ -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;
}

+ 125
- 0
src/assets/style/rankStyle.css Целия файл

@@ -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;
}

+ 4
- 0
src/assets/third-party/apex-chart.css Целия файл

@@ -0,0 +1,4 @@
.apexcharts-legend-series .apexcharts-legend-marker {
left: -4px !important;
top: 2px !important;
}

+ 75
- 0
src/assets/third-party/scrmhub.css Целия файл

@@ -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%;
}

+ 190
- 0
src/auth/index.js Целия файл

@@ -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);
}
);
};

+ 11
- 0
src/auth/jwt/coreUseJwt.js Целия файл

@@ -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
}
}

+ 15
- 0
src/auth/jwt/jwtDefaultConfig.js Целия файл

@@ -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'
}

+ 111
- 0
src/auth/jwt/jwtService.js Целия файл

@@ -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()
})
}
}

+ 7
- 0
src/auth/jwt/useJwt.js Целия файл

@@ -0,0 +1,7 @@
// ** Core JWT Import
import createJwt from './coreUseJwt'
import jwtApplicationConfig from "../jwtApplicationConfig"

const { jwt } = createJwt(jwtApplicationConfig)

export default jwt

+ 17
- 0
src/auth/jwtApplicationConfig.js Целия файл

@@ -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'
}

+ 17
- 0
src/auth/jwtConfig.js Целия файл

@@ -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'
}

+ 33
- 0
src/auth/utils.js Целия файл

@@ -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'}
}

+ 29
- 0
src/components/@extended/AnimateButton.js Целия файл

@@ -0,0 +1,29 @@
import PropTypes from 'prop-types';

// third-party
import { motion } from 'framer-motion';

// ==============================|| ANIMATION BUTTON ||============================== //

export default function AnimateButton({ children, type }) {
switch (type) {
case 'rotate': // only available in paid version
case 'slide': // only available in paid version
case 'scale': // only available in paid version
default:
return (
<motion.div whileHover={{ scale: 1 }} whileTap={{ scale: 0.9 }}>
{children}
</motion.div>
);
}
}

AnimateButton.propTypes = {
children: PropTypes.node,
type: PropTypes.oneOf(['slide', 'scale', 'rotate'])
};

AnimateButton.defaultProps = {
type: 'scale'
};

+ 106
- 0
src/components/@extended/Breadcrumbs.js Целия файл

@@ -0,0 +1,106 @@
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';

// material-ui
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
import { Grid, Typography } from '@mui/material';

// project imports
import MainCard from '../MainCard';

// ==============================|| BREADCRUMBS ||============================== //

const Breadcrumbs = ({ navigation, title, ...others }) => {
const location = useLocation();
const [main, setMain] = useState();
const [item, setItem] = useState();

// set active item state
const getCollapse = (menu) => {
if (menu.children) {
menu.children.filter((collapse) => {
if (collapse.type && collapse.type === 'collapse') {
getCollapse(collapse);
} else if (collapse.type && collapse.type === 'item') {
if (location.pathname === collapse.url) {
setMain(menu);
setItem(collapse);
}
}
return false;
});
}
};

useEffect(() => {
navigation?.items?.map((menu) => {
if (menu.type && menu.type === 'group') {
getCollapse(menu);
}
return false;
});
});

// only used for component demo breadcrumbs
if (location.pathname === '/breadcrumbs') {
location.pathname = '/dashboard/analytics';
}

let mainContent;
let itemContent;
let breadcrumbContent = <Typography />;
let itemTitle = '';

// collapse item
if (main && main.type === 'collapse') {
mainContent = (
<Typography component={Link} to={document.location.pathname} variant="h6" sx={{ textDecoration: 'none' }} color="textSecondary">
{main.title}
</Typography>
);
}

// items
if (item && item.type === 'item') {
itemTitle = item.title;
itemContent = (
<Typography variant="subtitle1" color="textPrimary">
{itemTitle}
</Typography>
);

// main
if (item.breadcrumbs !== false) {
breadcrumbContent = (
<MainCard border={false} sx={{ mb: 3, bgcolor: 'transparent' }} {...others} content={false}>
<Grid container direction="column" justifyContent="flex-start" alignItems="flex-start" spacing={1}>
<Grid item>
<MuiBreadcrumbs aria-label="breadcrumb">
<Typography component={Link} to="/" color="textSecondary" variant="h6" sx={{ textDecoration: 'none' }}>
Home
</Typography>
{mainContent}
{itemContent}
</MuiBreadcrumbs>
</Grid>
{title && (
<Grid item sx={{ mt: 2 }}>
<Typography variant="h5">{item.title}</Typography>
</Grid>
)}
</Grid>
</MainCard>
);
}
}

return breadcrumbContent;
};

Breadcrumbs.propTypes = {
navigation: PropTypes.object,
title: PropTypes.bool
};

export default Breadcrumbs;

+ 48
- 0
src/components/@extended/Dot.js Целия файл

@@ -0,0 +1,48 @@
import PropTypes from 'prop-types';

// material-ui
import { useTheme } from '@mui/material/styles';
import { Box } from '@mui/material';

const Dot = ({ color, size }) => {
const theme = useTheme();
let main;
switch (color) {
case 'secondary':
main = theme.palette.secondary.main;
break;
case 'error':
main = theme.palette.error.main;
break;
case 'warning':
main = theme.palette.warning.main;
break;
case 'info':
main = theme.palette.info.main;
break;
case 'success':
main = theme.palette.success.main;
break;
case 'primary':
default:
main = theme.palette.primary.main;
}

return (
<Box
sx={{
width: size || 8,
height: size || 8,
borderRadius: '50%',
bgcolor: main
}}
/>
);
};

Dot.propTypes = {
color: PropTypes.string,
size: PropTypes.number
};

export default Dot;

+ 62
- 0
src/components/@extended/Transitions.js Целия файл

@@ -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;

+ 17
- 0
src/components/AbilityProvider.js Целия файл

@@ -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;


+ 149
- 0
src/components/AutoLogoutProvider.js Целия файл

@@ -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 };

+ 49
- 0
src/components/I18nProvider.js Целия файл

@@ -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;

+ 15
- 0
src/components/Loadable.js Целия файл

@@ -0,0 +1,15 @@
import { Suspense } from 'react';

// project import
import Loader from './Loader';

// ==============================|| LOADABLE - LAZY LOADING ||============================== //

const Loadable = (Component) => (props) =>
(
<Suspense fallback={<Loader />}>
<Component {...props} />
</Suspense>
);

export default Loadable;

+ 25
- 0
src/components/Loader.js Целия файл

@@ -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;

+ 49
- 0
src/components/Logo/Logo.js Целия файл

@@ -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;

+ 36
- 0
src/components/Logo/index.js Целия файл

@@ -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;

+ 105
- 0
src/components/MainCard.js Целия файл

@@ -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;

+ 70
- 0
src/components/RefreshTokenProvider.js Целия файл

@@ -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 };

+ 27
- 0
src/components/ScrollTop.js Целия файл

@@ -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;

+ 46
- 0
src/components/UploadProvider.js Целия файл

@@ -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;

+ 61
- 0
src/components/cards/AuthFooter.js Целия файл

@@ -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">
&copy; Mantis React Dashboard Template By&nbsp;
<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;

+ 70
- 0
src/components/cards/statistics/AnalyticEcommerce.js Целия файл

@@ -0,0 +1,70 @@
import PropTypes from 'prop-types';

// material-ui
import { Box, Chip, Grid, Stack, Typography } from '@mui/material';

// project import
import MainCard from 'components/MainCard';

// assets
import { RiseOutlined, FallOutlined } from '@ant-design/icons';

// ==============================|| STATISTICS - ECOMMERCE CARD ||============================== //

const AnalyticEcommerce = ({ color, title, count, percentage, isLoss, extra }) => (
<MainCard contentSX={{ p: 2.25 }}>
<Stack spacing={0.5}>
<Typography variant="h6" color="textSecondary">
{title}
</Typography>
<Grid container alignItems="center">
<Grid item>
<Typography variant="h4" color="inherit">
{count}
</Typography>
</Grid>
{percentage && (
<Grid item>
<Chip
variant="combined"
color={color}
icon={
<>
{!isLoss && <RiseOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />}
{isLoss && <FallOutlined style={{ fontSize: '0.75rem', color: 'inherit' }} />}
</>
}
label={`${percentage}%`}
sx={{ ml: 1.25, pl: 1 }}
size="small"
/>
</Grid>
)}
</Grid>
</Stack>
<Box sx={{ pt: 2.25 }}>
<Typography variant="caption" color="textSecondary">
You made an extra{' '}
<Typography component="span" variant="caption" sx={{ color: `${color || 'primary'}.main` }}>
{extra}
</Typography>{' '}
this year
</Typography>
</Box>
</MainCard>
);

AnalyticEcommerce.propTypes = {
color: PropTypes.string,
title: PropTypes.string,
count: PropTypes.string,
percentage: PropTypes.number,
isLoss: PropTypes.bool,
extra: PropTypes.oneOfType([PropTypes.node, PropTypes.string])
};

AnalyticEcommerce.defaultProps = {
color: 'primary'
};

export default AnalyticEcommerce;

+ 65
- 0
src/components/third-party/Highlighter.js Целия файл

@@ -0,0 +1,65 @@
import PropTypes from 'prop-types';
import { useState } from 'react';

// material-ui
import { Box, CardActions, Collapse, Divider, IconButton, Tooltip } from '@mui/material';

// third-party
import { CopyToClipboard } from 'react-copy-to-clipboard';
import reactElementToJSXString from 'react-element-to-jsx-string';

// project import
import SyntaxHighlight from 'utils/SyntaxHighlight';

// assets
import { CodeOutlined, CopyOutlined } from '@ant-design/icons';

// ==============================|| CLIPBOARD & HIGHLIGHTER ||============================== //

const Highlighter = ({ children }) => {
const [highlight, setHighlight] = useState(false);

return (
<Box sx={{ position: 'relative' }}>
<CardActions sx={{ justifyContent: 'flex-end', p: 1, mb: highlight ? 1 : 0 }}>
<Box sx={{ display: 'flex', position: 'inherit', right: 0, top: 6 }}>
<CopyToClipboard text={reactElementToJSXString(children, { showFunctions: true, maxInlineAttributesLineLength: 100 })}>
<Tooltip title="Copy the source" placement="top-end">
<IconButton color="secondary" size="small" sx={{ fontSize: '0.875rem' }}>
<CopyOutlined />
</IconButton>
</Tooltip>
</CopyToClipboard>
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} />
<Tooltip title="Show the source" placement="top-end">
<IconButton
sx={{ fontSize: '0.875rem' }}
size="small"
color={highlight ? 'primary' : 'secondary'}
onClick={() => setHighlight(!highlight)}
>
<CodeOutlined />
</IconButton>
</Tooltip>
</Box>
</CardActions>
<Collapse in={highlight}>
{highlight && (
<SyntaxHighlight>
{reactElementToJSXString(children, {
showFunctions: true,
showDefaultProps: false,
maxInlineAttributesLineLength: 100
})}
</SyntaxHighlight>
)}
</Collapse>
</Box>
);
};

Highlighter.propTypes = {
children: PropTypes.node
};

export default Highlighter;

+ 62
- 0
src/components/third-party/SimpleBar.js Целия файл

@@ -0,0 +1,62 @@
import PropTypes from 'prop-types';

// material-ui
import { alpha, styled } from '@mui/material/styles';
import { Box } from '@mui/material';

// third-party
import SimpleBar from 'simplebar-react';
import { BrowserView, MobileView } from 'react-device-detect';

// root style
const RootStyle = styled(BrowserView)({
flexGrow: 1,
height: '100%',
overflow: 'hidden'
});

// scroll bar wrapper
const SimpleBarStyle = styled(SimpleBar)(({ theme }) => ({
maxHeight: '100%',
'& .simplebar-scrollbar': {
'&:before': {
backgroundColor: alpha(theme.palette.grey[500], 0.48)
},
'&.simplebar-visible:before': {
opacity: 1
}
},
'& .simplebar-track.simplebar-vertical': {
width: 10
},
'& .simplebar-track.simplebar-horizontal .simplebar-scrollbar': {
height: 6
},
'& .simplebar-mask': {
zIndex: 'inherit'
}
}));

// ==============================|| SIMPLE SCROLL BAR ||============================== //

export default function SimpleBarScroll({ children, sx, ...other }) {
return (
<>
<RootStyle>
<SimpleBarStyle timeout={500} clickOnTrack={false} sx={sx} {...other}>
{children}
</SimpleBarStyle>
</RootStyle>
<MobileView>
<Box sx={{ overflowX: 'auto', ...sx }} {...other}>
{children}
</Box>
</MobileView>
</>
);
}

SimpleBarScroll.propTypes = {
children: PropTypes.node,
sx: PropTypes.object
};

+ 19
- 0
src/config.js Целия файл

@@ -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';

+ 52
- 0
src/index.js Целия файл

@@ -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();

+ 32
- 0
src/layout/MainLayout/Drawer/DrawerContent/NavCard.js Целия файл

@@ -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;

+ 97
- 0
src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavGroup.js Целия файл

@@ -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;

+ 148
- 0
src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js Целия файл

@@ -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;

+ 54
- 0
src/layout/MainLayout/Drawer/DrawerContent/Navigation/index.js Целия файл

@@ -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;

+ 21
- 0
src/layout/MainLayout/Drawer/DrawerContent/index.js Целия файл

@@ -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;

+ 15
- 0
src/layout/MainLayout/Drawer/DrawerHeader/DrawerHeaderStyled.js Целия файл

@@ -0,0 +1,15 @@
// material-ui
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';

// ==============================|| DRAWER HEADER - STYLED ||============================== //

const DrawerHeaderStyled = styled(Box, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
...theme.mixins.toolbar,
display: 'flex',
alignItems: 'center',
justifyContent: open ? 'flex-start' : 'center',
paddingLeft: theme.spacing(open ? 3 : 0)
}));

export default DrawerHeaderStyled;

+ 38
- 0
src/layout/MainLayout/Drawer/DrawerHeader/index.js Целия файл

@@ -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;

+ 47
- 0
src/layout/MainLayout/Drawer/MiniDrawerStyled.js Целия файл

@@ -0,0 +1,47 @@
// material-ui
import { styled } from '@mui/material/styles';
import Drawer from '@mui/material/Drawer';

// project import
import { drawerWidth } from 'config';

const openedMixin = (theme) => ({
width: drawerWidth,
borderRight: `1px solid ${theme.palette.divider}`,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
}),
overflowX: 'hidden',
boxShadow: 'none'
});

const closedMixin = (theme) => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
overflowX: 'hidden',
width: 0,
borderRight: 'none',
boxShadow: theme.customShadows.z1
});

// ==============================|| DRAWER - MINI STYLED ||============================== //

const MiniDrawerStyled = styled(Drawer, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme)
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme)
})
}));

export default MiniDrawerStyled;

+ 66
- 0
src/layout/MainLayout/Drawer/index.js Целия файл

@@ -0,0 +1,66 @@
import PropTypes from 'prop-types';
import { useMemo } from 'react';

// material-ui
import { useTheme } from '@mui/material/styles';
import { Box, Drawer, useMediaQuery } from '@mui/material';

// project import
import DrawerHeader from './DrawerHeader';
import DrawerContent from './DrawerContent';
import MiniDrawerStyled from './MiniDrawerStyled';
import { drawerWidth } from 'config';

// ==============================|| MAIN LAYOUT - DRAWER ||============================== //

const MainDrawer = ({ open, handleDrawerToggle, window }) => {
const theme = useTheme();
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg'));

// responsive drawer container
const container = window !== undefined ? () => window().document.body : undefined;

// header content
const drawerContent = useMemo(() => <DrawerContent />, []);
const drawerHeader = useMemo(() => <DrawerHeader open={open} />, [open]);

return (
<Box component="nav" sx={{ flexShrink: { md: 0 }, zIndex: 1300 }} aria-label="mailbox folders">
{!matchDownMD ? (
<MiniDrawerStyled variant="permanent" open={open}>
{drawerHeader}
{drawerContent}
</MiniDrawerStyled>
) : (
<Drawer
container={container}
variant="temporary"
open={open}
onClose={handleDrawerToggle}
ModalProps={{ keepMounted: true }}
sx={{
display: { xs: 'block', lg: 'none' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
borderRight: `1px solid ${theme.palette.divider}`,
backgroundImage: 'none',
boxShadow: 'inherit'
}
}}
>
{open && drawerHeader}
{open && drawerContent}
</Drawer>
)}
</Box>
);
};

MainDrawer.propTypes = {
open: PropTypes.bool,
handleDrawerToggle: PropTypes.func,
window: PropTypes.object
};

export default MainDrawer;

+ 26
- 0
src/layout/MainLayout/Header/AppBarStyled.js Целия файл

@@ -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;

+ 142
- 0
src/layout/MainLayout/Header/HeaderContent/LocaleSelector.js Целия файл

@@ -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;

+ 102
- 0
src/layout/MainLayout/Header/HeaderContent/MobileSection.js Целия файл

@@ -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;

+ 279
- 0
src/layout/MainLayout/Header/HeaderContent/Notification.js Целия файл

@@ -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&apos;s{' '}
<Typography component="span" variant="subtitle1">
Cristina danny&apos;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 &nbsp;
<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;

+ 304
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/ChangePasswordWindow.js Целия файл

@@ -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>
)
}

+ 146
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js Целия файл

@@ -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;

+ 56
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/SettingTab.js Целия файл

@@ -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;

+ 169
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/index.js Целия файл

@@ -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;

+ 139
- 0
src/layout/MainLayout/Header/HeaderContent/Search.js Целия файл

@@ -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;

+ 30
- 0
src/layout/MainLayout/Header/HeaderContent/index.js Целия файл

@@ -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;

+ 69
- 0
src/layout/MainLayout/Header/index.js Целия файл

@@ -0,0 +1,69 @@
import PropTypes from 'prop-types';

// material-ui
import { useTheme } from '@mui/material/styles';
import { AppBar, IconButton, Toolbar, useMediaQuery } from '@mui/material';

// project import
import AppBarStyled from './AppBarStyled';
import HeaderContent from './HeaderContent';

// assets
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';

// ==============================|| MAIN LAYOUT - HEADER ||============================== //

const Header = ({ open, handleDrawerToggle }) => {
const theme = useTheme();
const matchDownMD = useMediaQuery(theme.breakpoints.down('lg'));

const iconBackColor = 'grey.100';
const iconBackColorOpen = 'grey.200';

// common header
const mainHeader = (
<Toolbar>
<IconButton
disableRipple
aria-label="open drawer"
onClick={handleDrawerToggle}
edge="start"
color="secondary"
sx={{ color: 'text.primary', bgcolor: open ? iconBackColorOpen : iconBackColor, ml: { xs: 0, lg: -2 } }}
>
{!open ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</IconButton>
<HeaderContent />
</Toolbar>
);

// app-bar params
const appBar = {
position: 'fixed',
color: 'inherit',
elevation: 0,
sx: {
borderBottom: `1px solid ${theme.palette.divider}`
// boxShadow: theme.customShadows.z1
}
};

return (
<>
{!matchDownMD ? (
<AppBarStyled open={open} {...appBar}>
{mainHeader}
</AppBarStyled>
) : (
<AppBar {...appBar}>{mainHeader}</AppBar>
)}
</>
);
};

Header.propTypes = {
open: PropTypes.bool,
handleDrawerToggle: PropTypes.func
};

export default Header;

+ 60
- 0
src/layout/MainLayout/index.js Целия файл

@@ -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;

+ 11
- 0
src/layout/MinimalLayout/index.js Целия файл

@@ -0,0 +1,11 @@
import { Outlet } from 'react-router-dom';

// ==============================|| MINIMAL LAYOUT ||============================== //

const MinimalLayout = () => (
<>
<Outlet />
</>
);

export default MinimalLayout;

+ 39
- 0
src/menu-items/appreciation.js Целия файл

@@ -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;

+ 51
- 0
src/menu-items/award.js Целия файл

@@ -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;

+ 33
- 0
src/menu-items/client.js Целия файл

@@ -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;

+ 61
- 0
src/menu-items/dashboard.js Целия файл

@@ -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;

+ 19
- 0
src/menu-items/index.js Целия файл

@@ -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;

+ 29
- 0
src/menu-items/misc.js Целия файл

@@ -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;

+ 36
- 0
src/menu-items/pages.js Целия файл

@@ -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;

+ 164
- 0
src/menu-items/setting.js Целия файл

@@ -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;

+ 36
- 0
src/menu-items/support.js Целия файл

@@ -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;

+ 60
- 0
src/menu-items/utilities.js Целия файл

@@ -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;

+ 34
- 0
src/pages/authentication/AuthCard.js Целия файл

@@ -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;

+ 51
- 0
src/pages/authentication/AuthWrapper.js Целия файл

@@ -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;

+ 30
- 0
src/pages/authentication/Login.js Целия файл

@@ -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&apos;t have an account?*/}
{/*</Typography>*/}
</Stack>
</Grid>
<Grid item xs={12}>
<AuthLogin />
</Grid>
</Grid>
</AuthWrapper>
);

export default Login;

+ 30
- 0
src/pages/authentication/Register.js Целия файл

@@ -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;

+ 282
- 0
src/pages/authentication/auth-forms/AuthLogin.js Целия файл

@@ -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;

+ 263
- 0
src/pages/authentication/auth-forms/AuthRegister.js Целия файл

@@ -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 &nbsp;
<Link variant="subtitle2" component={RouterLink} to="#">
Terms of Service
</Link>
&nbsp; and &nbsp;
<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;

+ 66
- 0
src/pages/authentication/auth-forms/FirebaseSocial.js Целия файл

@@ -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;

+ 122
- 0
src/pages/client/ClientMaintainPage/ApplicationTable.js Целия файл

@@ -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>
);
}

Някои файлове не бяха показани, защото твърде много файлове са промени

Зареждане…
Отказ
Запис