Bladeren bron

first commit

master
Alex Cheung 2 jaren geleden
commit
ad54e1d196
100 gewijzigde bestanden met toevoegingen van 27724 en 0 verwijderingen
  1. +2
    -0
      .env
  2. +90
    -0
      .eslintrc
  3. +111
    -0
      .gitignore
  4. +8
    -0
      .prettierrc
  5. +128
    -0
      CODE_OF_CONDUCT.md
  6. +21
    -0
      LICENSE
  7. +1
    -0
      README.md
  8. BIN
      build2.zip
  9. +9
    -0
      jsconfig.json
  10. +21254
    -0
      package-lock.json
  11. +93
    -0
      package.json
  12. +16
    -0
      public/favicon.svg
  13. +72
    -0
      public/index.html
  14. +18
    -0
      src/App.js
  15. +9
    -0
      src/App.test.js
  16. BIN
      src/assets/images/BHK_logo_rgb_zh-hk.png
  17. +38
    -0
      src/assets/images/auth/AuthBackground.js
  18. BIN
      src/assets/images/banner.jpg
  19. BIN
      src/assets/images/bg_ml.jpg
  20. BIN
      src/assets/images/hero-printing.png
  21. +3
    -0
      src/assets/images/icons/facebook.svg
  22. +6
    -0
      src/assets/images/icons/google.svg
  23. BIN
      src/assets/images/icons/icon_iAmSmart.png
  24. +3
    -0
      src/assets/images/icons/twitter.svg
  25. BIN
      src/assets/images/users/avatar-1.png
  26. BIN
      src/assets/images/users/avatar-2.png
  27. BIN
      src/assets/images/users/avatar-3.png
  28. BIN
      src/assets/images/users/avatar-4.png
  29. BIN
      src/assets/images/users/avatar-group.png
  30. +3
    -0
      src/assets/style/loginStyles.css
  31. +84
    -0
      src/assets/style/navbarStyles.css
  32. +4
    -0
      src/assets/style/styles.css
  33. +4
    -0
      src/assets/third-party/apex-chart.css
  34. +92
    -0
      src/auth/index.js
  35. +11
    -0
      src/auth/jwt/coreUseJwt.js
  36. +15
    -0
      src/auth/jwt/jwtDefaultConfig.js
  37. +111
    -0
      src/auth/jwt/jwtService.js
  38. +6
    -0
      src/auth/jwt/useJwt.js
  39. +17
    -0
      src/auth/jwtApplicationConfig.js
  40. +17
    -0
      src/auth/jwtConfig.js
  41. +33
    -0
      src/auth/utils.js
  42. +29
    -0
      src/components/@extended/AnimateButton.js
  43. +106
    -0
      src/components/@extended/Breadcrumbs.js
  44. +48
    -0
      src/components/@extended/Dot.js
  45. +62
    -0
      src/components/@extended/Transitions.js
  46. +15
    -0
      src/components/Loadable.js
  47. +25
    -0
      src/components/Loader.js
  48. +51
    -0
      src/components/Logo/Logo.js
  49. +36
    -0
      src/components/Logo/index.js
  50. +103
    -0
      src/components/MainCard.js
  51. +51
    -0
      src/components/MobileLogo/MobileLogo.js
  52. +36
    -0
      src/components/MobileLogo/index.js
  53. +26
    -0
      src/components/ScrollTop.js
  54. +61
    -0
      src/components/cards/AuthFooter.js
  55. +70
    -0
      src/components/cards/statistics/AnalyticEcommerce.js
  56. +65
    -0
      src/components/third-party/Highlighter.js
  57. +62
    -0
      src/components/third-party/SimpleBar.js
  58. +19
    -0
      src/config.js
  59. +38
    -0
      src/index.js
  60. +32
    -0
      src/layout/MainLayout/Drawer/DrawerContent/NavCard.js
  61. +59
    -0
      src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavGroup.js
  62. +142
    -0
      src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js
  63. +27
    -0
      src/layout/MainLayout/Drawer/DrawerContent/Navigation/index.js
  64. +21
    -0
      src/layout/MainLayout/Drawer/DrawerContent/index.js
  65. +15
    -0
      src/layout/MainLayout/Drawer/DrawerHeader/DrawerHeaderStyled.js
  66. +38
    -0
      src/layout/MainLayout/Drawer/DrawerHeader/index.js
  67. +47
    -0
      src/layout/MainLayout/Drawer/MiniDrawerStyled.js
  68. +66
    -0
      src/layout/MainLayout/Drawer/index.js
  69. +28
    -0
      src/layout/MainLayout/Header/AppBarStyled.js
  70. +102
    -0
      src/layout/MainLayout/Header/HeaderContent/MobileSection.js
  71. +279
    -0
      src/layout/MainLayout/Header/HeaderContent/Notification.js
  72. +62
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js
  73. +56
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/SettingTab.js
  74. +217
    -0
      src/layout/MainLayout/Header/HeaderContent/Profile/index.js
  75. +30
    -0
      src/layout/MainLayout/Header/HeaderContent/Search.js
  76. +45
    -0
      src/layout/MainLayout/Header/HeaderContent/index.js
  77. +151
    -0
      src/layout/MainLayout/Header/index.js
  78. +68
    -0
      src/layout/MainLayout/index.js
  79. +11
    -0
      src/layout/MinimalLayout/index.js
  80. +35
    -0
      src/menu-items/dashboard.js
  81. +14
    -0
      src/menu-items/index.js
  82. +36
    -0
      src/menu-items/pages.js
  83. +36
    -0
      src/menu-items/support.js
  84. +60
    -0
      src/menu-items/utilities.js
  85. +359
    -0
      src/pages/arsdashboard/index.js
  86. +34
    -0
      src/pages/authentication/AuthCard.js
  87. +34
    -0
      src/pages/authentication/AuthCardCustom.js
  88. +73
    -0
      src/pages/authentication/AuthWrapper.js
  89. +55
    -0
      src/pages/authentication/AuthWrapperCustom.js
  90. +29
    -0
      src/pages/authentication/Login.js
  91. +198
    -0
      src/pages/authentication/Register.js
  92. +76
    -0
      src/pages/authentication/RegisterCustom.js
  93. +225
    -0
      src/pages/authentication/auth-forms/AuthLogin.js
  94. +220
    -0
      src/pages/authentication/auth-forms/AuthLoginCustom.js
  95. +263
    -0
      src/pages/authentication/auth-forms/AuthRegister.js
  96. +1009
    -0
      src/pages/authentication/auth-forms/CustomFormWizard.js
  97. +66
    -0
      src/pages/authentication/auth-forms/FirebaseSocial.js
  98. +24
    -0
      src/pages/components-overview/AntIcons.js
  99. +141
    -0
      src/pages/components-overview/Color.js
  100. +59
    -0
      src/pages/components-overview/ComponentSkeleton.js

+ 2
- 0
.env Bestand weergeven

@@ -0,0 +1,2 @@
REACT_APP_VERSION = v0.5.0
GENERATE_SOURCEMAP = false

+ 90
- 0
.eslintrc Bestand weergeven

@@ -0,0 +1,90 @@
{
"root": true,
"env": {
"browser": true,
"es2021": true
},
"extends": [
"prettier",
"plugin:react/jsx-runtime",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended",
"eslint:recommended",
"plugin:react/recommended"
],
"settings": {
"react": {
"createClass": "createReactClass", // Regex for Component Factory to use,
// default to "createReactClass"
"pragma": "React", // Pragma to use, default to "React"
"fragment": "Fragment", // Fragment to use (may be a property of <pragma>), default to "Fragment"
"version": "detect", // React version. "detect" automatically picks the version you have installed.
// You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
// It will default to "latest" and warn if missing, and to "detect" in the future
"flowVersion": "0.53" // Flow version
},
"import/resolver": {
"node": {
"moduleDirectory": ["node_modules", "src/"]
}
}
},
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"impliedStrict": true,
"jsx": true
},
"ecmaVersion": 12
},
"plugins": ["prettier", "react", "react-hooks"],
"rules": {
"no-debugger": "error",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"react/react-in-jsx-scope": "off",
"no-undef": "off",
"react/display-name": "off",
"react/jsx-filename-extension": "off",
"no-param-reassign": "off",
"react/prop-types": 1,
"react/require-default-props": "off",
"react/no-array-index-key": "off",
"react/jsx-props-no-spreading": "off",
"react/forbid-prop-types": "off",
"import/order": "off",
"import/no-cycle": "off",
"no-console": "off",
"jsx-a11y/anchor-is-valid": "off",
"prefer-destructuring": "off",
"no-shadow": "off",
"import/no-named-as-default": "off",
"import/no-extraneous-dependencies": "off",
"jsx-a11y/no-autofocus": "off",
"no-restricted-imports": [
"error",
{
"patterns": ["@mui/*/*/*", "!@mui/material/test-utils/*"]
}
],
"no-unused-vars": [
"error",
{
"ignoreRestSiblings": false
}
],
"prettier/prettier": [
"warn",
{
"bracketSpacing": true,
"printWidth": 140,
"singleQuote": true,
"trailingComma": "none",
"tabWidth": 2,
"useTabs": false,
"endOfLine": "auto"
}
]
}
}

+ 111
- 0
.gitignore Bestand weergeven

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

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

+ 128
- 0
CODE_OF_CONDUCT.md Bestand weergeven

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

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

+ 1
- 0
README.md Bestand weergeven

@@ -0,0 +1 @@
2fi PNSPS frontend temp

BIN
build2.zip Bestand weergeven


+ 9
- 0
jsconfig.json Bestand weergeven

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

+ 21254
- 0
package-lock.json
Diff onderdrukt omdat het te groot bestand
Bestand weergeven


+ 93
- 0
package.json Bestand weergeven

@@ -0,0 +1,93 @@
{
"name": "pnsps-react",
"version": "1.1.2",
"private": true,
"homepage": ".",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",
"@casl/react": "^3.1.0",
"@emotion/cache": "^11.10.3",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@mui/icons-material": "^5.14.3",
"@mui/lab": "^5.0.0-alpha.100",
"@mui/material": "^5.10.6",
"@reduxjs/toolkit": "^1.8.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"apexcharts": "^3.35.5",
"axios": "^1.4.0",
"formik": "^2.2.9",
"framer-motion": "^7.3.6",
"history": "^5.3.0",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-apexcharts": "^1.4.0",
"react-copy-to-clipboard": "^5.1.0",
"react-device-detect": "^2.2.2",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-element-to-jsx-string": "^15.0.0",
"react-number-format": "^4.9.4",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^8.0.4",
"react-router": "^6.4.1",
"react-router-dom": "^6.4.1",
"react-scripts": "^5.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-window": "^1.8.7",
"redux": "^4.2.0",
"simplebar": "^5.3.8",
"simplebar-react": "^2.4.1",
"typescript": "4.8.3",
"web-vitals": "^3.0.2",
"yup": "^0.32.11"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"babel": {
"presets": [
"@babel/preset-react"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/eslint-parser": "^7.21.3",
"eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^2.8.7"
}
}

+ 16
- 0
public/favicon.svg Bestand weergeven

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

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#1f1f1f" />
<meta name="title" content="PNSPS" />
<meta
name="description"
content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license."
/>
<meta
name="keywords"
content="react dashboard, react admin, react redux dashboard, ant design template, saas admin, free react dashboard"
/>
<meta name="author" content="CodedThemes" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- Open Graph / Facebook -->
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://mantisdashboard.io/" />
<meta property="og:site_name" content="mantisdashboard.io" />
<meta property="article:publisher" content="https://www.facebook.com/codedthemes" />
<meta property="og:title" content="PNSPS" />
<meta
property="og:description"
content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license."
/>
<meta property="og:image" content="https://mantisdashboard.io/adv-banner-images/og-social.png" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://mantisdashboard.io" />
<meta property="twitter:title" content="PNSPS" />
<meta
property="twitter:description"
content="Mantis is a free, super flexible and customizable react redux dashboard template built using MUI React components with open source MIT license."
/>
<meta property="twitter:image" content="https://mantisdashboard.io/adv-banner-images/og-social.png" />
<meta name="twitter:creator" content="@codedthemes" />

<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>PNSPS</title>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap&family=Public+Sans:wght@400;500;600;700"
rel="stylesheet"
/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

+ 18
- 0
src/App.js Bestand weergeven

@@ -0,0 +1,18 @@
// project import
import Routes from 'routes';
import ThemeCustomization from 'themes';
import ScrollTop from 'components/ScrollTop';
//import {isUserLoggedIn} from 'utils/Utils';
//import {DefaultRoute} from 'routes/index'
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== //

const App = () => (
<ThemeCustomization>
<ScrollTop>
<Routes>
</Routes>
</ScrollTop>
</ThemeCustomization>
);

export default App;

+ 9
- 0
src/App.test.js Bestand weergeven

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

BIN
src/assets/images/BHK_logo_rgb_zh-hk.png Bestand weergeven

Voor Na
Breedte: 1200  |  Hoogte: 588  |  Grootte: 45 KiB

+ 38
- 0
src/assets/images/auth/AuthBackground.js Bestand weergeven

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

// ==============================|| AUTH BLUR BACK SVG ||============================== //

const AuthBackground = () => {
// const theme = useTheme();
return (
<Box sx={{ position: 'absolute', filter: 'blur(18px)', zIndex: -1, bottom: 0 }}>
{/* <svg width="100%" height="calc(100vh - 175px)" viewBox="0 0 405 809" fill="none" xmlns="http://www.w3.org/2000/svg"> */}
<svg width="100%" viewBox="0 0 405 809" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clipPath22">
<path d="m0 256h213v-256h-213z"/>
</clipPath>
</defs>
<g transform="translate(-31.999 -25.836)">
<g transform="matrix(.35278 0 0 -.35278 46.979 25.836)">
<path d="m0 0h16.273v-37.417l17.85-0.035v-12.864h-34.123z" fill="#00a650"/>
</g>
<g transform="matrix(.35278 0 0 -.35278 16.798 99.848)">
<g clipPath="url(#clipPath22)">
<g transform="translate(82.049 183.46)">
<path d="m0 0c-17.536-0.633-27.516-14.176-27.516-28.318 0-14.143 9.241-28.019 27.563-28.071 3.447-9e-3 8.679 1.279 21.255 17.378l-17.808 0.07v10.335l34.132 0.061v-35.705l-10.467 3.577 0.045 12.858c-12.924-19.307-24.442-19.361-26.895-19.378-23.677-0.157-39.268 17.825-39.268 38.884s14.944 37.674 38.959 38.589z" fill="#1c63b7"/>
</g>
<g transform="translate(119.64 176.52)">
<path d="m0 0-10.391 0.041v11.372c3.151 1.943 6.524 3.573 10.424 4.515 3.633 0.879 7.526 1.321 11.567 1.321 24.407 0 39.006-17.548 39.006-38.608s-14.633-39.843-39.006-38.608c-2.765 0.14-3.93 0.531-7.946 1.652l0.055 9.924c3.341-0.628 3.344-0.529 7.891-1.047 17.298-1.971 27.561 13.927 27.561 28.069 0 14.143-10.11 28.09-27.561 28.09-4.162 0-8.175-0.816-11.567-2.433l-0.033-4.288" fill="#1c63b7"/>
</g>
</g>
</g>
</g>
</svg>
</Box>
);
};

export default AuthBackground;

BIN
src/assets/images/banner.jpg Bestand weergeven

Voor Na
Breedte: 1600  |  Hoogte: 252  |  Grootte: 123 KiB

BIN
src/assets/images/bg_ml.jpg Bestand weergeven

Voor Na
Breedte: 1920  |  Hoogte: 578  |  Grootte: 222 KiB

BIN
src/assets/images/hero-printing.png Bestand weergeven

Voor Na
Breedte: 1280  |  Hoogte: 348  |  Grootte: 522 KiB

+ 3
- 0
src/assets/images/icons/facebook.svg Bestand weergeven

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

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

BIN
src/assets/images/icons/icon_iAmSmart.png Bestand weergeven

Voor Na
Breedte: 120  |  Hoogte: 120  |  Grootte: 7.1 KiB

+ 3
- 0
src/assets/images/icons/twitter.svg Bestand weergeven

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

BIN
src/assets/images/users/avatar-1.png Bestand weergeven

Voor Na
Breedte: 64  |  Hoogte: 64  |  Grootte: 9.3 KiB

BIN
src/assets/images/users/avatar-2.png Bestand weergeven

Voor Na
Breedte: 64  |  Hoogte: 64  |  Grootte: 9.4 KiB

BIN
src/assets/images/users/avatar-3.png Bestand weergeven

Voor Na
Breedte: 64  |  Hoogte: 64  |  Grootte: 6.9 KiB

BIN
src/assets/images/users/avatar-4.png Bestand weergeven

Voor Na
Breedte: 64  |  Hoogte: 64  |  Grootte: 9.0 KiB

BIN
src/assets/images/users/avatar-group.png Bestand weergeven

Voor Na
Breedte: 113  |  Hoogte: 94  |  Grootte: 8.6 KiB

+ 3
- 0
src/assets/style/loginStyles.css Bestand weergeven

@@ -0,0 +1,3 @@
body{
padding-top: 0px;
}

+ 84
- 0
src/assets/style/navbarStyles.css Bestand weergeven

@@ -0,0 +1,84 @@
#nav{
display: flex;
align-items: center;
justify-content: space-between;
background-color: white;
/* padding: 20px 80px; */
box-shadow: 0 5px 15px rgba(0,0 0,0,0.06);
position:fixed;
top: 0px;
width: 100%;
z-index: 9999;
border-bottom: 3px solid #0C489E;
}
#navbar{
display: flex;
align-items: center;
justify-content: center;
}
#navbar li{
list-style: none;
padding: 0 20px;
position: relative;
}
#navbar li a{
text-decoration: none;
font-size: 1.5rem;
font-weight: 600;
font-family: 微軟正黑體;
color: black;
transition: 0.3s ease-in-out;
}
#navbar li a:hover{
color: #0C489E;
}
#navbar li a:hover::after,
#navbar li a:focus::after{
content: "";
width: 60%;
height: 2px;
background:#0C489E;
position: absolute;
top: -4px;
left: 20px;
}
#systemTitle{
text-decoration: none;
font-size: 1.3rem;
font-weight: 600;
color: #0C489E;
transition: 0.3s ease-in-out;
font-family: 微軟正黑體;
text-align: center;
}
#mobileTitle{
text-decoration: none;
font-size: 1.2rem;
font-weight: 600;
color: #0C489E;
transition: 0.3s ease-in-out;
font-family: 微軟正黑體;
text-align: center;
}
#sidebar{
align-items: center;
justify-content: center;
padding: 0;
}
#sidebar li{
list-style: none;
padding: 0 20px;
position: relative;
}
#sidebar li a{
text-decoration: none;
font-size: 1.3rem;
font-weight: 600;
font-family: 微軟正黑體;
color: black;
transition: 0.3s ease-in-out;
}
#sidebar li a:hover{
color: #0C489E;
}

+ 4
- 0
src/assets/style/styles.css Bestand weergeven

@@ -0,0 +1,4 @@
body{
padding-top: 43px;
font-family: 微軟正黑體;
}

+ 4
- 0
src/assets/third-party/apex-chart.css Bestand weergeven

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

+ 92
- 0
src/auth/index.js Bestand weergeven

@@ -0,0 +1,92 @@
// ** UseJWT import to get config
import axios from "axios";
import {isUserLoggedIn} from "../utils/Utils";
import jwtApplicationConfig from "auth/jwtApplicationConfig";
import jwt_decode from "jwt-decode";

//import axios from "axios";
export const refreshIntervalName = 'refreshInterval'
export const predictProductionQty = 'predictProductionQty'
export const predictUsageCount = 'predictUsageCount'
export const windowCount = 'windowCount'

// ** Handle User Login
export const handleLogin = data => {
return dispatch => {

dispatch({
type: 'LOGIN',
data,
jwtApplicationConfig,
accessToken: data['accessToken'],
refreshToken: data['refreshToken']
})

console.log(data)
// ** Add to user, accessToken & refreshToken to localStorage
localStorage.setItem('userData', JSON.stringify(data))
localStorage.setItem('accessToken', data.accessToken)
localStorage.setItem('refreshToken', data.refreshToken)
localStorage.setItem('axiosToken', "Bearer " + data.accessToken)
//localStorage.setItem(config.storageUserRoleKeyName, JSON.stringify(data.role).slice(1).slice(0, -1))
localStorage.setItem(refreshIntervalName, "60")
// for demo only
localStorage.setItem(windowCount, '0')
localStorage.setItem(predictProductionQty, '0')
localStorage.setItem(predictUsageCount, '0')
}
}

export const isTokenValid = () =>{
let isExpired = false;
const token = localStorage.getItem('accessToken');
let decodedToken=jwt_decode(token);
let dateNow = new Date();
if(decodedToken.exp < dateNow.getTime())
isExpired = true;
return isExpired;
}

// ** Handle User Logout
export const handleLogoutFunction = () => {
return dispatch => {
dispatch({
type: 'LOGOUT',
accessToken: null,
refreshToken: null
}
)

console.log("logout");
// ** Remove user, accessToken & refreshToken from localStorage
localStorage.removeItem('userData')
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
//localStorage.removeItem(config.storageUserRoleKeyName)
localStorage.removeItem(refreshIntervalName)
localStorage.removeItem(windowCount)
localStorage.removeItem(predictProductionQty)
localStorage.removeItem(predictUsageCount)

}
}

// ** Handle axios token
export const setupAxiosInterceptors = () => {
axios.interceptors.request.use(
config => {
// ** Get token from localStorage
const accessToken = localStorage.getItem("accessToken")

// ** If token is present add it to request's Authorization Header
if (isUserLoggedIn()) {
config.headers.Authorization = `${jwtApplicationConfig.tokenType} ${accessToken}`
}
config.headers['X-Authorization'] = process.env.REACT_APP_API_KEY

return config
},
error => Promise.reject(error)
)
}

+ 11
- 0
src/auth/jwt/coreUseJwt.js Bestand weergeven

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

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

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

+ 6
- 0
src/auth/jwt/useJwt.js Bestand weergeven

@@ -0,0 +1,6 @@
import createJwt from './coreUseJwt'
import jwtApplicationConfig from "../jwtApplicationConfig"

const { jwt } = createJwt(jwtApplicationConfig)

export default jwt

+ 17
- 0
src/auth/jwtApplicationConfig.js Bestand weergeven

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

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

@@ -0,0 +1,33 @@
import useJwt from 'auth/jwt/coreUseJwt'

/**
* Return if user is logged in
* This is completely up to you and how you want to store the token in your frontend application
* e.g. If you are using cookies to store the application please update this function
*/
// eslint-disable-next-line arrow-body-style
export const hostname = "localhost"
const hostPort = "8090"
export const hostPath = `http://${hostname}:${hostPort}`
export const apiPath = `${hostPath}/api`

export const isUserLoggedIn = () => {
return localStorage.getItem('userData') && localStorage.getItem(useJwt.jwtConfig.storageTokenKeyName)
}

export const getUserData = () => JSON.parse(localStorage.getItem('userData'))

/**
* This function is used for demo purpose route navigation
* In real app you won't need this function because your app will navigate to same route for each users regardless of ability
* Please note role field is just for showing purpose it's not used by anything in frontend
* We are checking role just for ease
* NOTE: If you have different pages to navigate based on user ability then this function can be useful. However, you need to update it.
* @param {String} userRole Role of user
*/
export const getHomeRouteForLoggedInUser = userRole => {
if (userRole === 'admin') return '/'
if (userRole === 'user') return '/'
if (userRole === 'client') return {name: 'access-control'}
return {name: 'auth-login'}
}

+ 29
- 0
src/components/@extended/AnimateButton.js Bestand weergeven

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

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

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

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

+ 15
- 0
src/components/Loadable.js Bestand weergeven

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

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

+ 51
- 0
src/components/Logo/Logo.js Bestand weergeven

@@ -0,0 +1,51 @@
// material-ui
// import { useTheme } from '@mui/material/styles';

/**
* if you want to use image instead of <svg> uncomment following.
*
* import logoDark from 'assets/images/logo-dark.svg';
* import logo from 'assets/images/logo.svg';
*
*/

// ==============================|| LOGO SVG ||============================== //

const Logo = () => {
// const theme = useTheme();

return (
/**
* if you want to use image instead of svg uncomment following, and comment out <svg> element.
*
* <img src={logo} alt="Mantis" width="100" />
*
*/
<>
<svg width="130" height="85" viewBox="20 0 10 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clipPath22">
<path d="m0 256h213v-256h-213z"/>
</clipPath>
</defs>
<g transform="translate(-31.999 -25.836)">
<g transform="matrix(.35278 0 0 -.35278 46.979 25.836)">
<path d="m0 0h16.273v-37.417l17.85-0.035v-12.864h-34.123z" fill="#00a650"/>
</g>
<g transform="matrix(.35278 0 0 -.35278 16.798 99.848)">
<g clipPath="url(#clipPath22)">
<g transform="translate(82.049 183.46)">
<path d="m0 0c-17.536-0.633-27.516-14.176-27.516-28.318 0-14.143 9.241-28.019 27.563-28.071 3.447-9e-3 8.679 1.279 21.255 17.378l-17.808 0.07v10.335l34.132 0.061v-35.705l-10.467 3.577 0.045 12.858c-12.924-19.307-24.442-19.361-26.895-19.378-23.677-0.157-39.268 17.825-39.268 38.884s14.944 37.674 38.959 38.589z" fill="#1c63b7"/>
</g>
<g transform="translate(119.64 176.52)">
<path d="m0 0-10.391 0.041v11.372c3.151 1.943 6.524 3.573 10.424 4.515 3.633 0.879 7.526 1.321 11.567 1.321 24.407 0 39.006-17.548 39.006-38.608s-14.633-39.843-39.006-38.608c-2.765 0.14-3.93 0.531-7.946 1.652l0.055 9.924c3.341-0.628 3.344-0.529 7.891-1.047 17.298-1.971 27.561 13.927 27.561 28.069 0 14.143-10.11 28.09-27.561 28.09-4.162 0-8.175-0.816-11.567-2.433l-0.033-4.288" fill="#1c63b7"/>
</g>
</g>
</g>
</g>
</svg>
</>
);
};

export default Logo;

+ 36
- 0
src/components/Logo/index.js Bestand weergeven

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

+ 103
- 0
src/components/MainCard.js Bestand weergeven

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

// material-ui
import { useTheme } from '@mui/material/styles';
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material';

// project import
import Highlighter from './third-party/Highlighter';

// header style
const headerSX = {
p: 2.5,
'& .MuiCardHeader-action': { m: '0px auto', alignSelf: 'center' }
};

// ==============================|| CUSTOM - MAIN CARD ||============================== //

const MainCard = forwardRef(
(
{
border = true,
boxShadow,
children,
content = true,
contentSX = {},
darkTitle,
elevation,
secondary,
shadow,
sx = {},
title,
codeHighlight,
...others
},
ref
) => {
const theme = useTheme();
boxShadow = theme.palette.mode === 'dark' ? boxShadow || true : boxShadow;

return (
<Card
elevation={elevation || 0}
ref={ref}
{...others}
sx={{
border: border ? '1px solid' : 'none',
borderRadius: 2,
borderColor: theme.palette.mode === 'dark' ? theme.palette.divider : theme.palette.grey.A800,
boxShadow: boxShadow && (!border || theme.palette.mode === 'dark') ? shadow || theme.customShadows.z1 : 'inherit',
':hover': {
boxShadow: boxShadow ? shadow || theme.customShadows.z1 : 'inherit'
},
'& pre': {
m: 0,
p: '16px !important',
fontFamily: theme.typography.fontFamily,
fontSize: '0.75rem'
},
...sx
}}
>
{/* card header and action */}
{!darkTitle && title && (
<CardHeader sx={headerSX} titleTypographyProps={{ variant: 'subtitle1' }} title={title} action={secondary} />
)}
{darkTitle && title && <CardHeader sx={headerSX} title={<Typography variant="h3">{title}</Typography>} action={secondary} />}

{/* card content */}
{content && <CardContent sx={contentSX}>{children}</CardContent>}
{!content && children}

{/* card footer - clipboard & highlighter */}
{codeHighlight && (
<>
<Divider sx={{ borderStyle: 'dashed' }} />
<Highlighter codeHighlight={codeHighlight} main>
{children}
</Highlighter>
</>
)}
</Card>
);
}
);

MainCard.propTypes = {
border: PropTypes.bool,
boxShadow: PropTypes.bool,
contentSX: PropTypes.object,
darkTitle: PropTypes.bool,
divider: PropTypes.bool,
elevation: PropTypes.number,
secondary: PropTypes.node,
shadow: PropTypes.string,
sx: PropTypes.object,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
codeHighlight: PropTypes.bool,
content: PropTypes.bool,
children: PropTypes.node
};

export default MainCard;

+ 51
- 0
src/components/MobileLogo/MobileLogo.js Bestand weergeven

@@ -0,0 +1,51 @@
// material-ui
// import { useTheme } from '@mui/material/styles';

/**
* if you want to use image instead of <svg> uncomment following.
*
* import logoDark from 'assets/images/logo-dark.svg';
* import logo from 'assets/images/logo.svg';
*
*/

// ==============================|| LOGO SVG ||============================== //

const MobileLogo = () => {
// const theme = useTheme();

return (
/**
* if you want to use image instead of svg uncomment following, and comment out <svg> element.
*
* <img src={logo} alt="Mantis" width="100" />
*
*/
<>
<svg width="60" height="42" viewBox="20 0 10 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clipPath22">
<path d="m0 256h213v-256h-213z"/>
</clipPath>
</defs>
<g transform="translate(-31.999 -25.836)">
<g transform="matrix(.35278 0 0 -.35278 46.979 25.836)">
<path d="m0 0h16.273v-37.417l17.85-0.035v-12.864h-34.123z" fill="#00a650"/>
</g>
<g transform="matrix(.35278 0 0 -.35278 16.798 99.848)">
<g clipPath="url(#clipPath22)">
<g transform="translate(82.049 183.46)">
<path d="m0 0c-17.536-0.633-27.516-14.176-27.516-28.318 0-14.143 9.241-28.019 27.563-28.071 3.447-9e-3 8.679 1.279 21.255 17.378l-17.808 0.07v10.335l34.132 0.061v-35.705l-10.467 3.577 0.045 12.858c-12.924-19.307-24.442-19.361-26.895-19.378-23.677-0.157-39.268 17.825-39.268 38.884s14.944 37.674 38.959 38.589z" fill="#1c63b7"/>
</g>
<g transform="translate(119.64 176.52)">
<path d="m0 0-10.391 0.041v11.372c3.151 1.943 6.524 3.573 10.424 4.515 3.633 0.879 7.526 1.321 11.567 1.321 24.407 0 39.006-17.548 39.006-38.608s-14.633-39.843-39.006-38.608c-2.765 0.14-3.93 0.531-7.946 1.652l0.055 9.924c3.341-0.628 3.344-0.529 7.891-1.047 17.298-1.971 27.561 13.927 27.561 28.069 0 14.143-10.11 28.09-27.561 28.09-4.162 0-8.175-0.816-11.567-2.433l-0.033-4.288" fill="#1c63b7"/>
</g>
</g>
</g>
</g>
</svg>
</>
);
};

export default MobileLogo;

+ 36
- 0
src/components/MobileLogo/index.js Bestand weergeven

@@ -0,0 +1,36 @@
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';

// material-ui
import { ButtonBase } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';

// project import
import Logo from './MobileLogo';
import config from 'config';
import { activeItem } from 'store/reducers/menu';

// ==============================|| MAIN LOGO ||============================== //

const LogoSection = ({ sx, to }) => {
const { defaultId } = useSelector((state) => state.menu);
const dispatch = useDispatch();
return (
<ButtonBase
disableRipple
component={Link}
onClick={() => dispatch(activeItem({ openItem: [defaultId] }))}
to={!to ? config.defaultPath : to}
sx={sx}
>
<Logo />
</ButtonBase>
);
};

LogoSection.propTypes = {
sx: PropTypes.object,
to: PropTypes.string
};

export default LogoSection;

+ 26
- 0
src/components/ScrollTop.js Bestand weergeven

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

// ==============================|| NAVIGATION - SCROLL TO TOP ||============================== //

const ScrollTop = ({ children }) => {
const location = useLocation();
const { pathname } = location;

useEffect(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
});
}, [pathname]);

return children || null;
};

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

export default ScrollTop;

+ 61
- 0
src/components/cards/AuthFooter.js Bestand weergeven

@@ -0,0 +1,61 @@
// material-ui
import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material';
import bhkLogo from 'assets/images/BHK_logo_rgb_zh-hk.png';
// ==============================|| FOOTER - AUTHENTICATION ||============================== //

const AuthFooter = () => {
const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm'));

return (
<Container maxWidth= "xl">
<Stack
direction={matchDownSM ? 'column' : 'row'}
justifyContent={matchDownSM ? 'center' : 'flex-start'}
spacing={2}
textAlign={matchDownSM ? 'center' : 'inherit'}
alignItems="center"
>
<Typography variant="subtitle2" color="secondary" component="span">
2024 &copy; 政府物流服務署
</Typography>
<Typography
variant="subtitle2"
color="secondary"
component={Link}
// href="https://material-ui.com/store/contributors/codedthemes/"
target="_blank"
underline="hover"
>
重要告示
</Typography>
<Typography
variant="subtitle2"
color="secondary"
component={Link}
// href="https://codedthemes.com"
href="/testMailPage"
target="_blank"
underline="hover"
>
私隱政策
</Typography>
</Stack>
<Stack direction={matchDownSM ? 'column' : 'row'} spacing={matchDownSM ? 1 : 3} textAlign={matchDownSM ? 'center' : 'inherit'} justifyContent={matchDownSM?"center":"flex-end"}>
<a href="https://www.w3.org/WAI/WCAG2AAA-Conformance"
title="Explanation of WCAG 2 Level AAA conformance">
<img height="32" width="88"
src="https://www.w3.org/WAI/wcag2AAA"
alt="Level AAA conformance,
W3C WAI Web Content Accessibility Guidelines 2.0"/>
</a>
<a href="https://www.brandhk.gov.hk/zh-hk"
title="">
<img src={bhkLogo} alt="logo" height="32" width="88" />
</a>
</Stack>
</Container>
);
};

export default AuthFooter;

+ 70
- 0
src/components/cards/statistics/AnalyticEcommerce.js Bestand weergeven

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

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

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

@@ -0,0 +1,19 @@
// ==============================|| THEME CONFIG ||============================== //

const config = {
defaultPath: '/dashboard',
fontFamily: `'Public Sans', sans-serif`,
i18n: 'en',
miniDrawer: false,
container: true,
mode: 'light',
presetColor: 'default',
themeDirection: 'ltr'
};

export default config;
export const drawerWidth = 260;

export const twitterColor = '#1DA1F2';
export const facebookColor = '#3b5998';
export const linkedInColor = '#0e76a8';

+ 38
- 0
src/index.js Bestand weergeven

@@ -0,0 +1,38 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';

// scroll bar
import 'simplebar/src/simplebar.css';

// third-party
import { Provider as ReduxProvider } from 'react-redux';

// apex-chart
import 'assets/third-party/apex-chart.css';

// project import
import App from './App';
import { store } from 'store';
import reportWebVitals from './reportWebVitals';

// ==============================|| MAIN - REACT DOM RENDER ||============================== //

const container = document.getElementById('root');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
//const NotAuthorized = lazy(() => import('../views/NotAuthorized'))
//const Error = lazy(() => import('../views/Error'))
root.render(
<StrictMode>
<ReduxProvider store={store}>
<BrowserRouter basename="/">
<App />
</BrowserRouter>
</ReduxProvider>
</StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

+ 32
- 0
src/layout/MainLayout/Drawer/DrawerContent/NavCard.js Bestand weergeven

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

+ 59
- 0
src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavGroup.js Bestand weergeven

@@ -0,0 +1,59 @@
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';

// material-ui
import { Box, List, Typography } from '@mui/material';

// project import
import NavItem from './NavItem';

// ==============================|| NAVIGATION - LIST GROUP ||============================== //

const NavGroup = ({ item }) => {
const menu = useSelector((state) => state.menu);
const { drawerOpen } = menu;

const navCollapse = item.children?.map((menuItem) => {
switch (menuItem.type) {
case 'collapse':
return (
<Typography key={menuItem.id} variant="caption" color="error" sx={{ p: 2.5 }}>
collapse - only available in paid version
</Typography>
);
case 'item':
return <NavItem key={menuItem.id} item={menuItem} level={1} />;
default:
return (
<Typography key={menuItem.id} variant="h6" color="error" align="center">
Fix - Group Collapse or Items
</Typography>
);
}
});

return (
<List
subheader={
item.title &&
drawerOpen && (
<Box sx={{ pl: 3, mb: 1.5 }}>
<Typography variant="subtitle2" color="textSecondary">
{item.title}
</Typography>
{/* only available in paid version */}
</Box>
)
}
sx={{ mb: drawerOpen ? 1.5 : 0, py: 0, zIndex: 0 }}
>
{navCollapse}
</List>
);
};

NavGroup.propTypes = {
item: PropTypes.object
};

export default NavGroup;

+ 142
- 0
src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js Bestand weergeven

@@ -0,0 +1,142 @@
import PropTypes from 'prop-types';
import { forwardRef, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';

// material-ui
import { useTheme } from '@mui/material/styles';
import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material';

// project import
import { activeItem } from 'store/reducers/menu';

// ==============================|| NAVIGATION - LIST ITEM ||============================== //

const NavItem = ({ item, level }) => {
const theme = useTheme();
const dispatch = useDispatch();
const { pathname } = useLocation();

const { drawerOpen, openItem } = useSelector((state) => state.menu);

let itemTarget = '_self';
if (item.target) {
itemTarget = '_blank';
}

let listItemProps = { component: forwardRef((props, ref) => <Link ref={ref} {...props} to={item.url} target={itemTarget} />) };
if (item?.external) {
listItemProps = { component: 'a', href: item.url, target: itemTarget };
}

const itemHandler = (id) => {
dispatch(activeItem({ openItem: [id] }));
};

const Icon = item.icon;
const itemIcon = item.icon ? <Icon style={{ fontSize: drawerOpen ? '1rem' : '1.25rem' }} /> : false;

const isSelected = openItem.findIndex((id) => id === item.id) > -1;
// active menu item on page load
useEffect(() => {
if (pathname.includes(item.url)) {
dispatch(activeItem({ openItem: [item.id] }));
}
// eslint-disable-next-line
}, [pathname]);

const textColor = 'text.primary';
const iconSelectedColor = 'primary.main';

return (
<ListItemButton
{...listItemProps}
disabled={item.disabled}
onClick={() => itemHandler(item.id)}
selected={isSelected}
sx={{
zIndex: 1201,
pl: drawerOpen ? `${level * 28}px` : 1.5,
py: !drawerOpen && level === 1 ? 1.25 : 1,
...(drawerOpen && {
'&:hover': {
bgcolor: 'primary.lighter'
},
'&.Mui-selected': {
bgcolor: 'primary.lighter',
borderRight: `2px solid ${theme.palette.primary.main}`,
color: iconSelectedColor,
'&:hover': {
color: iconSelectedColor,
bgcolor: 'primary.lighter'
}
}
}),
...(!drawerOpen && {
'&:hover': {
bgcolor: 'transparent'
},
'&.Mui-selected': {
'&:hover': {
bgcolor: 'transparent'
},
bgcolor: 'transparent'
}
})
}}
>
{itemIcon && (
<ListItemIcon
sx={{
minWidth: 28,
color: isSelected ? iconSelectedColor : textColor,
...(!drawerOpen && {
borderRadius: 1.5,
width: 36,
height: 36,
alignItems: 'center',
justifyContent: 'center',
'&:hover': {
bgcolor: 'secondary.lighter'
}
}),
...(!drawerOpen &&
isSelected && {
bgcolor: 'primary.lighter',
'&:hover': {
bgcolor: 'primary.lighter'
}
})
}}
>
{itemIcon}
</ListItemIcon>
)}
{(drawerOpen || (!drawerOpen && level !== 1)) && (
<ListItemText
primary={
<Typography variant="h6" sx={{ color: isSelected ? iconSelectedColor : textColor }}>
{item.title}
</Typography>
}
/>
)}
{(drawerOpen || (!drawerOpen && level !== 1)) && item.chip && (
<Chip
color={item.chip.color}
variant={item.chip.variant}
size={item.chip.size}
label={item.chip.label}
avatar={item.chip.avatar && <Avatar>{item.chip.avatar}</Avatar>}
/>
)}
</ListItemButton>
);
};

NavItem.propTypes = {
item: PropTypes.object,
level: PropTypes.number
};

export default NavItem;

+ 27
- 0
src/layout/MainLayout/Drawer/DrawerContent/Navigation/index.js Bestand weergeven

@@ -0,0 +1,27 @@
// material-ui
import { Box, Typography } from '@mui/material';

// project import
import NavGroup from './NavGroup';
import menuItem from 'menu-items';

// ==============================|| DRAWER CONTENT - NAVIGATION ||============================== //

const Navigation = () => {
const navGroups = menuItem.items.map((item) => {
switch (item.type) {
case 'group':
return <NavGroup key={item.id} item={item} />;
default:
return (
<Typography key={item.id} variant="h6" color="error" align="center">
Fix - Navigation Group
</Typography>
);
}
});

return <Box sx={{ pt: 2 }}>{navGroups}</Box>;
};

export default Navigation;

+ 21
- 0
src/layout/MainLayout/Drawer/DrawerContent/index.js Bestand weergeven

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

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

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

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

// project import
import DrawerHeaderStyled from './DrawerHeaderStyled';
import Logo from 'components/Logo';

// ==============================|| DRAWER HEADER ||============================== //

const DrawerHeader = ({ open }) => {
const theme = useTheme();

return (
// only available in paid version
<DrawerHeaderStyled theme={theme} open={open}>
<Stack direction="row" spacing={1} alignItems="center">
<Logo />
{/* <Chip
label={process.env.REACT_APP_VERSION}
size="small"
sx={{ height: 16, '& .MuiChip-label': { fontSize: '0.625rem', py: 0.25 } }}
component="a"
target="_blank"
clickable
/> */}
</Stack>
</DrawerHeaderStyled>
);
};

DrawerHeader.propTypes = {
open: PropTypes.bool
};

export default DrawerHeader;

+ 47
- 0
src/layout/MainLayout/Drawer/MiniDrawerStyled.js Bestand weergeven

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

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

+ 28
- 0
src/layout/MainLayout/Header/AppBarStyled.js Bestand weergeven

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

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

// ==============================|| HEADER - APP BAR STYLED ||============================== //

const AppBarStyled = styled(AppBar, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
...(open && {
marginLeft: drawerWidth,
// width: `calc(100% - ${drawerWidth}px)`,
width: "100%",
// height:"100px",
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
})
})
}));

export default AppBarStyled;

+ 102
- 0
src/layout/MainLayout/Header/HeaderContent/MobileSection.js Bestand weergeven

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

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

+ 62
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js Bestand weergeven

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

// material-ui
import { useTheme } from '@mui/material/styles';
import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';

// assets
import { EditOutlined, ProfileOutlined, LogoutOutlined, UserOutlined, WalletOutlined } from '@ant-design/icons';

// ==============================|| HEADER PROFILE - PROFILE TAB ||============================== //

const ProfileTab = ({ handleLogout }) => {
const theme = useTheme();

const [selectedIndex, setSelectedIndex] = useState(0);
const handleListItemClick = (event, index) => {
setSelectedIndex(index);
};

return (
<List component="nav" sx={{ p: 0, '& .MuiListItemIcon-root': { minWidth: 32, color: theme.palette.grey[500] } }}>
<ListItemButton selected={selectedIndex === 0} onClick={(event) => handleListItemClick(event, 0)}>
<ListItemIcon>
<EditOutlined />
</ListItemIcon>
<ListItemText primary="Edit Profile" />
</ListItemButton>
<ListItemButton selected={selectedIndex === 1} onClick={(event) => handleListItemClick(event, 1)}>
<ListItemIcon>
<UserOutlined />
</ListItemIcon>
<ListItemText primary="View Profile" />
</ListItemButton>

<ListItemButton selected={selectedIndex === 3} onClick={(event) => handleListItemClick(event, 3)}>
<ListItemIcon>
<ProfileOutlined />
</ListItemIcon>
<ListItemText primary="Social Profile" />
</ListItemButton>
<ListItemButton selected={selectedIndex === 4} onClick={(event) => handleListItemClick(event, 4)}>
<ListItemIcon>
<WalletOutlined />
</ListItemIcon>
<ListItemText primary="Billing" />
</ListItemButton>
<ListItemButton selected={selectedIndex === 2} onClick={handleLogout}>
<ListItemIcon>
<LogoutOutlined />
</ListItemIcon>
<ListItemText primary="Logout" />
</ListItemButton>
</List>
);
};

ProfileTab.propTypes = {
handleLogout: PropTypes.func
};

export default ProfileTab;

+ 56
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/SettingTab.js Bestand weergeven

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

+ 217
- 0
src/layout/MainLayout/Header/HeaderContent/Profile/index.js Bestand weergeven

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

// material-ui
import { useTheme } from '@mui/material/styles';
import {
// Avatar,
Box,
ButtonBase,
CardContent,
ClickAwayListener,
Grid,
IconButton,
Paper,
Popper,
Stack,
Tab,
Tabs,
Typography
} from '@mui/material';

// project import
import MainCard from 'components/MainCard';
import Transitions from 'components/@extended/Transitions';
import ProfileTab from './ProfileTab';
import SettingTab from './SettingTab';

// assets
import avatar1 from 'assets/images/users/avatar-1.png';
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
import { handleLogoutFunction } from 'auth/index';
import {useNavigate} from "react-router-dom";
import {useDispatch} from "react-redux";
import AccountCircleIcon from '@mui/icons-material/AccountCircle';

// tab panel wrapper
function TabPanel({ children, value, index, ...other }) {
return (
<div role="tabpanel" hidden={value !== index} id={`profile-tabpanel-${index}`} aria-labelledby={`profile-tab-${index}`} {...other}>
{value === index && children}
</div>
);
}

TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired
};

function a11yProps(index) {
return {
id: `profile-tab-${index}`,
'aria-controls': `profile-tabpanel-${index}`
};
}

// ==============================|| HEADER CONTENT - PROFILE ||============================== //

const Profile = () => {
const theme = useTheme();
const navigate = useNavigate()
const dispatch = useDispatch()

const handleLogout = async () => {
dispatch(handleLogoutFunction());
//await handleLogoutFunction();
navigate('/login');
};
const userData = JSON.parse(localStorage.getItem("userData"));

const anchorRef = useRef(null);
const [open, setOpen] = useState(false);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};

const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};

const [value, setValue] = useState(0);

const handleChange = (event, newValue) => {
setValue(newValue);
};

const iconBackColorOpen = 'grey.300';

return (
<Box sx={{ flexShrink: 0, ml: 0.75 }}>
<ButtonBase
sx={{
p: 0.25,
bgcolor: open ? iconBackColorOpen : 'transparent',
borderRadius: 1,
'&:hover': { bgcolor: 'secondary.lighter' }
}}
aria-label="open profile"
ref={anchorRef}
aria-controls={open ? 'profile-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<Stack direction="row" alignItems="center" sx={{ p: 0.5 }}>
<AccountCircleIcon style={{ color: 'black' }} alt="profile user" src={avatar1} sx={{ width: 32, height: 32 }} />
<Typography style={{ color: 'black',fontFamily: "微軟正黑體", fontSize: "1.2rem" }} variant="subtitle1">我的帳戶</Typography>
</Stack>
</ButtonBase>
<Popper
placement="bottom-end"
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
popperOptions={{
modifiers: [
{
name: 'offset',
options: {
offset: [0, 9]
}
}
]
}}
>
{({ TransitionProps }) => (
<Transitions type="fade" in={open} {...TransitionProps}>
{open && (
<Paper
sx={{
boxShadow: theme.customShadows.z1,
width: 290,
minWidth: 240,
maxWidth: 290,
[theme.breakpoints.down('md')]: {
maxWidth: 250
}
}}
>
<ClickAwayListener onClickAway={handleClose}>
<MainCard elevation={0} border={false} content={false}>
<CardContent sx={{ px: 2.5, pt: 3 }}>
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Stack direction="row" spacing={1.25} alignItems="center">
<AccountCircleIcon style={{ color: 'black' }} alt="profile user" src={avatar1} sx={{ width: 32, height: 32 }} />
<Stack>
<Typography style={{ color: 'black',fontFamily: "微軟正黑體", fontSize: "1.2rem" }} variant="subtitle1">我的帳戶</Typography>
<Typography variant="body2" color="textSecondary">
{userData == null ? "" : userData.fullName}
</Typography>
{/* <Typography variant="subtitle1">{userData == null ? "" : userData.fullName}</Typography> */}
</Stack>
</Stack>
</Grid>
<Grid item>
<IconButton size="large" color="secondary" onClick={handleLogout}>
<LogoutOutlined />
</IconButton>
</Grid>
</Grid>
</CardContent>
{open && (
<>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label="profile tabs">
<Tab
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
textTransform: 'capitalize'
}}
icon={<UserOutlined style={{ marginBottom: 0, marginRight: '10px' }} />}
label="Profile"
{...a11yProps(0)}
/>
<Tab
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
textTransform: 'capitalize'
}}
icon={<SettingOutlined style={{ marginBottom: 0, marginRight: '10px' }} />}
label="Setting"
{...a11yProps(1)}
/>
</Tabs>
</Box>
<TabPanel value={value} index={0} dir={theme.direction}>
<ProfileTab handleLogout={handleLogout} />
</TabPanel>
<TabPanel value={value} index={1} dir={theme.direction}>
<SettingTab />
</TabPanel>
</>
)}
</MainCard>
</ClickAwayListener>
</Paper>
)}
</Transitions>
)}
</Popper>
</Box>
);
};

export default Profile;

+ 30
- 0
src/layout/MainLayout/Header/HeaderContent/Search.js Bestand weergeven

@@ -0,0 +1,30 @@
// material-ui
import { Box, FormControl, InputAdornment, OutlinedInput } from '@mui/material';

// assets
import { SearchOutlined } from '@ant-design/icons';

// ==============================|| HEADER CONTENT - SEARCH ||============================== //

const Search = () => (
<Box sx={{ width: '100%', ml: { xs: 0, md: 1 } }}>
<FormControl sx={{ width: { xs: '100%', md: 224 } }}>
<OutlinedInput
size="small"
id="header-search"
startAdornment={
<InputAdornment position="start" sx={{ mr: -0.5 }}>
<SearchOutlined />
</InputAdornment>
}
aria-describedby="header-search-text"
inputProps={{
'aria-label': 'weight'
}}
placeholder="Ctrl + K"
/>
</FormControl>
</Box>
);

export default Search;

+ 45
- 0
src/layout/MainLayout/Header/HeaderContent/index.js Bestand weergeven

@@ -0,0 +1,45 @@
// material-ui
import { Button ,Box } from '@mui/material';

// import { Box, IconButton, Link, useMediaQuery } from '@mui/material';
// import { GithubOutlined } from '@ant-design/icons';

// project import
// import Search from './Search';
import Profile from './Profile';
// import Notification from './Notification';
// import MobileSection from './MobileSection';

// import { useState } from 'react';

// ==============================|| HEADER - CONTENT ||============================== //
// const pages = ['Products', 'Pricing', 'Blog'];
const HeaderContent = () => {

return (
<>
{/* {!matchesXs && <Search />}
{matchesXs && <Box sx={{ width: '100%', ml: 1 }} />} */}
<Box>
<Button size="large" sx={{color:"#000000"}}>主頁</Button>
</Box>
{/* <IconButton
component={Link}
href="https://github.com/codedthemes/mantis-free-react-admin-template"
target="_blank"
disableRipple
color="secondary"
title="Download Free Version"
sx={{ color: 'text.primary', bgcolor: 'grey.100' }}
>
<GithubOutlined />
</IconButton> */}

{/* <Notification /> */}
<Profile />
{/* <MobileSection /> */}
</>
);
};

export default HeaderContent;

+ 151
- 0
src/layout/MainLayout/Header/index.js Bestand weergeven

@@ -0,0 +1,151 @@
import PropTypes from 'prop-types';
import React
,{useState}
from 'react';
// material-ui
// import { useTheme } from '@mui/material/styles';
import {
AppBar,
// Container,
Typography,
Box,
Stack,
// IconButton,
// Menu,
// MenuItem,
// Button,
// Tooltip,
// Avatar,
// Stack,
Toolbar,
Divider,
// List,
// ListItem,
// ListItemButton,
// ListItemText,
IconButton,
Drawer,
// useMediaQuery
} from '@mui/material';

import MenuIcon from '@mui/icons-material/Menu';
// project import
// import AppBarStyled from './AppBarStyled';
// import HeaderContent from './HeaderContent';
import Logo from 'components/Logo';
import MobileLogo from 'components/MobileLogo';
import Profile from './HeaderContent/Profile';
import "assets/style/navbarStyles.css";
// assets
// import { MenuFoldOutlined,MenuOutlined } from '@ant-design/icons';
// import { AppBar } from '../../../../node_modules/@mui/material/index';
import { Link } from "react-router-dom";

const drawerWidth = 240;
// const navItems = ['Home', 'About', 'Contact'];
// ==============================|| MAIN LAYOUT - HEADER ||============================== //

function Header(props) {
const { window } = props;
const [mobileOpen, setMobileOpen] = useState(false);

const handleDrawerToggle = () => {
setMobileOpen((prevState) => !prevState);
};

const drawer = (
<Box onClick={handleDrawerToggle} sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ my: 2 }}>
PNSPS
</Typography>
<Divider />
<ul id="sidebar">
<li>
<Link className="login" to='/login'>登入</Link>
</li>
<li>
<Link className="register" to='/register'>申請</Link>
</li>
</ul>
<Divider />
<Profile />
</Box>
);

const container = window !== undefined ? () => window().document.body : undefined;
return (
<Box>
<AppBar component="nav">
<Toolbar id ="nav" width="100%">
<Stack
direction="row"
justifyContent="flex-start"
alignItems="center"
spacing={0}
>
<Box sx={{flexGrow: 1,display: { xs: 'none', sm: 'block' } }}>
<Logo/>
<span id="systemTitle" >公共啟事提交及繳費系統</span>
</Box>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon style={{ color: '#0C489E' }} />
</IconButton>
<Box sx={{mr: 2, display: { sm: 'none' } }}>
<MobileLogo/>
<span id="mobileTitle" >公共啟事提交及繳費系統</span>
</Box>
</Stack>
<Box sx={{display: { xs: 'none', sm: 'block' } ,width:"75%"}}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
spacing={1}
>
<ul id="navbar" width="100%" >
<li>
<Link className="login" to='/login'>登入</Link>
</li>
<li>
<Link className="register" to='/register'>申請</Link>
</li>
</ul>
<Profile />
</Stack>
</Box>
</Toolbar>
</AppBar>
<Box component="nav">
<Drawer
container={container}
variant="temporary"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
>
{drawer}
</Drawer>
</Box>
</Box>
);
}
Header.propTypes = {
/**
* Injected by the documentation to work in an iframe.
* You won't need it on your project.
*/
window: PropTypes.func,
};
export default Header;

+ 68
- 0
src/layout/MainLayout/index.js Bestand weergeven

@@ -0,0 +1,68 @@
import { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';

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

// project import
// import Drawer from './Drawer';
import Header from './Header';
import Footer from 'components/cards/AuthFooter';

// import navigation from 'menu-items';
// import Breadcrumbs from 'components/@extended/Breadcrumbs';

// types
import { openDrawer } from 'store/reducers/menu';
import "assets/style/styles.css";


// ==============================|| MAIN LAYOUT ||============================== //

const MainLayout = () => {
const theme = useTheme();
const matchDownLG = useMediaQuery(theme.breakpoints.down('lg'));
const dispatch = useDispatch();

const { drawerOpen } = useSelector((state) => state.menu);

// drawer toggler
const [open, setOpen] = useState(drawerOpen);
// const handleDrawerToggle = () => {
// setOpen(!open);
// dispatch(openDrawer({ drawerOpen: !open }));
// };

// set media wise responsive drawer
useEffect(() => {
setOpen(!matchDownLG);
dispatch(openDrawer({ drawerOpen: !matchDownLG }));

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [matchDownLG]);

useEffect(() => {
if (open !== drawerOpen) setOpen(drawerOpen);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [drawerOpen]);

return (
<Box sx={{ display: 'flex', width: '100%', flexDirection: "column"}}>
<Header/>
{/* <Drawer open={open} handleDrawerToggle={handleDrawerToggle} /> */}
<Box component="main" sx={{ width: '100%', flexGrow: 1, p: { xs: 2, sm: 3 } }}>
<Toolbar />
{/* <Breadcrumbs navigation={navigation} title /> */}
<Outlet />
</Box>
<Box sx={{borderTop: "3px solid #0C489E"}}>
<Footer/>
</Box>
</Box>
);
};

export default MainLayout;

+ 11
- 0
src/layout/MinimalLayout/index.js Bestand weergeven

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

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

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

export default MinimalLayout;

+ 35
- 0
src/menu-items/dashboard.js Bestand weergeven

@@ -0,0 +1,35 @@
// assets
import { DashboardOutlined } from '@ant-design/icons';

// icons
const icons = {
DashboardOutlined
};

// ==============================|| MENU ITEMS - DASHBOARD ||============================== //

const dashboard = {
id: 'group-dashboard',
title: 'Navigation',
type: 'group',
children: [
{
id: 'dashboard',
title: 'Dashboard',
type: 'item',
url: '/dashboard',
icon: icons.DashboardOutlined,
breadcrumbs: false
},
// {
// id: 'arsdashboard',
// title: 'ARS Dashboard',
// type: 'item',
// url: '/arsDashboard',
// icon: icons.DashboardOutlined,
// breadcrumbs: false
// }
]
};

export default dashboard;

+ 14
- 0
src/menu-items/index.js Bestand weergeven

@@ -0,0 +1,14 @@
// project import
import pages from './pages';
import dashboard from './dashboard';
import utilities from './utilities';
import support from './support';

// ==============================|| MENU ITEMS ||============================== //

const menuItems = {
items: [dashboard, pages, utilities, support]
};
// pages, utilities, support

export default menuItems;

+ 36
- 0
src/menu-items/pages.js Bestand weergeven

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

+ 36
- 0
src/menu-items/support.js Bestand weergeven

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

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

+ 359
- 0
src/pages/arsdashboard/index.js Bestand weergeven

@@ -0,0 +1,359 @@
import {useEffect, useState} from 'react';

// material-ui
import {
Avatar,
AvatarGroup,
Box,
Button,
Grid,
List,
ListItemAvatar,
ListItemButton,
ListItemSecondaryAction,
ListItemText,
MenuItem,
Stack,
TextField,
Typography
} from '@mui/material';

// project import
import OrdersTable from 'pages/dashboard/OrdersTable';
import IncomeAreaChart from 'pages/dashboard/IncomeAreaChart';
import MonthlyBarChart from 'pages/dashboard/MonthlyBarChart';
import ReportAreaChart from 'pages/dashboard/ReportAreaChart';
import SalesColumnChart from 'pages/dashboard/SalesColumnChart';
import MainCard from 'components/MainCard';
import AnalyticEcommerce from 'components/cards/statistics/AnalyticEcommerce';

// assets
import { GiftOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons';
import avatar1 from 'assets/images/users/avatar-1.png';
import avatar2 from 'assets/images/users/avatar-2.png';
import avatar3 from 'assets/images/users/avatar-3.png';
import avatar4 from 'assets/images/users/avatar-4.png';
import axios from "axios";

// avatar style
const avatarSX = {
width: 36,
height: 36,
fontSize: '1rem'
};

// action style
const actionSX = {
mt: 0.75,
ml: 1,
top: 'auto',
right: 'auto',
alignSelf: 'flex-start',
transform: 'none'
};

// sales report status
const status = [
{
value: 'today',
label: 'Today'
},
{
value: 'month',
label: 'This Month'
},
{
value: 'year',
label: 'This Year'
}
];

// ==============================|| DASHBOARD - DEFAULT ||============================== //

const ARSDashboard = () => {
const [value, setValue] = useState('today');
const [slot, setSlot] = useState('week');

useEffect(() =>{
tryAPI();
},[]);

const tryAPI = () =>{
axios.get('http://localhost:8090/api/test')
.then((response) => {
console.log(response)
})
.catch(error => {
console.error(error);
});
}

return (
<Grid container rowSpacing={4.5} columnSpacing={2.75}>
{/* row 1 */}
<Grid item xs={12} sx={{ mb: -2.25 }}>
<Typography variant="h5">Event Overall Status</Typography>
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<AnalyticEcommerce title="Total Page Views" count="4,42,236" percentage={59.3} extra="35,000" />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<AnalyticEcommerce title="Total Users" count="78,250" percentage={70.5} extra="8,900" />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<AnalyticEcommerce title="Total Order" count="18,800" percentage={27.4} isLoss color="warning" extra="1,943" />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<AnalyticEcommerce title="Total Sales" count="$35,078" percentage={27.4} isLoss color="warning" extra="$20,395" />
</Grid>

<Grid item md={8} sx={{ display: { sm: 'none', md: 'block', lg: 'none' } }} />

{/* row 2 */}
<Grid item xs={12} md={7} lg={8}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Application</Typography>
</Grid>
<Grid item>
<Stack direction="row" alignItems="center" spacing={0}>
<Button
size="small"
onClick={() => setSlot('month')}
color={slot === 'month' ? 'primary' : 'secondary'}
variant={slot === 'month' ? 'outlined' : 'text'}
>
Month
</Button>
<Button
size="small"
onClick={() => setSlot('week')}
color={slot === 'week' ? 'primary' : 'secondary'}
variant={slot === 'week' ? 'outlined' : 'text'}
>
Week
</Button>
</Stack>
</Grid>
</Grid>
<MainCard content={false} sx={{ mt: 1.5 }}>
<Box sx={{ pt: 1, pr: 2 }}>
<IncomeAreaChart slot={slot} />
</Box>
</MainCard>
</Grid>
<Grid item xs={12} md={5} lg={4}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Income Overview</Typography>
</Grid>
<Grid item />
</Grid>
<MainCard sx={{ mt: 2 }} content={false}>
<Box sx={{ p: 3, pb: 0 }}>
<Stack spacing={2}>
<Typography variant="h6" color="textSecondary">
This Week Statistics
</Typography>
<Typography variant="h3">$7,650</Typography>
</Stack>
</Box>
<MonthlyBarChart />
</MainCard>
</Grid>

{/* row 3 */}
<Grid item xs={12} md={7} lg={8}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Recent Orders</Typography>
</Grid>
<Grid item />
</Grid>
<MainCard sx={{ mt: 2 }} content={false}>
<OrdersTable />
</MainCard>
</Grid>
<Grid item xs={12} md={5} lg={4}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Analytics Report</Typography>
</Grid>
<Grid item />
</Grid>
<MainCard sx={{ mt: 2 }} content={false}>
<List sx={{ p: 0, '& .MuiListItemButton-root': { py: 2 } }}>
<ListItemButton divider>
<ListItemText primary="Company Finance Growth" />
<Typography variant="h5">+45.14%</Typography>
</ListItemButton>
<ListItemButton divider>
<ListItemText primary="Company Expenses Ratio" />
<Typography variant="h5">0.58%</Typography>
</ListItemButton>
<ListItemButton>
<ListItemText primary="Business Risk Cases" />
<Typography variant="h5">Low</Typography>
</ListItemButton>
</List>
<ReportAreaChart />
</MainCard>
</Grid>

{/* row 4 */}
<Grid item xs={12} md={7} lg={8}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Sales Report</Typography>
</Grid>
<Grid item>
<TextField
id="standard-select-currency"
size="small"
select
value={value}
onChange={(e) => setValue(e.target.value)}
sx={{ '& .MuiInputBase-input': { py: 0.5, fontSize: '0.875rem' } }}
>
{status.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Grid>
</Grid>
<MainCard sx={{ mt: 1.75 }}>
<Stack spacing={1.5} sx={{ mb: -12 }}>
<Typography variant="h6" color="secondary">
Net Profit
</Typography>
<Typography variant="h4">$1560</Typography>
</Stack>
<SalesColumnChart />
</MainCard>
</Grid>
<Grid item xs={12} md={5} lg={4}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h5">Transaction History</Typography>
</Grid>
<Grid item />
</Grid>
<MainCard sx={{ mt: 2 }} content={false}>
<List
component="nav"
sx={{
px: 0,
py: 0,
'& .MuiListItemButton-root': {
py: 1.5,
'& .MuiAvatar-root': avatarSX,
'& .MuiListItemSecondaryAction-root': { ...actionSX, position: 'relative' }
}
}}
>
<ListItemButton divider>
<ListItemAvatar>
<Avatar
sx={{
color: 'success.main',
bgcolor: 'success.lighter'
}}
>
<GiftOutlined />
</Avatar>
</ListItemAvatar>
<ListItemText primary={<Typography variant="subtitle1">Order #002434</Typography>} secondary="Today, 2:00 AM" />
<ListItemSecondaryAction>
<Stack alignItems="flex-end">
<Typography variant="subtitle1" noWrap>
+ $1,430
</Typography>
<Typography variant="h6" color="secondary" noWrap>
78%
</Typography>
</Stack>
</ListItemSecondaryAction>
</ListItemButton>
<ListItemButton divider>
<ListItemAvatar>
<Avatar
sx={{
color: 'primary.main',
bgcolor: 'primary.lighter'
}}
>
<MessageOutlined />
</Avatar>
</ListItemAvatar>
<ListItemText primary={<Typography variant="subtitle1">Order #984947</Typography>} secondary="5 August, 1:45 PM" />
<ListItemSecondaryAction>
<Stack alignItems="flex-end">
<Typography variant="subtitle1" noWrap>
+ $302
</Typography>
<Typography variant="h6" color="secondary" noWrap>
8%
</Typography>
</Stack>
</ListItemSecondaryAction>
</ListItemButton>
<ListItemButton>
<ListItemAvatar>
<Avatar
sx={{
color: 'error.main',
bgcolor: 'error.lighter'
}}
>
<SettingOutlined />
</Avatar>
</ListItemAvatar>
<ListItemText primary={<Typography variant="subtitle1">Order #988784</Typography>} secondary="7 hours ago" />
<ListItemSecondaryAction>
<Stack alignItems="flex-end">
<Typography variant="subtitle1" noWrap>
+ $682
</Typography>
<Typography variant="h6" color="secondary" noWrap>
16%
</Typography>
</Stack>
</ListItemSecondaryAction>
</ListItemButton>
</List>
</MainCard>
<MainCard sx={{ mt: 2 }}>
<Stack spacing={3}>
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Stack>
<Typography variant="h5" noWrap>
Help & Support Chat
</Typography>
<Typography variant="caption" color="secondary" noWrap>
Typical replay within 5 min
</Typography>
</Stack>
</Grid>
<Grid item>
<AvatarGroup sx={{ '& .MuiAvatar-root': { width: 32, height: 32 } }}>
<Avatar alt="Remy Sharp" src={avatar1} />
<Avatar alt="Travis Howard" src={avatar2} />
<Avatar alt="Cindy Baker" src={avatar3} />
<Avatar alt="Agnes Walker" src={avatar4} />
</AvatarGroup>
</Grid>
</Grid>
<Button size="small" variant="contained" sx={{ textTransform: 'capitalize' }}>
Need Help?
</Button>
</Stack>
</MainCard>
</Grid>
</Grid>
);
};

export default ARSDashboard;

+ 34
- 0
src/pages/authentication/AuthCard.js Bestand weergeven

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

+ 34
- 0
src/pages/authentication/AuthCardCustom.js Bestand weergeven

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

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

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

// ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== //

const AuthCardCustom = ({ children, ...other }) => (
<MainCard
sx={{
maxWidth: { xs: 400, lg: 1000 },
margin: { xs: 2.5, md: 3 },
'& > *': {
flexGrow: 1,
flexBasis: '50%'
}
}}
content={false}
{...other}
border={false}
boxShadow
>
<Box sx={{ p: { xs: 2, sm: 3, md: 4, xl: 5 } }}>{children}</Box>
</MainCard>
);

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

export default AuthCardCustom;

+ 73
- 0
src/pages/authentication/AuthWrapper.js Bestand weergeven

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

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

// project import
import AuthCard from './AuthCard';
// import Logo from 'components/Logo';
// import AuthFooter from 'components/cards/AuthFooter';

// assets
// import AuthBackground from 'assets/images/auth/AuthBackground';
// import backgroundImage from 'assets/images/hero-printing.png';
import backbroundImg from 'assets/images/bg_ml.jpg'
import 'assets/style/loginStyles.css'
import { Stack } from '../../../node_modules/@mui/material/index';
const BackgroundHead = {
backgroundImage: `url(${backbroundImg})`,
width: '100%',
height: '100%',
backgroundSize:'cover'
}

// ==============================|| AUTHENTICATION - WRAPPER ||============================== //

const AuthWrapper = ({ children }) => (
<Box sx={{ minHeight: '100vh' }}>
{/* <AuthBackground /> */}
{/* <img src={banner} alt="banner" width="100%" /> */}
<div style={BackgroundHead}>
<Grid
container
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{
minHeight: '100vh'
}}
>
<Grid item xs={12}>
<Stack direction="row"
justifyContent="space-between"
alignItems="center"
spacing={2}>
<Grid item xs={12} sx={{ ml: 4, mt: 3 ,display: { xs: 'none', sm: 'block' }}}>
<Typography style={{textAlign: "center",fontFamily: "微軟正黑體",fontSize: "1.6rem"}}>香港特別行政區政府</Typography>
<Typography style={{textAlign: "center",fontFamily: "微軟正黑體",fontSize: "1.6rem",fontWeight:"bold"}}>憲報</Typography>
</Grid>
<Grid
item
xs={12}
container
justifyContent="right"
alignItems="center"
sx={{ minHeight: { xs: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' } }}
>
<Grid item>
<AuthCard>{children}</AuthCard>
</Grid>
</Grid>
</Stack>
</Grid>
</Grid>
</div>
{/* <AuthFooter/> */}
</Box>
);

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

export default AuthWrapper;

+ 55
- 0
src/pages/authentication/AuthWrapperCustom.js Bestand weergeven

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

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

// project import
import AuthCard from './AuthCardCustom';
// import Logo from 'components/Logo';
// import AuthFooter from 'components/cards/AuthFooter';

// assets
import AuthBackground from 'assets/images/auth/AuthBackground';

// ==============================|| AUTHENTICATION - WRAPPER ||============================== //

const AuthWrapperCustom = ({ children }) => (
<Box sx={{ minHeight: '100vh' }}>
<AuthBackground />
<Grid
container
direction="column"
justifyContent="flex-end"
sx={{
minHeight: '100vh'
}}
>
{/* <Grid item xs={12} sx={{ ml: 3, mt: 3 }}>
<Logo />
</Grid> */}
<Grid item xs={12}>
<Grid
item
xs={12}
container
justifyContent="center"
alignItems="center"
sx={{ minHeight: { xs: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' }}}
>
<Grid item>
<AuthCard>{children}</AuthCard>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} sx={{ m: 3, mt: 1 }}>
{/* <AuthFooter /> */}
</Grid>
</Grid>
</Box>
);

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

export default AuthWrapperCustom;

+ 29
- 0
src/pages/authentication/Login.js Bestand weergeven

@@ -0,0 +1,29 @@
//import { Link } from 'react-router-dom';

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

// project import
import AuthLogin from './auth-forms/AuthLoginCustom';
import AuthWrapper from './AuthWrapper';
// ================================|| LOGIN ||================================ //

const Login = () => (
<AuthWrapper>
<Grid container spacing={3}>
<Grid item xs={12}>
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
<Typography variant="h3">登入</Typography>
{/* <Typography component={Link} to="/register" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
Don&apos;t have an account?
</Typography> */}
</Stack>
</Grid>
<Grid item xs={12}>
<AuthLogin />
</Grid>
</Grid>
</AuthWrapper>
);

export default Login;

+ 198
- 0
src/pages/authentication/Register.js Bestand weergeven

@@ -0,0 +1,198 @@
// import { Link } from 'react-router-dom';
import React,{useState} from 'react';

// material-ui
import {
Stepper,
Step,
StepButton,
// Grid,
Stack,
Typography,
Button,

} from '@mui/material';
import VisibilityIcon from '@mui/icons-material/Visibility';

// project import
import CustomFormWizard from './auth-forms/CustomFormWizard';
import AuthWrapper from './AuthWrapperCustom';

// ================================|| REGISTER ||================================ //
const stepStyle = {
width:"80%",
boxShadow: 2,
backgroundColor: "rgba(0,0,0,0.1)",
padding: 2,
"& .Mui-active": {
"&.MuiStepIcon-root": {
color: "warning.main",
fontSize: "2rem",
},
"& .MuiStepConnector-line": {
borderColor: "warning.main"
}
},
"& .Mui-completed": {
"&.MuiStepIcon-root": {
color: "secondary.main",
fontSize: "2rem",
},
"& .MuiStepConnector-line": {
borderColor: "secondary.main"
}
}
}
const steps = ['個人資料', '預覽', '完成提交'];

const Register = () => {
const [activeStep, setActiveStep] = useState(0);
const [completed, setCompleted] = useState([false]);

const totalSteps = () => {
return steps.length;
};

const completedSteps = () => {
return Object.keys(completed).length;
};

const isLastStep = () => {
return activeStep === totalSteps() - 1;
};

const allStepsCompleted = () => {
return completedSteps() === totalSteps();
};

const handleNext = () => {
console.log("test 1")
const newActiveStep =
isLastStep() && !allStepsCompleted()
? // It's the last step, but not all steps have been completed,
// find the first step that has been completed
steps.findIndex((step, i) => !(i in completed))
: activeStep + 1;
setActiveStep(newActiveStep);
console.log(newActiveStep)
};

const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};

const handleStep = (step) => () => {
setActiveStep(step);
};

// const handleComplete = () => {
// const newCompleted = completed;
// newCompleted[activeStep] = true;
// setCompleted(newCompleted);
// handleNext();
// };

const handleReset = () => {
setActiveStep(0);
setCompleted({});
};

return(
// <AuthWrapper>
<Stack sx={{ width: '100%',fontSize: '2rem'}} alignItems="center">
<Stepper activeStep={activeStep} sx={stepStyle}>
{steps.map((label, index) => (
<Step key={label} completed={completed[index]}>
{
index < 2 ?
(<StepButton onClick={handleStep(index)}>
{label}
</StepButton>) :
(<StepButton
sx={activeStep === 2 ? { "& .MuiSvgIcon-root": { color: "warning.main", fontSize: "2rem" } } : allStepsCompleted() ? { "& .MuiSvgIcon-root": { color: "secondary.main", fontSize: "2rem" } } : { color: "rgba(0, 0, 0, 0.38)" }}
icon={<VisibilityIcon />}
onClick={handleStep(index)}
>
{label}
</StepButton>)
}

</Step>
))}
</Stepper>
{allStepsCompleted() ? (
<React.Fragment>
<Typography sx={{ mt: 2, mb: 1 }}>
All steps completed - you&apos;re finished
</Typography>
<Stack direction="row" sx={{ pt: 2 }}>
<Stack sx={{ flex: '1 1 auto' }} />
<Button onClick={handleReset}>Reset</Button>
</Stack>
</React.Fragment>
) : (
<React.Fragment>
<AuthWrapper>
<CustomFormWizard step={activeStep} />
</AuthWrapper>
<Stack direction="row" sx={{ pt: 2 }}>
{ activeStep === totalSteps() - 1 ? (
<Button
color="inherit"
disabled={true}
onClick={handleBack}
sx={{ mr: 1 }}
>
返回
</Button>
):(
<Button
color="inherit"
disabled={activeStep === 0}
onClick={handleBack}
sx={{ mr: 1 }}
>
返回
</Button>
)
}
<Stack sx={{ flex: '1 1 auto' }} />
{activeStep === totalSteps() - 2 ?
(
<Button onClick={handleNext} sx={{ mr: 1 }}>
完成
</Button>
) : ( activeStep === totalSteps() - 1 ?
(
<Button color="inherit"
disabled={true} sx={{ mr: 1 }}>
完成
</Button>
):
(
<Button onClick={handleNext} sx={{ mr: 1 }}>
繼續
</Button>
)
)}
{/* {activeStep !== steps.length &&
(completed[activeStep] ? (
<Typography variant="caption" sx={{ display: 'inline-block' }}>
Step {activeStep + 1} already completed
</Typography>
) : (
<Button onClick={handleComplete}>
{completedSteps() === totalSteps() - 1
? 'Finish'
: 'Complete Step'}
</Button>
))} */}
</Stack>
</React.Fragment>
)}
</Stack >
// </AuthWrapper>
);
};

export default Register;

+ 76
- 0
src/pages/authentication/RegisterCustom.js Bestand weergeven

@@ -0,0 +1,76 @@


// material-ui
import {Link, Button, Card , Box, Grid } from '@mui/material';
import Typography from '@mui/material/Typography';
import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png';
import banner from 'assets/images/banner.jpg';
import { Stack } from '../../../node_modules/@mui/material/index';


// ================================|| LOGIN ||================================ //



const RegisterCustom = () => (

<Stack justifyContent="center" sx={{ minHeight: '100vh' }}>

<img src={banner} alt="banner" width="100%" />


<center>
<Card
sx={{
maxWidth: { xs: 1, lg: 1000 },
margin: { xs: 2.5, md: 3 },
'& > *': {
flexGrow: 1,
flexBasis: '50%'
}
}}
>
<Box alignItems="center">
<Typography mt={4} variant="h1">立即成為<strong style={{color: '#FF5733'}}>憲報刊登公告</strong>用戶</Typography>
<Typography variant="body1">只需4-5分鐘</Typography>

<Grid mt={5} mb={5} container >
<Grid item xs={6} sx={{ borderRight: 1 , borderColor: 'grey.500' }}>
<Typography mb={4} variant="h3">個人用戶</Typography>
<Button variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}>以「智方便」繼續</Button>
<Box mt={4} ml={2} mr={2} bgcolor="grey.100" p={1.5} >
<Typography textAlign='justify' variant="body1" display="block" gutterBottom>
你可點擊「智方便」按鈕,系統會自動輸入個人資料,或自行輸入個人資料,以即時啟動憲報刊登公告帳戶。
<br/>如欲使用「智方便」提供個人資料,請先下載「智方便」流動應用程式並登記成為「智方便」用戶。
</Typography>
<Link href="#">了解更多</Link>
</Box>

<Typography m={5}>或</Typography>

<Button href="/registerFrom" variant="contained">申請個人用戶</Button>

<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{fontWeight: 'bold'}} gutterBottom>
需上載身份證明文件數碼檔案以進行網上申請。
<br/>如:香港身份證;護照;往來港澳通行證等
</Typography>

</Grid>
<Grid item xs={6} sx={{ borderLeft: 1 ,borderColor: 'grey.500' }}>
<Typography mb={4} variant="h3">機構/公司用戶</Typography>
<Button href="/registerFrom" variant="contained">申請機構/公司用戶</Button>
<Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{fontWeight: 'bold'}} gutterBottom>
需上載以下任何一份證明文件以進行網上申請。
<br/>如:商業登記證;專業執業證書
</Typography>
</Grid>
</Grid>
</Box>
</Card >
</center>
</Stack>

);

export default RegisterCustom;

+ 225
- 0
src/pages/authentication/auth-forms/AuthLogin.js Bestand weergeven

@@ -0,0 +1,225 @@
import React, {useEffect, useState} from 'react';
import {useNavigate} from 'react-router-dom';

// material-ui
import {
Button,
//Checkbox,
//Divider,
//FormControlLabel,
FormHelperText,
Grid,
//Link,
IconButton,
InputAdornment,
InputLabel,
OutlinedInput,
Stack,
//Typography
} from '@mui/material';

// third party
import * as Yup from 'yup';
import { Formik } from 'formik';

// project import
//import FirebaseSocial from './FirebaseSocial';
import AnimateButton from 'components/@extended/AnimateButton';
//import {AbilityContext} from "@src/utility/context/Can"
// assets
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
import axios from "axios";
import {useDispatch} from "react-redux";
import {handleLogin} from "auth/index";
// ============================|| FIREBASE - LOGIN ||============================ //

const AuthLogin = () => {
//const ability = useContext(AbilityContext)
const dispatch = useDispatch()
const navigate = useNavigate()
//const [checked, setChecked] = useState(false);

const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => {
setShowPassword(!showPassword);
};

let [posts, setPosts] = useState([]);
let [userName, setUserName] = useState("");
let [userPassword, setUserPassword] = useState("");

useEffect(() => {
//console.log("POST: " + posts.accessToken);
},[posts]);

const handleMouseDownPassword = (event) => {
event.preventDefault();
};

const tryLogin = () => {
axios.post('http://localhost:8090/api/login',
{
"username": userName,
"password": userPassword
})
.then((response) => {
//setPosts("12354")
setPosts(response.data);
const userData = {
id: response.data.id,
fullName: response.data.name,
email: response.data.email,
role: response.data.role,
abilities: response.data.abilities,
//avatar: require('src/assets/images/users/avatar-3.png').default,
}
// const abilities = response.data.abilities
// ability.update(abilities)

const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken}
dispatch(handleLogin(data))

navigate('/dashboard');
//history.push(getHomeRouteForLoggedInUser("user"))

})
.catch(error => {
console.error(error);
});
}

const onUserNameChange = (event) => {
setUserName(event.target.value);
}

const onPasswordChange = (event) => {
setUserPassword(event.target.value);
}
return (
<>
<Formik
initialValues={{
email: '',
password: '',
submit: null
}}
validationSchema={Yup.object().shape({
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'),
password: Yup.string().max(255).required('Password is required')
})}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
try {
setStatus({ success: false });
setSubmitting(false);
} catch (err) {
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
}
}}
>
{({ errors, handleBlur, handleSubmit, isSubmitting, touched }) => (
<form noValidate onSubmit={handleSubmit}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="email-login">User Name</InputLabel>
<OutlinedInput
id="username"
name="username"
onBlur={handleBlur}
onChange={onUserNameChange}
placeholder="Enter user name"
fullWidth
error={Boolean(touched.email && errors.email)}
/>
{touched.email && errors.email && (
<FormHelperText error id="standard-weight-helper-text-email-login">
{errors.email}
</FormHelperText>
)}
</Stack>
</Grid>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="password-login">Password</InputLabel>
<OutlinedInput
fullWidth
error={Boolean(touched.password && errors.password)}
id="-password-login"
type={showPassword ? 'text' : 'password'}
name="password"
onBlur={handleBlur}
onChange={onPasswordChange}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
size="large"
>
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
</IconButton>
</InputAdornment>
}
placeholder="Enter password"
/>
{touched.password && errors.password && (
<FormHelperText error id="standard-weight-helper-text-password-login">
{errors.password}
</FormHelperText>
)}
</Stack>
</Grid>

<Grid item xs={12} sx={{ mt: -1 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
{/*<FormControlLabel*/}
{/* control={*/}
{/* <Checkbox*/}
{/* checked={checked}*/}
{/* onChange={(event) => setChecked(event.target.checked)}*/}
{/* name="checked"*/}
{/* color="primary"*/}
{/* size="small"*/}
{/* />*/}
{/* }*/}
{/* label={<Typography variant="h6">Keep me sign in</Typography>}*/}
{/*/>*/}
{/*<Link variant="h6" component={RouterLink} to="" color="text.primary">*/}
{/* Forgot Password?*/}
{/*</Link>*/}
</Stack>
</Grid>
{errors.submit && (
<Grid item xs={12}>
<FormHelperText error>{errors.submit}</FormHelperText>
</Grid>
)}
<Grid item xs={12}>
<AnimateButton>
<Button disableElevation onClick={tryLogin}
disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
登入
</Button>
</AnimateButton>
</Grid>
{/*<Grid item xs={12}>*/}
{/* <Divider>*/}
{/* <Typography variant="caption"> Login with</Typography>*/}
{/* </Divider>*/}
{/*</Grid>*/}
{/*<Grid item xs={12}>*/}
{/* <FirebaseSocial />*/}
{/*</Grid>*/}
</Grid>
</form>
)}
</Formik>
</>
);
};

export default AuthLogin;

+ 220
- 0
src/pages/authentication/auth-forms/AuthLoginCustom.js Bestand weergeven

@@ -0,0 +1,220 @@
import React, {
// useEffect,
useState} from 'react';
import { Link as RouterLink } from 'react-router-dom';
import {useNavigate} from 'react-router-dom';
// material-ui
import {
Button,
//Checkbox,
//Divider,
//FormControlLabel,
FormHelperText,
Grid,
Link,
IconButton,
InputAdornment,
InputLabel,
OutlinedInput,
Stack,
Typography
} from '@mui/material';

// third party
import * as Yup from 'yup';
import { Formik } from 'formik';

// project import
//import FirebaseSocial from './FirebaseSocial';
import AnimateButton from 'components/@extended/AnimateButton';

// assets
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
// import axios from "axios";
import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png';
import {useDispatch} from "react-redux";
import {handleLogin} from "auth/index";
import useJwt from "../../../auth/jwt/useJwt";

// ============================|| FIREBASE - LOGIN ||============================ //

const AuthLoginCustom = () => {
const dispatch = useDispatch()
const navigate = useNavigate()

const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => {
setShowPassword(!showPassword);
};

// let [posts, setPosts] = useState([]);
let [userName, setUserName] = useState(null);
let [userPassword, setUserPassword] = useState(null);

// useEffect(() => {
// // console.log("POST: " + posts.accessToken);
// },[posts]);

const handleMouseDownPassword = (event) => {
event.preventDefault();
};

const tryLogin = () => {
useJwt
.login({username: userName, password: userPassword})
.then((response) => {
const userData = {
id: response.data.id,
fullName: response.data.name,
email: response.data.email,
role: response.data.role,
abilities: response.data.abilities,
//avatar: require('src/assets/images/users/avatar-3.png').default,
}
const data = {...userData, accessToken: response.data.accessToken, refreshToken: response.data.refreshToken}
dispatch(handleLogin(data))
//const abilities = response.data.abilities
//ability.update(abilities)
navigate('/dashboard');
})
.catch((error) => {
console.error(error)
});
}



const onUserNameChange = (event) => {
setUserName(event.target.value);
}

const onPasswordChange = (event) => {
setUserPassword(event.target.value);
}

return (
<Formik
initialValues={{
username: '',
password: '',
submit: null
}}
validationSchema={Yup.object().shape({
username: Yup.string().email('Must be a valid email').max(255).required('Email is required'),
password: Yup.string().max(255).required('請輸入密碼'),
})}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
try {
setStatus({ success: false });
setSubmitting(false);
} catch (err) {
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
}
}}
>
{({ errors, handleBlur, handleSubmit, isSubmitting, touched }) => (
<form noValidate onSubmit={handleSubmit}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="email-login">用戶帳號或別名</InputLabel>
<OutlinedInput
id="username"
name="username"
onBlur={handleBlur}
onChange={onUserNameChange}
placeholder=""
fullWidth
error={Boolean(touched.email && errors.email)}
/>
{touched.email && errors.email && (
<FormHelperText error id="standard-weight-helper-text-email-login">
{errors.email}
</FormHelperText>
)}
</Stack>
</Grid>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="password-login">密碼</InputLabel>
<OutlinedInput
fullWidth
error={Boolean(touched.password && errors.password)}
id="password-login"
type={showPassword ? 'text' : 'password'}
name="password"
onBlur={handleBlur}
onChange={onPasswordChange}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
size="large"
>
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
</IconButton>
</InputAdornment>
}
placeholder=""
/>
{touched.password && errors.password && (
<FormHelperText error id="standard-weight-helper-text-password-login">
{errors.password}
</FormHelperText>
)}
</Stack>
</Grid>

<Grid item xs={12}>
<AnimateButton>
<Button disableElevation onClick={tryLogin}
disabled={isSubmitting} fullWidth size="large" type="submit" variant="contained" color="primary">
登錄
</Button>
</AnimateButton>
</Grid>
<Grid item xs={12}>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Link variant="h6" component={RouterLink} to="" color="text.primary">
<Typography align="center">
忘記密碼?
</Typography>
</Link>
</Stack>
</Grid>
<Grid item xs={12}>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Button fullWidth size="large" variant="outlined" startIcon={<img src={iAmSmartICon} alt="iAM Smart" width="30" />}>智方便登入</Button>
</Stack>
</Grid>
<Grid item xs={12}>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Link href="#">了解更多智方便</Link>
</Stack>
</Grid>
<Grid item xs={12}>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Button fullWidth size="large" variant="outlined" href="/register" >建立/重新啟動帳戶</Button>
</Stack>
</Grid>
{/* <Grid item xs={12}>*/}
{/* <Divider>*/}
{/* <Typography variant="caption"> Login with</Typography>*/}
{/* </Divider>*/}
{/*</Grid>*/}
{/*<Grid item xs={12}>*/}
{/* <FirebaseSocial />*/}
{/*</Grid> */}
</Grid>
</form>
)}
</Formik>
);
};

export default AuthLoginCustom;

+ 263
- 0
src/pages/authentication/auth-forms/AuthRegister.js Bestand weergeven

@@ -0,0 +1,263 @@
import { useEffect, useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';

// material-ui
import {
Box,
Button,
Divider,
FormControl,
FormHelperText,
Grid,
Link,
IconButton,
InputAdornment,
InputLabel,
OutlinedInput,
Stack,
Typography
} from '@mui/material';

// third party
import * as Yup from 'yup';
import { Formik } from 'formik';

// project import
import FirebaseSocial from './FirebaseSocial';
import AnimateButton from 'components/@extended/AnimateButton';
import { strengthColorChi, strengthIndicator } from 'utils/password-strength';

// assets
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';

// ============================|| FIREBASE - REGISTER ||============================ //

const AuthRegister = () => {
const [level, setLevel] = useState();
const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => {
setShowPassword(!showPassword);
};

const handleMouseDownPassword = (event) => {
event.preventDefault();
};

const changePassword = (value) => {
const temp = strengthIndicator(value);
setLevel(strengthColorChi(temp));
};

useEffect(() => {
changePassword('');
}, []);

return (
<>
<Formik
initialValues={{
firstname: '',
lastname: '',
email: '',
company: '',
password: '',
submit: null
}}
validationSchema={Yup.object().shape({
firstname: Yup.string().max(255).required('First Name is required'),
lastname: Yup.string().max(255).required('Last Name is required'),
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'),
password: Yup.string().max(255).required('Password is required')
})}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
try {
setStatus({ success: false });
setSubmitting(false);
} catch (err) {
console.error(err);
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
}
}}
>
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
<form noValidate onSubmit={handleSubmit}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Stack spacing={1}>
<InputLabel htmlFor="firstname-signup">First Name*</InputLabel>
<OutlinedInput
id="firstname-login"
type="firstname"
value={values.firstname}
name="firstname"
onBlur={handleBlur}
onChange={handleChange}
placeholder="John"
fullWidth
error={Boolean(touched.firstname && errors.firstname)}
/>
{touched.firstname && errors.firstname && (
<FormHelperText error id="helper-text-firstname-signup">
{errors.firstname}
</FormHelperText>
)}
</Stack>
</Grid>
<Grid item xs={12} md={6}>
<Stack spacing={1}>
<InputLabel htmlFor="lastname-signup">Last Name*</InputLabel>
<OutlinedInput
fullWidth
error={Boolean(touched.lastname && errors.lastname)}
id="lastname-signup"
type="lastname"
value={values.lastname}
name="lastname"
onBlur={handleBlur}
onChange={handleChange}
placeholder="Doe"
inputProps={{}}
/>
{touched.lastname && errors.lastname && (
<FormHelperText error id="helper-text-lastname-signup">
{errors.lastname}
</FormHelperText>
)}
</Stack>
</Grid>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="company-signup">Company</InputLabel>
<OutlinedInput
fullWidth
error={Boolean(touched.company && errors.company)}
id="company-signup"
value={values.company}
name="company"
onBlur={handleBlur}
onChange={handleChange}
placeholder="Demo Inc."
inputProps={{}}
/>
{touched.company && errors.company && (
<FormHelperText error id="helper-text-company-signup">
{errors.company}
</FormHelperText>
)}
</Stack>
</Grid>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="email-signup">Email Address*</InputLabel>
<OutlinedInput
fullWidth
error={Boolean(touched.email && errors.email)}
id="email-login"
type="email"
value={values.email}
name="email"
onBlur={handleBlur}
onChange={handleChange}
placeholder="[email protected]"
inputProps={{}}
/>
{touched.email && errors.email && (
<FormHelperText error id="helper-text-email-signup">
{errors.email}
</FormHelperText>
)}
</Stack>
</Grid>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor="password-signup">Password</InputLabel>
<OutlinedInput
fullWidth
error={Boolean(touched.password && errors.password)}
id="password-signup"
type={showPassword ? 'text' : 'password'}
value={values.password}
name="password"
onBlur={handleBlur}
onChange={(e) => {
handleChange(e);
changePassword(e.target.value);
}}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
size="large"
>
{showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
</IconButton>
</InputAdornment>
}
placeholder="******"
inputProps={{}}
/>
{touched.password && errors.password && (
<FormHelperText error id="helper-text-password-signup">
{errors.password}
</FormHelperText>
)}
</Stack>
<FormControl fullWidth sx={{ mt: 2 }}>
<Grid container spacing={2} alignItems="center">
<Grid item>
<Box sx={{ bgcolor: level?.color, width: 85, height: 8, borderRadius: '7px' }} />
</Grid>
<Grid item>
<Typography variant="subtitle1" fontSize="0.75rem">
{level?.label}
</Typography>
</Grid>
</Grid>
</FormControl>
</Grid>
<Grid item xs={12}>
<Typography variant="body2">
By Signing up, you agree to our &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;

+ 1009
- 0
src/pages/authentication/auth-forms/CustomFormWizard.js
Diff onderdrukt omdat het te groot bestand
Bestand weergeven


+ 66
- 0
src/pages/authentication/auth-forms/FirebaseSocial.js Bestand weergeven

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

+ 24
- 0
src/pages/components-overview/AntIcons.js Bestand weergeven

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

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

// styles
const IFrameWrapper = styled('iframe')(() => ({
height: 'calc(100vh - 210px)',
border: 'none'
}));

// ============================|| ANT ICONS ||============================ //

const AntIcons = () => (
<ComponentSkeleton>
<MainCard title="Ant Icons">
<IFrameWrapper title="Ant Icon" width="100%" src="https://ant.design/components/icon/" />
</MainCard>
</ComponentSkeleton>
);

export default AntIcons;

+ 141
- 0
src/pages/components-overview/Color.js Bestand weergeven

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

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

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

// ===============================|| COLOR BOX ||=============================== //

function ColorBox({ bgcolor, title, data, dark, main }) {
return (
<>
<Card sx={{ '&.MuiPaper-root': { borderRadius: '0px' } }}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
py: 2.5,
bgcolor,
color: dark ? 'grey.800' : '#ffffff',
border: main ? '1px dashed' : '1px solid transparent'
}}
>
{title && (
<Grid container justifyContent="space-around" alignItems="center">
<Grid item>
{data && (
<Stack spacing={0.75} alignItems="center">
<Typography variant="subtitle2">{data.label}</Typography>
<Typography variant="subtitle1">{data.color}</Typography>
</Stack>
)}
</Grid>
<Grid item>
<Typography variant="subtitle1" color="inherit">
{title}
</Typography>
</Grid>
</Grid>
)}
</Box>
</Card>
</>
);
}

ColorBox.propTypes = {
bgcolor: PropTypes.string,
title: PropTypes.string,
data: PropTypes.object.isRequired,
dark: PropTypes.bool,
main: PropTypes.bool
};

// ===============================|| COMPONENT - COLOR ||=============================== //

const ComponentColor = () => (
<ComponentSkeleton>
<Grid container spacing={3}>
<Grid item xs={12} sm={6} md={4}>
<MainCard title="Primary Color" codeHighlight>
<Stack>
<ColorBox bgcolor="primary.lighter" data={{ label: 'Blue-1', color: '#e6f7ff' }} title="primary.lighter" dark />
<ColorBox bgcolor="primary.100" data={{ label: 'Blue-2', color: '#bae7ff' }} title="primary[100]" dark />
<ColorBox bgcolor="primary.200" data={{ label: 'Blue-3', color: '#91d5ff' }} title="primary[200]" dark />
<ColorBox bgcolor="primary.light" data={{ label: 'Blue-4', color: '#69c0ff' }} title="primary.light" dark />
<ColorBox bgcolor="primary.400" data={{ label: 'Blue-5', color: '#40a9ff' }} title="primary[400]" />
<ColorBox bgcolor="primary.main" data={{ label: 'Blue-6', color: '#1890ff' }} title="primary.main" main />
<ColorBox bgcolor="primary.dark" data={{ label: 'Blue-7', color: '#096dd9' }} title="primary.dark" />
<ColorBox bgcolor="primary.700" data={{ label: 'Blue-8', color: '#0050b3' }} title="primary[700]" />
<ColorBox bgcolor="primary.darker" data={{ label: 'Blue-9', color: '#003a8c' }} title="primary.darker" />
<ColorBox bgcolor="primary.900" data={{ label: 'Blue-10', color: '#002766' }} title="primary.900" />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<MainCard title="Secondary Color" codeHighlight>
<Stack>
<ColorBox bgcolor="secondary.lighter" data={{ label: 'Grey-1', color: '#fafafa' }} title="secondary.lighter" dark />
<ColorBox bgcolor="secondary.100" data={{ label: 'Grey-2', color: '#f5f5f5' }} title="secondary[100]" dark />
<ColorBox bgcolor="secondary.200" data={{ label: 'Grey-3', color: '#f0f0f0' }} title="secondary[200]" dark />
<ColorBox bgcolor="secondary.light" data={{ label: 'Grey-4', color: '#d9d9d9' }} title="secondary.light" dark />
<ColorBox bgcolor="secondary.400" data={{ label: 'Grey-5', color: '#bfbfbf' }} title="secondary[400]" dark />
<ColorBox bgcolor="secondary.main" data={{ label: 'Grey-6', color: '#8c8c8c' }} title="secondary.main" main />
<ColorBox bgcolor="secondary.600" data={{ label: 'Grey-7', color: '#595959' }} title="secondary.600" />
<ColorBox bgcolor="secondary.dark" data={{ label: 'Grey-8', color: '#262626' }} title="secondary.dark" />
<ColorBox bgcolor="secondary.800" data={{ label: 'Grey-9', color: '#141414' }} title="secondary[800]" />
<ColorBox bgcolor="secondary.darker" data={{ label: 'Grey-10', color: '#000000' }} title="secondary.darker" />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<MainCard title="Other Color" codeHighlight>
<Stack>
<ColorBox bgcolor="secondary.A100" data={{ label: 'Grey-A1', color: '#ffffff' }} title="secondary.A100" dark />
<ColorBox bgcolor="secondary.A200" data={{ label: 'Grey-A2', color: '#434343' }} title="secondary.A200" />
<ColorBox bgcolor="secondary.A300" data={{ label: 'Grey-A3', color: '#1f1f1f' }} title="secondary.A300" />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<MainCard title="Success Color" codeHighlight>
<Stack>
<ColorBox bgcolor="success.lighter" data={{ label: 'Green-1', color: '#f6ffed' }} title="success.lighter" dark />
<ColorBox bgcolor="success.light" data={{ label: 'Green-4', color: '#95de64' }} title="success.light" dark />
<ColorBox bgcolor="success.main" data={{ label: 'Green-6', color: '#52c41a' }} title="success.main" main />
<ColorBox bgcolor="success.dark" data={{ label: 'Green-8', color: '#237804' }} title="success.dark" />
<ColorBox bgcolor="success.darker" data={{ label: 'Green-10', color: '#092b00' }} title="success.darker" />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<MainCard title="Error Color" codeHighlight>
<Stack>
<ColorBox bgcolor="error.lighter" data={{ label: 'Red-1', color: '#fff1f0' }} title="error.lighter" dark />
<ColorBox bgcolor="error.light" data={{ label: 'Red-4', color: '#ff7875' }} title="error.light" dark />
<ColorBox bgcolor="error.main" data={{ label: 'Red-6', color: '#f5222d' }} title="error.main" main />
<ColorBox bgcolor="error.dark" data={{ label: 'Red-8', color: '#a8071a' }} title="error.dark" />
<ColorBox bgcolor="error.darker" data={{ label: 'Red-10', color: '#5c0011' }} title="error.darker" />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<MainCard title="Warning Color" codeHighlight>
<Stack>
<ColorBox bgcolor="warning.lighter" data={{ label: 'Gold-1', color: '#fffbe6' }} title="warning.lighter" dark />
<ColorBox bgcolor="warning.light" data={{ label: 'Gold-4', color: '#ffd666' }} title="warning.light" dark />
<ColorBox bgcolor="warning.main" data={{ label: 'Gold-6', color: '#faad14' }} title="warning.main" main />
<ColorBox bgcolor="warning.dark" data={{ label: 'Gold-8', color: '#ad6800' }} title="warning.dark" />
<ColorBox bgcolor="warning.darker" data={{ label: 'Gold-10', color: '#613400' }} title="warning.darker" />
</Stack>
</MainCard>
</Grid>
</Grid>
</ComponentSkeleton>
);

export default ComponentColor;

+ 59
- 0
src/pages/components-overview/ComponentSkeleton.js Bestand weergeven

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

// material-ui
import { Grid, Skeleton, Stack } from '@mui/material';

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

// ===============================|| COMPONENT - SKELETON ||=============================== //

const ComponentSkeleton = ({ children }) => {
const [isLoading, setLoading] = useState(true);
useEffect(() => {
setLoading(false);
}, []);

const skeletonCard = (
<MainCard
title={<Skeleton sx={{ width: { xs: 120, md: 180 } }} />}
secondary={<Skeleton animation="wave" variant="circular" width={24} height={24} />}
>
<Stack spacing={1}>
<Skeleton />
<Skeleton sx={{ height: 64 }} animation="wave" variant="rectangular" />
<Skeleton />
<Skeleton />
</Stack>
</MainCard>
);

return (
<>
{isLoading && (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
{skeletonCard}
</Grid>
<Grid item xs={12} md={6}>
{skeletonCard}
</Grid>
<Grid item xs={12} md={6}>
{skeletonCard}
</Grid>
<Grid item xs={12} md={6}>
{skeletonCard}
</Grid>
</Grid>
)}
{!isLoading && children}
</>
);
};

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

export default ComponentSkeleton;

Some files were not shown because too many files changed in this diff

Laden…
Annuleren
Opslaan