├── .czrc ├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── push-image.yml ├── .gitignore ├── .husky └── commit-msg ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── Taskfile.yaml ├── commitlint.config.js ├── envs ├── .env.local ├── .env.prod └── .env.stg ├── index.html ├── netlify.toml ├── nginx.conf ├── package.json ├── screenshots ├── blog.png ├── dashboard.png ├── product.png ├── signin.png ├── signup.png └── user.png ├── src ├── _mocks_ │ ├── account.ts │ ├── blog.ts │ ├── products.ts │ └── user.ts ├── assets │ ├── favicon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ ├── images │ │ ├── avatar_default.png │ │ ├── icons │ │ │ ├── ic_flag_de.svg │ │ │ ├── ic_flag_en.svg │ │ │ ├── ic_flag_fr.svg │ │ │ ├── ic_notification_chat.svg │ │ │ ├── ic_notification_mail.svg │ │ │ ├── ic_notification_package.svg │ │ │ ├── ic_notification_shipping.svg │ │ │ └── shape-avatar.svg │ │ ├── illustrations │ │ │ ├── illustration_404.svg │ │ │ ├── illustration_avatar.png │ │ │ ├── illustration_login.png │ │ │ └── illustration_register.png │ │ ├── logo.svg │ │ └── preview.png │ └── manifest.json ├── components │ ├── @material-extend │ │ ├── MHidden.ts │ │ └── index.ts │ ├── ColorManyPicker.tsx │ ├── ColorPreview.tsx │ ├── Label.tsx │ ├── Logo.tsx │ ├── MenuPopover.tsx │ ├── NavSection.tsx │ ├── Page.tsx │ ├── ScrollToTop.tsx │ ├── Scrollbar.tsx │ ├── SearchNotFound.tsx │ ├── SvgIconStyle.tsx │ ├── _dashboard │ │ ├── app │ │ │ ├── AppBugReports.tsx │ │ │ ├── AppConversionRates.tsx │ │ │ ├── AppCurrentSubject.tsx │ │ │ ├── AppCurrentVisits.tsx │ │ │ ├── AppItemOrders.tsx │ │ │ ├── AppNewUsers.tsx │ │ │ ├── AppNewsUpdate.tsx │ │ │ ├── AppOrderTimeline.tsx │ │ │ ├── AppTasks.tsx │ │ │ ├── AppTrafficBySite.tsx │ │ │ ├── AppWebsiteVisits.tsx │ │ │ ├── AppWeeklySales.tsx │ │ │ └── index.ts │ │ ├── blog │ │ │ ├── BlogPostCard.tsx │ │ │ ├── BlogPostsSearch.tsx │ │ │ ├── BlogPostsSort.tsx │ │ │ └── index.ts │ │ ├── products │ │ │ ├── ProductCard.tsx │ │ │ ├── ProductCartWidget.tsx │ │ │ ├── ProductFilterSidebar.tsx │ │ │ ├── ProductList.tsx │ │ │ ├── ProductSort.tsx │ │ │ └── index.ts │ │ └── user │ │ │ ├── UserListHead.tsx │ │ │ ├── UserListToolbar.tsx │ │ │ ├── UserMoreMenu.tsx │ │ │ └── index.ts │ ├── animate │ │ ├── MotionContainer.tsx │ │ ├── index.ts │ │ └── variants │ │ │ ├── Wrap.ts │ │ │ ├── bounce │ │ │ ├── BounceIn.ts │ │ │ ├── BounceOut.ts │ │ │ └── index.ts │ │ │ └── index.ts │ ├── authentication │ │ ├── AuthSocial.tsx │ │ ├── login │ │ │ ├── LoginForm.tsx │ │ │ └── index.ts │ │ └── register │ │ │ ├── RegisterForm.tsx │ │ │ └── index.ts │ └── charts │ │ ├── BaseOptionChart.ts │ │ └── index.ts ├── layouts │ ├── AuthLayout.tsx │ ├── LogoOnlyLayout.tsx │ └── dashboard │ │ ├── AccountPopover.tsx │ │ ├── DashboardNavbar.tsx │ │ ├── DashboardSidebar.tsx │ │ ├── LanguagePopover.tsx │ │ ├── NotificationsPopover.tsx │ │ ├── Searchbar.tsx │ │ ├── SidebarConfig.tsx │ │ └── index.tsx ├── main.tsx ├── models.ts ├── pages │ ├── Blog.tsx │ ├── DashboardApp.tsx │ ├── Login.tsx │ ├── Page404.tsx │ ├── Products.tsx │ ├── Register.tsx │ └── User.tsx ├── routes.tsx ├── setupTests.ts ├── theme │ ├── breakpoints.ts │ ├── globalStyles.ts │ ├── index.tsx │ ├── overrides │ │ ├── Autocomplete.ts │ │ ├── Backdrop.ts │ │ ├── Button.ts │ │ ├── Card.ts │ │ ├── IconButton.ts │ │ ├── Input.ts │ │ ├── Lists.ts │ │ ├── Paper.ts │ │ ├── Tooltip.ts │ │ ├── Typography.ts │ │ └── index.ts │ ├── palette.ts │ ├── shadows.ts │ ├── shape.ts │ └── typography.ts └── utils │ ├── formatNumber.ts │ ├── formatTime.ts │ └── mockImages.ts ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.czrc: -------------------------------------------------------------------------------- 1 | { "path": "cz-conventional-changelog" } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: ['tsconfig.json'], 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module' 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], 10 | root: true, 11 | env: { 12 | node: true, 13 | jest: true 14 | }, 15 | 16 | ignorePatterns: ['.eslintrc.js', '*.ejs', '*.js', 'vite.config.ts'], 17 | rules: { 18 | 'no-unused-vars': 'off', 19 | '@typescript-eslint/no-unused-vars': 'off', 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off' 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/push-image.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to ECR 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | name: Build Image 10 | runs-on: ubuntu-latest 11 | 12 | 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v2 16 | 17 | - name: Configure AWS credentials 18 | uses: aws-actions/configure-aws-credentials@v1 19 | with: 20 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 21 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 22 | aws-region: ${{ secrets.AWS_REGION }} 23 | 24 | - name: Login to Amazon ECR 25 | id: login-ecr 26 | uses: aws-actions/amazon-ecr-login@v1 27 | 28 | - name: Build, tag, and push image to Amazon ECR 29 | env: 30 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 31 | ECR_REPOSITORY: lantron-ecr-b5b2b12 32 | IMAGE_TAG: react-admin 33 | run: | 34 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 35 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | .idea 21 | *.iml 22 | .env 23 | dist -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | echo "Checking branch name..." 6 | if [[ $(git symbolic-ref --short HEAD) =~ ^(feature|bugfix|hotfix|temp)\/.*$ ]]; then 7 | exit 0 8 | else 9 | echo "Invalid branch name. Only feature/* and bugfix/* branches are allowed." 10 | exit 1 11 | fi 12 | 13 | yarn lint --fix 14 | yarn build 15 | 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "tabWidth": 4 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine as builder 2 | 3 | WORKDIR /app 4 | COPY . . 5 | RUN yarn 6 | RUN yarn build 7 | 8 | FROM nginx:alpine 9 | COPY --from=builder /app/dist /usr/share/nginx/html 10 | COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/default.conf 11 | 12 | # 设置 nginx 作为默认命令启动 13 | CMD ["nginx", "-g", "daemon off;"] 14 | 15 | # 设置端口 16 | EXPOSE 80 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ReactZone 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## stack 2 | - vite 3 | - react 4 | - typescript 5 | - mui 6 | 7 | 8 | ## screenshots 9 | ![dashboard](screenshots/dashboard.png) 10 | ![user](screenshots/user.png) 11 | ![product](screenshots/product.png) 12 | ![blog](screenshots/blog.png) 13 | ![signin](screenshots/signin.png) 14 | ![signup](screenshots/signup.png) 15 | -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | 3 | version: '3' 4 | 5 | tasks: 6 | default: 7 | cmds: 8 | - echo {{.CLI_ARGS}} 9 | silent: true 10 | 11 | init: 12 | cmds: 13 | - npm install -g conventional-changelog-cli 14 | - npm install -g standard-version 15 | - npm install -g commitizen 16 | - cp envs/.env.local .env 17 | - yarn install 18 | 19 | changelog: 20 | cmds: 21 | - conventional-changelog -p angular -i CHANGELOG.md -s -r 0 22 | 23 | release: 24 | cmds: 25 | - git fetch 26 | - yarn release 27 | - task changelog 28 | - git add . 29 | - git cz 30 | 31 | commit: 32 | cmds: 33 | - git add . 34 | - git cz 35 | - git push 36 | 37 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | // config.js 2 | 3 | module.exports = { 4 | /* 5 | * 继承自@commitlint/config-conventional@commitlint/config-conventional 6 | */ 7 | extends: ['@commitlint/config-conventional'], 8 | /* 9 | * 使用@commitlint/format格式化 10 | */ 11 | formatter: '@commitlint/format', 12 | /* 13 | * 重新自定义校验规则 14 | */ 15 | rules: { 16 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']], 17 | }, 18 | /* 19 | * 排除校验情况 20 | */ 21 | ignores: [(commit) => commit === ''], 22 | /* 23 | * commitlint默认排除规则 24 | */ 25 | defaultIgnores: true, 26 | /* 27 | * 展示commit错误求助链接,配置无效 28 | */ 29 | helpUrl: 30 | '需要根据commitlint格式提交,请查看README了解提交方式', 31 | /* 32 | * 提示输入 33 | */ 34 | prompt: { 35 | messages: {}, 36 | questions: { 37 | type: { 38 | description: 'please input type:', 39 | }, 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /envs/.env.local: -------------------------------------------------------------------------------- 1 | NODE_ENV = local 2 | VITE_BUILD_ENV = development 3 | VITE_HOST = local.lantron.ltd 4 | VITE_PORT = 10086 5 | VITE_BASE_URL = ./ 6 | VITE_OUTPUT_DIR = dist 7 | VITE_APP_BASE_API = https://mock.local.lantron.ltd -------------------------------------------------------------------------------- /envs/.env.prod: -------------------------------------------------------------------------------- 1 | NODE_ENV = prod 2 | VITE_BUILD_ENV = production 3 | VITE_HOST = lantron.ltd 4 | VITE_PORT = 5 | VITE_BASE_URL = ./ 6 | VITE_OUTPUT_DIR = dist 7 | VITE_APP_BASE_API = https://mock.lantron.ltd -------------------------------------------------------------------------------- /envs/.env.stg: -------------------------------------------------------------------------------- 1 | NODE_ENV = stg 2 | VITE_BUILD_ENV = production 3 | VITE_HOST = stg.lantron.ltd 4 | VITE_PORT = 10086 5 | VITE_BASE_URL = ./ 6 | VITE_OUTPUT_DIR = dist 7 | VITE_APP_BASE_API = https://mock.stg.lantron.ltd -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite React Admin 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/" 4 | status = 200 -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | gzip on; 3 | listen 80; 4 | listen [::]:80; 5 | server_name localhost; 6 | 7 | location / { 8 | root /usr/share/nginx/html; 9 | index index.html index.htm; 10 | try_files $uri $uri/ /index.html; 11 | } 12 | 13 | location /admin { 14 | alias /usr/share/nginx/html/admin; 15 | index index.html index.htm; 16 | try_files $uri $uri/ /admin/index.html; 17 | } 18 | 19 | location @rewrites { 20 | rewrite ^(.*)$ /index.html last; 21 | } 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-ts-admin", 3 | "author": "@houko", 4 | "licence": "MIT", 5 | "version": "1.0.0", 6 | "private": false, 7 | "homepage": "https://reactZone.github.io/vite-react-ts-admin", 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=local vite --host", 10 | "dev:stg": "cross-env NODE_ENV=stg vite", 11 | "serve": "vite preview --host", 12 | "build": "vite build --mode prod", 13 | "build:stg": "cross-env vite build --mode test", 14 | "build:prod": "cross-env vite build --mode production", 15 | "deploy": "gh-pages -d dist", 16 | "lint": "eslint --ext .jsx,.js,.ts,.tsx .", 17 | "fix": "yarn lint --fix" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | }, 31 | "dependencies": { 32 | "@emotion/react": "^11.11.3", 33 | "@emotion/styled": "^11.11.0", 34 | "@faker-js/faker": "^7.6.0", 35 | "@iconify/icons-ant-design": "^1.2.5", 36 | "@iconify/icons-eva": "^1.2.6", 37 | "@iconify/icons-ic": "^1.2.13", 38 | "@iconify/react": "^4.1.0", 39 | "@mui/lab": "^5.0.0-alpha.158", 40 | "@mui/material": "^5.15.2", 41 | "@mui/styles": "^5.15.2", 42 | "@mui/utils": "^5.15.2", 43 | "@testing-library/jest-dom": "^5.16.5", 44 | "apexcharts": "^3.37.0", 45 | "change-case": "^4.1.2", 46 | "date-fns": "^2.29.3", 47 | "formik": "^2.2.9", 48 | "framer-motion": "^9.0.4", 49 | "history": "^5.3.0", 50 | "lodash": "^4.17.21", 51 | "numeral": "^2.0.6", 52 | "react": "^18.2.0", 53 | "react-apexcharts": "^1.4.0", 54 | "react-dom": "^18.2.0", 55 | "react-helmet-async": "^1.3.0", 56 | "react-router-dom": "^6.8.1", 57 | "react-scripts": "5.0.1", 58 | "simplebar": "^6.2.1", 59 | "simplebar-react": "^3.2.1", 60 | "web-vitals": "^3.1.1", 61 | "yup": "^1.0.0" 62 | }, 63 | "devDependencies": { 64 | "@commitlint/config-conventional": "^17.4.4", 65 | "@types/node": "^18.14.0", 66 | "@types/react": "^18.0.28", 67 | "@types/react-dom": "^18.0.11", 68 | "@types/react-router-dom": "^5.3.3", 69 | "@typescript-eslint/eslint-plugin": "^5.52.0", 70 | "@typescript-eslint/parser": "^5.52.0", 71 | "@vitejs/plugin-react": "^4.2.1", 72 | "commitlint": "^17.4.4", 73 | "cross-env": "^7.0.3", 74 | "eslint": "^8.34.0", 75 | "eslint-config-airbnb": "^19.0.4", 76 | "eslint-config-prettier": "^8.6.0", 77 | "eslint-plugin-import": "^2.27.5", 78 | "eslint-plugin-jsx-a11y": "^6.7.1", 79 | "eslint-plugin-prettier": "^4.2.1", 80 | "eslint-plugin-react": "^7.32.2", 81 | "eslint-plugin-react-hooks": "^4.6.0", 82 | "gh-pages": "^5.0.0", 83 | "prettier": "^2.8.4", 84 | "typescript": "^4.9.5", 85 | "vite": "^4.1.3" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /screenshots/blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/screenshots/blog.png -------------------------------------------------------------------------------- /screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/screenshots/dashboard.png -------------------------------------------------------------------------------- /screenshots/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/screenshots/product.png -------------------------------------------------------------------------------- /screenshots/signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/screenshots/signin.png -------------------------------------------------------------------------------- /screenshots/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/screenshots/signup.png -------------------------------------------------------------------------------- /screenshots/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/screenshots/user.png -------------------------------------------------------------------------------- /src/_mocks_/account.ts: -------------------------------------------------------------------------------- 1 | import { IAccount } from '@/models'; 2 | import Avatar from '@/assets/images/avatar_default.png'; 3 | 4 | const account: IAccount = { 5 | displayName: 'Jaydon Frankie', 6 | email: 'demo@minimals.cc', 7 | photoURL: Avatar, 8 | role: 'admin' 9 | }; 10 | 11 | export default account; 12 | -------------------------------------------------------------------------------- /src/_mocks_/blog.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { mockImgCover } from '@/utils/mockImages'; 3 | import { IPost } from '@/models'; 4 | 5 | const POST_TITLES = [ 6 | 'Whiteboard Templates By Industry Leaders', 7 | 'Tesla Cybertruck-inspired camper trailer for Tesla fans who can’t just wait for the truck!', 8 | 'Designify Agency Landing Page Design', 9 | '✨What is Done is Done ✨', 10 | 'Fresh Prince', 11 | 'Six Socks Studio', 12 | 'vincenzo de cotiis’ crossing over showcases a research on contamination', 13 | 'Simple, Great Looking Animations in Your Project | Video Tutorial', 14 | '40 Free Serif Fonts for Digital Designers', 15 | 'Examining the Evolution of the Typical Web Design Client', 16 | 'Katie Griffin loves making that homey art', 17 | 'The American Dream retold through mid-century railroad graphics', 18 | 'Illustration System Design', 19 | 'CarZio-Delivery Driver App SignIn/SignUp', 20 | 'How to create a client-serverless Jamstack app using Netlify, Gatsby and Fauna', 21 | 'Tylko Organise effortlessly -3D & Motion Design', 22 | 'RAYO ?? A expanded visual arts festival identity', 23 | 'Anthony Burrill and Wired mag’s Andrew Diprose discuss how they made January’s Change Everything cover', 24 | 'Inside the Mind of Samuel Day', 25 | 'Portfolio Review: Is This Portfolio Too Creative?', 26 | 'Akkers van Margraten', 27 | 'Gradient Ticket icon', 28 | 'Here’s a Dyson motorcycle concept that doesn’t ‘suck’!', 29 | 'How to Animate a SVG with border-image' 30 | ]; 31 | 32 | const baseUrl = 'https://image.xiaomo.info/mock'; 33 | const posts: IPost[] = [...Array(23)].map((_, index) => ({ 34 | id: faker.datatype.uuid(), 35 | cover: mockImgCover(index + 1), 36 | title: POST_TITLES[index + 1], 37 | createdAt: faker.date.past(), 38 | view: faker.datatype.number(), 39 | comment: faker.datatype.number(), 40 | share: faker.datatype.number(), 41 | favorite: faker.datatype.number(), 42 | author: { 43 | name: faker.name.fullName(), 44 | avatarUrl: `${baseUrl}/avatar/avatar_${index + 1}.jpg` 45 | } 46 | })); 47 | 48 | export default posts; 49 | -------------------------------------------------------------------------------- /src/_mocks_/products.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { sample } from 'lodash'; 3 | import { mockImgProduct } from '@/utils/mockImages'; 4 | import { IProduct } from '@/models'; 5 | 6 | const PRODUCT_NAME = [ 7 | 'Nike Air Force 1 NDESTRUKT', 8 | 'Nike Space Hippie 04', 9 | 'Nike Air Zoom Pegasus 37 A.I.R. Chaz Bear', 10 | 'Nike Blazer Low 77 Vintage', 11 | 'Nike ZoomX SuperRep Surge', 12 | 'Zoom Freak 2', 13 | 'Nike Air Max Zephyr', 14 | 'Jordan Delta', 15 | 'Air Jordan XXXV PF', 16 | 'Nike Waffle Racer Crater', 17 | 'Kyrie 7 EP Sisterhood', 18 | 'Nike Air Zoom BB NXT', 19 | 'Nike Air Force 1 07 LX', 20 | 'Nike Air Force 1 Shadow SE', 21 | 'Nike Air Zoom Tempo NEXT%', 22 | 'Nike DBreak-Type', 23 | 'Nike Air Max Up', 24 | 'Nike Air Max 270 React ENG', 25 | 'NikeCourt Royale', 26 | 'Nike Air Zoom Pegasus 37 Premium', 27 | 'Nike Air Zoom SuperRep', 28 | 'NikeCourt Royale', 29 | 'Nike React Art3mis', 30 | 'Nike React Infinity Run Flyknit A.I.R. Chaz Bear' 31 | ]; 32 | const PRODUCT_COLOR = [ 33 | '#00AB55', 34 | '#000000', 35 | '#FFFFFF', 36 | '#FFC0CB', 37 | '#FF4842', 38 | '#1890FF', 39 | '#94D82D', 40 | '#FFC107' 41 | ]; 42 | 43 | // ---------------------------------------------------------------------- 44 | 45 | const products: IProduct[] = [...Array(24)].map((_, index) => { 46 | const setIndex = index + 1; 47 | 48 | return { 49 | id: faker.datatype.uuid() as string, 50 | cover: mockImgProduct(setIndex), 51 | name: PRODUCT_NAME[index], 52 | price: faker.datatype.number({ min: 4, max: 99, precision: 0.01 }) as number, 53 | priceSale: 54 | setIndex % 3 55 | ? null 56 | : (faker.datatype.number({ min: 19, max: 29, precision: 0.01 }) as number), 57 | colors: 58 | (setIndex === 1 && PRODUCT_COLOR.slice(0, 2)) || 59 | (setIndex === 2 && PRODUCT_COLOR.slice(1, 3)) || 60 | (setIndex === 3 && PRODUCT_COLOR.slice(2, 4)) || 61 | (setIndex === 4 && PRODUCT_COLOR.slice(3, 6)) || 62 | (setIndex === 23 && PRODUCT_COLOR.slice(4, 6)) || 63 | (setIndex === 24 && PRODUCT_COLOR.slice(5, 6)) || 64 | PRODUCT_COLOR, 65 | status: sample(['sale', 'new', '', '']) 66 | }; 67 | }); 68 | 69 | export default products; 70 | -------------------------------------------------------------------------------- /src/_mocks_/user.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { sample } from 'lodash'; 3 | import { mockImgAvatar } from '@/utils/mockImages'; 4 | import { IUser } from '@/models'; 5 | 6 | const users: IUser[] = [...Array(24)].map((_, index) => ({ 7 | id: faker.datatype.uuid() as string, 8 | avatarUrl: mockImgAvatar(index + 1), 9 | name: faker.name.fullName() as string, 10 | company: faker.company.name() as string, 11 | isVerified: faker.datatype.boolean() as boolean, 12 | status: sample(['active', 'banned']), 13 | role: sample([ 14 | 'Leader', 15 | 'Hr Manager', 16 | 'UI Designer', 17 | 'UX Designer', 18 | 'UI/UX Designer', 19 | 'Project Manager', 20 | 'Backend Developer', 21 | 'Full Stack Designer', 22 | 'Front End Developer', 23 | 'Full Stack Developer' 24 | ]) 25 | })); 26 | 27 | export default users; 28 | -------------------------------------------------------------------------------- /src/assets/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/favicon/favicon.ico -------------------------------------------------------------------------------- /src/assets/images/avatar_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/images/avatar_default.png -------------------------------------------------------------------------------- /src/assets/images/icons/ic_flag_de.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/ic_flag_en.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/ic_flag_fr.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/ic_notification_chat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/ic_notification_mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/ic_notification_package.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/ic_notification_shipping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/shape-avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/illustrations/illustration_404.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 14 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/assets/images/illustrations/illustration_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/images/illustrations/illustration_avatar.png -------------------------------------------------------------------------------- /src/assets/images/illustrations/illustration_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/images/illustrations/illustration_login.png -------------------------------------------------------------------------------- /src/assets/images/illustrations/illustration_register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/images/illustrations/illustration_register.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yldm-tech/vite-react-ts-admin-template/f014bbd109a293ef31e11f351285d997145a64c6/src/assets/images/preview.png -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Minimal App", 3 | "name": "React Material Minimal UI Kit", 4 | "icons": [ 5 | { 6 | "src": "favicon/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "favicon/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /src/components/@material-extend/MHidden.ts: -------------------------------------------------------------------------------- 1 | import { Theme, useMediaQuery } from '@mui/material'; 2 | 3 | interface Props { 4 | width: string; 5 | children; 6 | theme?; 7 | } 8 | 9 | export const MHidden = (props: Props): JSX.Element | null => { 10 | const { width, children } = props; 11 | const breakpoint = width.substring(0, 2) as unknown as number; 12 | 13 | const hiddenUp = useMediaQuery((theme: Theme) => theme.breakpoints.up(breakpoint)); 14 | const hiddenDown = useMediaQuery((theme: Theme) => theme.breakpoints.down(breakpoint)); 15 | 16 | if (width.includes('Down')) { 17 | return hiddenDown ? null : children; 18 | } 19 | 20 | if (width.includes('Up')) { 21 | return hiddenUp ? null : children; 22 | } 23 | 24 | return null; 25 | }; 26 | 27 | export default MHidden; 28 | -------------------------------------------------------------------------------- /src/components/@material-extend/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MHidden } from './MHidden'; 2 | -------------------------------------------------------------------------------- /src/components/ColorManyPicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import checkmarkFill from '@iconify/icons-eva/checkmark-fill'; 4 | import { Box, Checkbox } from '@mui/material'; 5 | 6 | interface IconColorProps { 7 | sx?; 8 | other?; 9 | } 10 | 11 | function IconColor(props: IconColorProps) { 12 | const { sx, ...other } = props; 13 | return ( 14 | 25 | theme.transitions.create('all', { 26 | duration: theme.transitions.duration.shortest 27 | }), 28 | ...sx 29 | }} 30 | {...other} 31 | > 32 | 33 | 34 | ); 35 | } 36 | 37 | interface Props { 38 | name?; 39 | colors: string[]; 40 | onChecked: (color: string) => boolean; 41 | sx; 42 | onChange?; 43 | } 44 | 45 | const ColorManyPicker = (props: Props): JSX.Element => { 46 | const { colors, onChecked, sx, ...other } = props; 47 | return ( 48 | 49 | {colors.map((color) => { 50 | const isWhite = color === '#FFFFFF' || color === 'white'; 51 | 52 | return ( 53 | `solid 1px ${theme.palette.divider}` 64 | }) 65 | }} 66 | /> 67 | } 68 | checkedIcon={ 69 | `solid 1px ${theme.palette.divider}`, 83 | boxShadow: (theme) => 84 | `4px 4px 8px 0 ${theme.palette.grey[500_24]}`, 85 | '& svg': { width: 12, height: 12, color: 'common.black' } 86 | }) 87 | }} 88 | /> 89 | } 90 | sx={{ 91 | color, 92 | '&:hover': { opacity: 0.72 } 93 | }} 94 | {...other} 95 | /> 96 | ); 97 | })} 98 | 99 | ); 100 | }; 101 | 102 | export default ColorManyPicker; 103 | -------------------------------------------------------------------------------- /src/components/ColorPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { alpha, styled } from '@mui/material/styles'; 3 | import { Box, Typography } from '@mui/material'; 4 | 5 | const RootStyle = styled(Box)({ 6 | display: 'flex', 7 | alignItems: 'center', 8 | justifyContent: 'flex-end' 9 | }); 10 | 11 | const IconStyle = styled('div')(({ theme }) => ({ 12 | marginLeft: -4, 13 | borderRadius: '50%', 14 | width: theme.spacing(2), 15 | height: theme.spacing(2), 16 | border: `solid 2px ${theme.palette.background.paper}`, 17 | boxShadow: `inset -1px 1px 2px ${alpha(theme.palette.common.black, 0.24)}` 18 | })); 19 | 20 | interface Props { 21 | colors: string[]; 22 | limit?: number; 23 | } 24 | 25 | export const ColorPreview = (props: Props): JSX.Element => { 26 | const { colors, limit = 3, ...other } = props; 27 | const showColor = colors.slice(0, limit); 28 | const moreColor = colors.length - limit; 29 | 30 | return ( 31 | 32 | {showColor.map((color, index) => ( 33 | 34 | ))} 35 | 36 | {colors.length > limit && ( 37 | {`+${moreColor}`} 38 | )} 39 | 40 | ); 41 | }; 42 | 43 | export default ColorPreview; 44 | -------------------------------------------------------------------------------- /src/components/Label.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { alpha, styled } from '@mui/material/styles'; 3 | 4 | const RootStyle = styled('span')(({ theme, styleProps }) => { 5 | const { color, variant } = styleProps; 6 | 7 | const styleFilled = (color) => ({ 8 | color: theme.palette[color].contrastText, 9 | backgroundColor: theme.palette[color].main 10 | }); 11 | 12 | const styleOutlined = (color) => ({ 13 | color: theme.palette[color].main, 14 | backgroundColor: 'transparent', 15 | border: `1px solid ${theme.palette[color].main}` 16 | }); 17 | 18 | const styleGhost = (color) => ({ 19 | color: theme.palette[color].dark, 20 | backgroundColor: alpha(theme.palette[color].main, 0.16) 21 | }); 22 | 23 | return { 24 | height: 22, 25 | minWidth: 22, 26 | lineHeight: 0, 27 | borderRadius: 8, 28 | cursor: 'default', 29 | alignItems: 'center', 30 | whiteSpace: 'nowrap', 31 | display: 'inline-flex', 32 | justifyContent: 'center', 33 | padding: theme.spacing(0, 1), 34 | color: theme.palette.grey[800], 35 | fontSize: theme.typography.pxToRem(12), 36 | fontFamily: theme.typography.fontFamily, 37 | backgroundColor: theme.palette.grey[300], 38 | fontWeight: theme.typography.fontWeightBold, 39 | 40 | ...(color !== 'default' 41 | ? { 42 | ...(variant === 'filled' && { ...styleFilled(color) }), 43 | ...(variant === 'outlined' && { ...styleOutlined(color) }), 44 | ...(variant === 'ghost' && { ...styleGhost(color) }) 45 | } 46 | : { 47 | ...(variant === 'outlined' && { 48 | backgroundColor: 'transparent', 49 | color: theme.palette.text.primary, 50 | border: `1px solid ${theme.palette.grey[500_32]}` 51 | }), 52 | ...(variant === 'ghost' && { 53 | color: theme.palette.text.secondary, 54 | backgroundColor: theme.palette.grey[500_16] 55 | }) 56 | }) 57 | }; 58 | }); 59 | 60 | interface Props { 61 | color?: 'default' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'; 62 | variant?: 'filled' | 'outlined' | 'ghost'; 63 | children?: JSX.Element; 64 | other?; 65 | sx?; 66 | } 67 | 68 | const Label = (props: Props): JSX.Element => { 69 | const { color = 'default', variant = 'ghost', children, ...other } = props; 70 | return ( 71 | 72 | {children} 73 | 74 | ); 75 | }; 76 | 77 | export default Label; 78 | -------------------------------------------------------------------------------- /src/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box } from '@mui/material'; 3 | import LogoImage from '@/assets/images/logo.svg'; 4 | 5 | interface Props { 6 | sx?; 7 | } 8 | 9 | const Logo = (props: Props): JSX.Element => { 10 | const { sx } = props; 11 | return ; 12 | }; 13 | 14 | export default Logo; 15 | -------------------------------------------------------------------------------- /src/components/MenuPopover.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Popover } from '@mui/material'; 3 | import { alpha, styled } from '@mui/material/styles'; 4 | 5 | const ArrowStyle = styled('span')(({ theme }) => ({ 6 | [theme.breakpoints.up('sm')]: { 7 | top: -7, 8 | zIndex: 1, 9 | width: 12, 10 | right: 20, 11 | height: 12, 12 | position: 'absolute', 13 | borderRadius: '0 0 4px 0', 14 | transform: 'rotate(-135deg)', 15 | background: theme.palette.background.paper, 16 | borderRight: `solid 1px ${alpha(theme.palette.grey[500], 0.12)}`, 17 | borderBottom: `solid 1px ${alpha(theme.palette.grey[500], 0.12)}` 18 | } 19 | })); 20 | 21 | interface Props { 22 | children?; 23 | sx?; 24 | open?; 25 | onClose?; 26 | anchorEl?; 27 | } 28 | 29 | const MenuPopover = (props: Props): JSX.Element => { 30 | const { open, children, ...other } = props; 31 | return ( 32 | theme.customShadows.z20, 42 | border: (theme) => `solid 1px ${theme.palette.grey[5008]}`, 43 | width: 200 44 | } 45 | }} 46 | {...other} 47 | > 48 | 49 | 50 | {children} 51 | 52 | ); 53 | }; 54 | 55 | export default MenuPopover; 56 | -------------------------------------------------------------------------------- /src/components/NavSection.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import { NavLink as RouterLink, matchPath, useLocation } from 'react-router-dom'; 4 | import arrowIosForwardFill from '@iconify/icons-eva/arrow-ios-forward-fill'; 5 | import arrowIosDownwardFill from '@iconify/icons-eva/arrow-ios-downward-fill'; 6 | import { alpha, useTheme, styled } from '@mui/material/styles'; 7 | import { Box, List, Collapse, ListItemText, ListItemIcon, ListItemButton } from '@mui/material'; 8 | import { NavItemConfig } from '@/models'; 9 | 10 | const ListItemStyle = styled((props) => )( 11 | ({ theme }) => ({ 12 | ...theme.typography.body2, 13 | height: 48, 14 | position: 'relative', 15 | textTransform: 'capitalize', 16 | paddingLeft: theme.spacing(5), 17 | paddingRight: theme.spacing(2.5), 18 | color: theme.palette.text.secondary, 19 | '&:before': { 20 | top: 0, 21 | right: 0, 22 | width: 3, 23 | bottom: 0, 24 | display: 'none', 25 | position: 'absolute', 26 | borderTopLeftRadius: 4, 27 | borderBottomLeftRadius: 4, 28 | backgroundColor: theme.palette.primary.main 29 | } 30 | }) 31 | ); 32 | 33 | const ListItemIconStyle = styled(ListItemIcon)({ 34 | width: 22, 35 | height: 22, 36 | display: 'flex', 37 | alignItems: 'center', 38 | justifyContent: 'center' 39 | }); 40 | 41 | interface NavItemProps { 42 | item: NavItemConfig; 43 | active: (path: string) => boolean; 44 | } 45 | 46 | function NavItem(props: NavItemProps) { 47 | const { item, active } = props; 48 | const theme = useTheme(); 49 | const isActiveRoot = active(item.path); 50 | const { title, path, icon, info, children } = item; 51 | const [open, setOpen] = useState(isActiveRoot); 52 | 53 | const handleOpen = () => { 54 | setOpen((prev) => !prev); 55 | }; 56 | 57 | const activeRootStyle = { 58 | color: 'primary.main', 59 | fontWeight: 'fontWeightMedium', 60 | bgcolor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), 61 | '&:before': { display: 'block' } 62 | }; 63 | 64 | const activeSubStyle = { 65 | color: 'text.primary', 66 | fontWeight: 'fontWeightMedium' 67 | }; 68 | 69 | if (children) { 70 | return ( 71 | <> 72 | 78 | {icon && icon} 79 | 80 | {info && info} 81 | 86 | 87 | 88 | 89 | 90 | {children.map((item) => { 91 | const { title, path } = item; 92 | const isActiveSub = active(path); 93 | 94 | return ( 95 | 103 | 104 | 115 | theme.transitions.create('transform'), 116 | ...(isActiveSub && { 117 | transform: 'scale(2)', 118 | bgcolor: 'primary.main' 119 | }) 120 | }} 121 | /> 122 | 123 | 124 | 125 | ); 126 | })} 127 | 128 | 129 | 130 | ); 131 | } 132 | 133 | return ( 134 | 141 | {icon && icon} 142 | 143 | {info && info} 144 | 145 | ); 146 | } 147 | 148 | interface Props { 149 | navConfig: NavItemConfig[]; 150 | other?; 151 | } 152 | 153 | const NavSection = (props: Props): JSX.Element => { 154 | const { navConfig, ...other } = props; 155 | const { pathname } = useLocation(); 156 | const match = (path) => (path ? !!matchPath({ path, end: false }, pathname) : false); 157 | 158 | return ( 159 | 160 | 161 | {navConfig.map((item) => ( 162 | 163 | ))} 164 | 165 | 166 | ); 167 | }; 168 | 169 | export default NavSection; 170 | -------------------------------------------------------------------------------- /src/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet-async'; 2 | import React, { forwardRef } from 'react'; 3 | import { Box } from '@mui/material'; 4 | 5 | interface Props { 6 | children?; 7 | title?; 8 | other?; 9 | } 10 | 11 | const Page = forwardRef(function Page(props: Props, ref) { 12 | const { children, title = '', ...other } = props; 13 | return ( 14 | 15 | 16 | {title} 17 | 18 | {children} 19 | 20 | ); 21 | }); 22 | 23 | export default Page; 24 | -------------------------------------------------------------------------------- /src/components/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | 4 | const ScrollToTop = (): JSX.Element => { 5 | const { pathname } = useLocation(); 6 | 7 | useEffect(() => { 8 | window.scrollTo(0, 0); 9 | }, [pathname]); 10 | 11 | return ; 12 | }; 13 | export default ScrollToTop; 14 | -------------------------------------------------------------------------------- /src/components/Scrollbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SimpleBarReact from 'simplebar-react'; 3 | import { alpha, styled } from '@mui/material/styles'; 4 | import { Box } from '@mui/material'; 5 | 6 | const RootStyle = styled('div')({ 7 | flexGrow: 1, 8 | height: '100%', 9 | overflow: 'hidden' 10 | }); 11 | 12 | const SimpleBarStyle = styled(SimpleBarReact)(({ theme }) => ({ 13 | maxHeight: '100%', 14 | '& .simplebar-scrollbar': { 15 | '&:before': { 16 | backgroundColor: alpha(theme.palette.grey[600], 0.48) 17 | }, 18 | '&.simplebar-visible:before': { 19 | opacity: 1 20 | } 21 | }, 22 | '& .simplebar-track.simplebar-vertical': { 23 | width: 10 24 | }, 25 | '& .simplebar-track.simplebar-horizontal .simplebar-scrollbar': { 26 | height: 6 27 | }, 28 | '& .simplebar-mask': { 29 | zIndex: 'inherit' 30 | } 31 | })); 32 | 33 | interface Props { 34 | children?; 35 | sx?; 36 | other?; 37 | } 38 | 39 | const Scrollbar = (props: Props): JSX.Element => { 40 | const { children, sx, other } = props; 41 | const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 42 | navigator.userAgent 43 | ); 44 | 45 | if (isMobile) { 46 | return ( 47 | 48 | {children} 49 | 50 | ); 51 | } 52 | 53 | return ( 54 | 55 | 56 | {children} 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default Scrollbar; 63 | -------------------------------------------------------------------------------- /src/components/SearchNotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Paper, Typography } from '@mui/material'; 3 | 4 | interface SearchNotFoundProps { 5 | searchQuery: string; 6 | other?; 7 | } 8 | 9 | const SearchNotFound = (props: SearchNotFoundProps): JSX.Element => { 10 | const { searchQuery = '', ...other } = props; 11 | return ( 12 | 13 | 14 | Not found 15 | 16 | 17 | No results found for   18 | "{searchQuery}". Try checking for typos or using complete 19 | words. 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default SearchNotFound; 26 | -------------------------------------------------------------------------------- /src/components/SvgIconStyle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box } from '@mui/material'; 3 | 4 | interface Props { 5 | src: string; 6 | color: string; 7 | sx?; 8 | } 9 | 10 | const SvgIconStyle = (props: Props): JSX.Element => { 11 | const { src, color = 'inherit', sx } = props; 12 | return ( 13 | 28 | ); 29 | }; 30 | 31 | export default SvgIconStyle; 32 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppBugReports.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import bugFilled from '@iconify/icons-ant-design/bug-filled'; 4 | import { alpha, styled } from '@mui/material/styles'; 5 | import { Card, Typography } from '@mui/material'; 6 | import { fShortenNumber } from '@/utils/formatNumber'; 7 | 8 | const RootStyle = styled(Card)(({ theme }) => ({ 9 | boxShadow: 'none', 10 | textAlign: 'center', 11 | padding: theme.spacing(5, 0) 12 | // color: theme.palette.error.darker, 13 | // backgroundColor: theme.palette.error.lighter 14 | })); 15 | 16 | const IconWrapperStyle = styled('div')(({ theme }) => ({ 17 | margin: 'auto', 18 | display: 'flex', 19 | borderRadius: '50%', 20 | alignItems: 'center', 21 | width: theme.spacing(8), 22 | height: theme.spacing(8), 23 | justifyContent: 'center', 24 | marginBottom: theme.spacing(3), 25 | color: theme.palette.error.dark, 26 | backgroundImage: `linear-gradient(135deg, ${alpha(theme.palette.error.dark, 0)} 0%, ${alpha( 27 | theme.palette.error.dark, 28 | 0.24 29 | )} 100%)` 30 | })); 31 | 32 | const TOTAL = 234; 33 | 34 | const AppBugReports = (): JSX.Element => { 35 | return ( 36 | 37 | 38 | 39 | 40 | {fShortenNumber(TOTAL)} 41 | 42 | Bug Reports 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default AppBugReports; 49 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppConversionRates.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { merge } from 'lodash'; 3 | import ReactApexChart from 'react-apexcharts'; 4 | import { Box, Card, CardHeader } from '@mui/material'; 5 | import { fNumber } from '@/utils/formatNumber'; 6 | import { BaseOptionChart } from '@/components/charts'; 7 | import { ApexOptions } from 'apexcharts'; 8 | 9 | const CHART_DATA = [{ data: [400, 430, 448, 470, 540, 580, 690, 1100, 1200, 1380] }]; 10 | 11 | const AppConversionRates = (): JSX.Element => { 12 | const chartOptions: ApexOptions = merge(BaseOptionChart(), { 13 | tooltip: { 14 | marker: { show: false }, 15 | y: { 16 | formatter: (seriesName) => fNumber(seriesName), 17 | title: { 18 | formatter: (seriesName) => `#${seriesName}` 19 | } 20 | } 21 | }, 22 | plotOptions: { 23 | bar: { horizontal: true, barHeight: '28%', borderRadius: 2 } 24 | }, 25 | xaxis: { 26 | categories: [ 27 | 'Italy', 28 | 'Japan', 29 | 'China', 30 | 'Canada', 31 | 'France', 32 | 'Germany', 33 | 'South Korea', 34 | 'Netherlands', 35 | 'United States', 36 | 'United Kingdom' 37 | ] 38 | } 39 | }); 40 | 41 | return ( 42 | 43 | 44 | 45 | 51 | 52 | 53 | ); 54 | }; 55 | 56 | export default AppConversionRates; 57 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppCurrentSubject.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { merge } from 'lodash'; 3 | import ReactApexChart from 'react-apexcharts'; 4 | import { useTheme, styled } from '@mui/material/styles'; 5 | import { Card, CardHeader } from '@mui/material'; 6 | import { BaseOptionChart } from '@/components/charts'; 7 | import { ApexOptions } from 'apexcharts'; 8 | 9 | const CHART_HEIGHT = 392; 10 | const LEGEND_HEIGHT = 72; 11 | 12 | const ChartWrapperStyle = styled('div')(({ theme }) => ({ 13 | height: CHART_HEIGHT, 14 | marginTop: theme.spacing(2), 15 | '& .apexcharts-canvas svg': { 16 | height: CHART_HEIGHT 17 | }, 18 | '& .apexcharts-canvas svg,.apexcharts-canvas foreignObject': { 19 | overflow: 'visible' 20 | }, 21 | '& .apexcharts-legend': { 22 | height: LEGEND_HEIGHT, 23 | alignContent: 'center', 24 | position: 'relative !important', 25 | borderTop: `solid 1px ${theme.palette.divider}`, 26 | top: `calc(${CHART_HEIGHT - LEGEND_HEIGHT}px) !important` 27 | } 28 | })); 29 | 30 | // ---------------------------------------------------------------------- 31 | 32 | const CHART_DATA = [ 33 | { name: 'Series 1', data: [80, 50, 30, 40, 100, 20] }, 34 | { name: 'Series 2', data: [20, 30, 40, 80, 20, 80] }, 35 | { name: 'Series 3', data: [44, 76, 78, 13, 43, 10] } 36 | ]; 37 | 38 | const AppCurrentSubject = (): JSX.Element => { 39 | const theme = useTheme(); 40 | 41 | const chartOptions: ApexOptions = merge(BaseOptionChart(), { 42 | stroke: { width: 2 }, 43 | fill: { opacity: 0.48 }, 44 | legend: { floating: true, horizontalAlign: 'center' }, 45 | xaxis: { 46 | categories: ['English', 'History', 'Physics', 'Geography', 'Chinese', 'Math'], 47 | labels: { 48 | style: { 49 | colors: [ 50 | theme.palette.text.secondary, 51 | theme.palette.text.secondary, 52 | theme.palette.text.secondary, 53 | theme.palette.text.secondary, 54 | theme.palette.text.secondary, 55 | theme.palette.text.secondary 56 | ] 57 | } 58 | } 59 | } 60 | }); 61 | 62 | return ( 63 | 64 | 65 | 66 | 72 | 73 | 74 | ); 75 | }; 76 | 77 | export default AppCurrentSubject; 78 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppCurrentVisits.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { merge } from 'lodash'; 3 | import ReactApexChart from 'react-apexcharts'; 4 | import { useTheme, styled } from '@mui/material/styles'; 5 | import { Card, CardHeader } from '@mui/material'; 6 | import { fNumber } from '@/utils/formatNumber'; 7 | import { BaseOptionChart } from '@/components/charts'; 8 | import { ApexOptions } from 'apexcharts'; 9 | 10 | const CHART_HEIGHT = 372; 11 | const LEGEND_HEIGHT = 72; 12 | 13 | const ChartWrapperStyle = styled('div')(({ theme }) => ({ 14 | height: CHART_HEIGHT, 15 | marginTop: theme.spacing(5), 16 | '& .apexcharts-canvas svg': { height: CHART_HEIGHT }, 17 | '& .apexcharts-canvas svg,.apexcharts-canvas foreignObject': { 18 | overflow: 'visible' 19 | }, 20 | '& .apexcharts-legend': { 21 | height: LEGEND_HEIGHT, 22 | alignContent: 'center', 23 | position: 'relative !important', 24 | borderTop: `solid 1px ${theme.palette.divider}`, 25 | top: `calc(${CHART_HEIGHT - LEGEND_HEIGHT}px) !important` 26 | } 27 | })); 28 | 29 | // ---------------------------------------------------------------------- 30 | 31 | const CHART_DATA = [4344, 5435, 1443, 4443]; 32 | 33 | const AppCurrentVisits = (): JSX.Element => { 34 | const theme = useTheme(); 35 | 36 | const chartOptions: ApexOptions = merge(BaseOptionChart(), { 37 | colors: [ 38 | theme.palette.primary.main, 39 | theme.palette.info.main, 40 | theme.palette.warning.main, 41 | theme.palette.error.main 42 | ], 43 | labels: ['America', 'Asia', 'Europe', 'Africa'], 44 | stroke: { colors: [theme.palette.background.paper] }, 45 | legend: { floating: true, horizontalAlign: 'center' }, 46 | dataLabels: { enabled: true, dropShadow: { enabled: false } }, 47 | tooltip: { 48 | fillSeriesColor: false, 49 | y: { 50 | formatter: (seriesName) => fNumber(seriesName), 51 | title: { 52 | formatter: (seriesName) => `#${seriesName}` 53 | } 54 | } 55 | }, 56 | plotOptions: { 57 | pie: { donut: { labels: { show: false } } } 58 | } 59 | }); 60 | 61 | return ( 62 | 63 | 64 | 65 | 71 | 72 | 73 | ); 74 | }; 75 | 76 | export default AppCurrentVisits; 77 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppItemOrders.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import windowsFilled from '@iconify/icons-ant-design/windows-filled'; 4 | import { alpha, styled } from '@mui/material/styles'; 5 | import { Card, Typography } from '@mui/material'; 6 | import { fShortenNumber } from '@/utils/formatNumber'; 7 | 8 | const RootStyle = styled(Card)(({ theme }) => ({ 9 | boxShadow: 'none', 10 | textAlign: 'center', 11 | padding: theme.spacing(5, 0) 12 | // color: theme.palette.warning.darker, 13 | // backgroundColor: theme.palette.warning.lighter 14 | })); 15 | 16 | const IconWrapperStyle = styled('div')(({ theme }) => ({ 17 | margin: 'auto', 18 | display: 'flex', 19 | borderRadius: '50%', 20 | alignItems: 'center', 21 | width: theme.spacing(8), 22 | height: theme.spacing(8), 23 | justifyContent: 'center', 24 | marginBottom: theme.spacing(3), 25 | color: theme.palette.warning.dark, 26 | backgroundImage: `linear-gradient(135deg, ${alpha(theme.palette.warning.dark, 0)} 0%, ${alpha( 27 | theme.palette.warning.dark, 28 | 0.24 29 | )} 100%)` 30 | })); 31 | 32 | const TOTAL = 1723315; 33 | 34 | const AppItemOrders = (): JSX.Element => { 35 | return ( 36 | 37 | 38 | 39 | 40 | {fShortenNumber(TOTAL)} 41 | 42 | Item Orders 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default AppItemOrders; 49 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppNewUsers.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import appleFilled from '@iconify/icons-ant-design/apple-filled'; 4 | import { alpha, styled } from '@mui/material/styles'; 5 | import { Card, Typography } from '@mui/material'; 6 | import { fShortenNumber } from '@/utils/formatNumber'; 7 | 8 | const RootStyle = styled(Card)(({ theme }) => ({ 9 | boxShadow: 'none', 10 | textAlign: 'center', 11 | padding: theme.spacing(5, 0) 12 | // color: theme.palette.info.darker, 13 | // backgroundColor: theme.palette.info.lighter 14 | })); 15 | 16 | const IconWrapperStyle = styled('div')(({ theme }) => ({ 17 | margin: 'auto', 18 | display: 'flex', 19 | borderRadius: '50%', 20 | alignItems: 'center', 21 | width: theme.spacing(8), 22 | height: theme.spacing(8), 23 | justifyContent: 'center', 24 | marginBottom: theme.spacing(3), 25 | color: theme.palette.info.dark, 26 | backgroundImage: `linear-gradient(135deg, ${alpha(theme.palette.info.dark, 0)} 0%, ${alpha( 27 | theme.palette.info.dark, 28 | 0.24 29 | )} 100%)` 30 | })); 31 | 32 | const TOTAL = 1352831; 33 | 34 | const AppNewUsers = (): JSX.Element => { 35 | return ( 36 | 37 | 38 | 39 | 40 | {fShortenNumber(TOTAL)} 41 | 42 | New Users 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default AppNewUsers; 49 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppNewsUpdate.tsx: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import React from 'react'; 3 | import { Icon } from '@iconify/react'; 4 | import { formatDistance } from 'date-fns'; 5 | import { Link as RouterLink } from 'react-router-dom'; 6 | import arrowIosForwardFill from '@iconify/icons-eva/arrow-ios-forward-fill'; 7 | import { Box, Stack, Link, Card, Button, Divider, Typography, CardHeader } from '@mui/material'; 8 | import { mockImgCover } from '@/utils/mockImages'; 9 | import Scrollbar from '../../Scrollbar'; 10 | import { News } from '@/models'; 11 | 12 | const NEWS = [...Array(5)].map((_, index) => { 13 | const setIndex = index + 1; 14 | return { 15 | title: faker.name.jobTitle(), 16 | description: faker.lorem.paragraphs(), 17 | image: mockImgCover(setIndex), 18 | postedAt: faker.date.soon() 19 | }; 20 | }); 21 | 22 | interface Props { 23 | news: News; 24 | } 25 | 26 | const NewsItem = (props: Props) => { 27 | const { news } = props; 28 | const { image, title, description, postedAt } = news; 29 | 30 | return ( 31 | 32 | 38 | 39 | 40 | 41 | {title} 42 | 43 | 44 | 45 | {description} 46 | 47 | 48 | 49 | {formatDistance(postedAt, new Date())} 50 | 51 | 52 | ); 53 | }; 54 | 55 | const AppNewsUpdate = (): JSX.Element => { 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | {NEWS.map((news) => ( 63 | 64 | ))} 65 | 66 | 67 | 68 | 69 | 70 | 71 | 80 | 81 | 82 | ); 83 | }; 84 | 85 | export default AppNewsUpdate; 86 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppOrderTimeline.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { faker } from '@faker-js/faker'; 3 | 4 | import { Card, Typography, CardHeader, CardContent } from '@mui/material'; 5 | import { 6 | Timeline, 7 | TimelineItem, 8 | TimelineContent, 9 | TimelineConnector, 10 | TimelineSeparator, 11 | TimelineDot 12 | } from '@mui/lab'; 13 | import { fDateTime } from '@/utils/formatTime'; 14 | 15 | const TIMELINES = [ 16 | { 17 | title: '1983, orders, $4220', 18 | time: faker.date.past(), 19 | type: 'order1' 20 | }, 21 | { 22 | title: '12 Invoices have been paid', 23 | time: faker.date.past(), 24 | type: 'order2' 25 | }, 26 | { 27 | title: 'Order #37745 from September', 28 | time: faker.date.past(), 29 | type: 'order3' 30 | }, 31 | { 32 | title: 'New order placed #XF-2356', 33 | time: faker.date.past(), 34 | type: 'order4' 35 | }, 36 | { 37 | title: 'New order placed #XF-2346', 38 | time: faker.date.past(), 39 | type: 'order5' 40 | } 41 | ]; 42 | 43 | interface Props { 44 | item; 45 | isLast: boolean; 46 | } 47 | 48 | function OrderItem(props: Props) { 49 | const { item, isLast } = props; 50 | const { type, title, time } = item; 51 | return ( 52 | 53 | 54 | 64 | {isLast ? null : } 65 | 66 | 67 | {title} 68 | 69 | {fDateTime(time)} 70 | 71 | 72 | 73 | ); 74 | } 75 | 76 | const AppOrderTimeline = (): JSX.Element => { 77 | return ( 78 | 85 | 86 | 87 | 88 | {TIMELINES.map((item, index) => ( 89 | 94 | ))} 95 | 96 | 97 | 98 | ); 99 | }; 100 | 101 | export default AppOrderTimeline; 102 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppTasks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, FormikProvider, useFormik } from 'formik'; 3 | import { 4 | Box, 5 | Card, 6 | Checkbox, 7 | CardHeader, 8 | Typography, 9 | FormControlLabel, 10 | Stack 11 | } from '@mui/material'; 12 | 13 | const TASKS = [ 14 | 'Create FireStone Logo', 15 | 'Add SCSS and JS files if required', 16 | 'Stakeholder Meeting', 17 | 'Scoping & Estimations', 18 | 'Sprint Showcase' 19 | ]; 20 | 21 | interface Props { 22 | task; 23 | checked; 24 | formik; 25 | other?; 26 | } 27 | 28 | const TaskItem = (props: Props) => { 29 | const { getFieldProps } = props.formik; 30 | 31 | return ( 32 | 33 | 41 | } 42 | label={ 43 | 52 | {props.task} 53 | 54 | } 55 | /> 56 | 57 | ); 58 | }; 59 | 60 | export const AppTasks = (): JSX.Element => { 61 | const formik = useFormik({ 62 | initialValues: { 63 | checked: [TASKS[2]] 64 | }, 65 | onSubmit: (values) => { 66 | console.log(values); 67 | } 68 | }); 69 | 70 | const { values, handleSubmit } = formik; 71 | 72 | return ( 73 | 74 | 75 | 76 | 77 |
78 | {TASKS.map((task) => ( 79 | 85 | ))} 86 | 87 |
88 |
89 |
90 | ); 91 | }; 92 | 93 | export default AppTasks; 94 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppTrafficBySite.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { faker } from '@faker-js/faker'; 3 | import { Icon } from '@iconify/react'; 4 | import googleFill from '@iconify/icons-eva/google-fill'; 5 | import twitterFill from '@iconify/icons-eva/twitter-fill'; 6 | import facebookFill from '@iconify/icons-eva/facebook-fill'; 7 | import linkedinFill from '@iconify/icons-eva/linkedin-fill'; 8 | import { Box, Grid, Card, Paper, Typography, CardHeader, CardContent } from '@mui/material'; 9 | import { fShortenNumber } from '@/utils/formatNumber'; 10 | 11 | const SOCIALS = [ 12 | { 13 | name: 'FaceBook', 14 | value: faker.datatype.number(), 15 | icon: 16 | }, 17 | { 18 | name: 'Google', 19 | value: faker.datatype.number(), 20 | icon: 21 | }, 22 | { 23 | name: 'Linkedin', 24 | value: faker.datatype.number(), 25 | icon: 26 | }, 27 | { 28 | name: 'Twitter', 29 | value: faker.datatype.number(), 30 | icon: 31 | } 32 | ]; 33 | 34 | interface Props { 35 | site; 36 | } 37 | 38 | function SiteItem(props: Props) { 39 | const { site } = props; 40 | const { icon, value, name } = site; 41 | 42 | return ( 43 | 44 | 45 | {icon} 46 | {fShortenNumber(value)} 47 | 48 | {name} 49 | 50 | 51 | 52 | ); 53 | } 54 | 55 | export const AppTrafficBySite = (): JSX.Element => { 56 | return ( 57 | 58 | 59 | 60 | 61 | {SOCIALS.map((site) => ( 62 | 63 | ))} 64 | 65 | 66 | 67 | ); 68 | }; 69 | 70 | export default AppTrafficBySite; 71 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppWebsiteVisits.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { merge } from 'lodash'; 3 | import ReactApexChart from 'react-apexcharts'; 4 | import { Card, CardHeader, Box } from '@mui/material'; 5 | import { BaseOptionChart } from '../../charts'; 6 | import { ApexOptions } from 'apexcharts'; 7 | 8 | const CHART_DATA = [ 9 | { 10 | name: 'Team A', 11 | type: 'column', 12 | data: [23, 11, 22, 27, 13, 22, 37, 21, 44, 22, 30] 13 | }, 14 | { 15 | name: 'Team B', 16 | type: 'area', 17 | data: [44, 55, 41, 67, 22, 43, 21, 41, 56, 27, 43] 18 | }, 19 | { 20 | name: 'Team C', 21 | type: 'line', 22 | data: [30, 25, 36, 30, 45, 35, 64, 52, 59, 36, 39] 23 | } 24 | ]; 25 | 26 | export const AppWebsiteVisits = (): JSX.Element => { 27 | const chartOptions: ApexOptions = merge(BaseOptionChart(), { 28 | stroke: { width: [0, 2, 3] }, 29 | plotOptions: { bar: { columnWidth: '11%', borderRadius: 4 } }, 30 | fill: { type: ['solid', 'gradient', 'solid'] }, 31 | labels: [ 32 | '01/01/2003', 33 | '02/01/2003', 34 | '03/01/2003', 35 | '04/01/2003', 36 | '05/01/2003', 37 | '06/01/2003', 38 | '07/01/2003', 39 | '08/01/2003', 40 | '09/01/2003', 41 | '10/01/2003', 42 | '11/01/2003' 43 | ], 44 | xaxis: { type: 'datetime' }, 45 | tooltip: { 46 | shared: true, 47 | intersect: false, 48 | y: { 49 | formatter: (y) => { 50 | if (typeof y !== 'undefined') { 51 | return `${y.toFixed(0)} visits`; 52 | } 53 | return y; 54 | } 55 | } 56 | } 57 | }); 58 | 59 | return ( 60 | 61 | 62 | 63 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | export default AppWebsiteVisits; 75 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/AppWeeklySales.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import androidFilled from '@iconify/icons-ant-design/android-filled'; 4 | import { alpha, styled } from '@mui/material/styles'; 5 | import { Card, Typography } from '@mui/material'; 6 | import { fShortenNumber } from '@/utils/formatNumber'; 7 | 8 | const RootStyle = styled(Card)(({ theme }) => ({ 9 | boxShadow: 'none', 10 | textAlign: 'center', 11 | padding: theme.spacing(5, 0) 12 | // color: theme.palette.primary.darker, 13 | // backgroundColor: theme.palette.primary.lighter 14 | })); 15 | 16 | const IconWrapperStyle = styled('div')(({ theme }) => ({ 17 | margin: 'auto', 18 | display: 'flex', 19 | borderRadius: '50%', 20 | alignItems: 'center', 21 | width: theme.spacing(8), 22 | height: theme.spacing(8), 23 | justifyContent: 'center', 24 | marginBottom: theme.spacing(3), 25 | color: theme.palette.primary.dark, 26 | backgroundImage: `linear-gradient(135deg, ${alpha(theme.palette.primary.dark, 0)} 0%, ${alpha( 27 | theme.palette.primary.dark, 28 | 0.24 29 | )} 100%)` 30 | })); 31 | 32 | // ---------------------------------------------------------------------- 33 | 34 | const TOTAL = 714000; 35 | 36 | export const AppWeeklySales = (): JSX.Element => { 37 | return ( 38 | 39 | 40 | 41 | 42 | {fShortenNumber(TOTAL)} 43 | 44 | Weekly Sales 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default AppWeeklySales; 51 | -------------------------------------------------------------------------------- /src/components/_dashboard/app/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppBugReports } from '@/components/_dashboard/app/AppBugReports'; 2 | export { default as AppConversionRates } from '@/components/_dashboard/app/AppConversionRates'; 3 | export { default as AppCurrentSubject } from '@/components/_dashboard/app/AppCurrentSubject'; 4 | export { default as AppCurrentVisits } from '@/components/_dashboard/app/AppCurrentVisits'; 5 | export { default as AppItemOrders } from '@/components/_dashboard/app/AppItemOrders'; 6 | export { default as AppNewsUpdate } from '@/components/_dashboard/app/AppNewsUpdate'; 7 | export { default as AppNewUsers } from '@/components/_dashboard/app/AppNewUsers'; 8 | export { default as AppOrderTimeline } from '@/components/_dashboard/app/AppOrderTimeline'; 9 | export { default as AppTasks } from '@/components/_dashboard/app/AppTasks'; 10 | export { default as AppTrafficBySite } from '@/components/_dashboard/app/AppTrafficBySite'; 11 | export { default as AppWebsiteVisits } from '@/components/_dashboard/app/AppWebsiteVisits'; 12 | export { default as AppWeeklySales } from '@/components/_dashboard/app/AppWeeklySales'; 13 | -------------------------------------------------------------------------------- /src/components/_dashboard/blog/BlogPostCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import eyeFill from '@iconify/icons-eva/eye-fill'; 4 | import shareFill from '@iconify/icons-eva/share-fill'; 5 | import messageCircleFill from '@iconify/icons-eva/message-circle-fill'; 6 | import { alpha, styled } from '@mui/material/styles'; 7 | import { Box, Link, Card, Grid, Avatar, Typography, CardContent } from '@mui/material'; 8 | import { fDate } from '@/utils/formatTime'; 9 | import { fShortenNumber } from '@/utils/formatNumber'; 10 | import SvgIconStyle from '../../SvgIconStyle'; 11 | import { IPost } from '@/models'; 12 | 13 | import ShapeAvatar from '@/assets/images/icons/shape-avatar.svg'; 14 | 15 | const CardMediaStyle = styled('div')({ 16 | position: 'relative', 17 | paddingTop: 'calc(100% * 3 / 4)' 18 | }); 19 | 20 | const TitleStyle = styled(Link)({ 21 | height: 44, 22 | overflow: 'hidden', 23 | WebkitLineClamp: 2, 24 | display: '-webkit-box', 25 | WebkitBoxOrient: 'vertical' 26 | }); 27 | 28 | const AvatarStyle = styled(Avatar)(({ theme }) => ({ 29 | zIndex: 9, 30 | width: 32, 31 | height: 32, 32 | position: 'absolute', 33 | left: theme.spacing(3), 34 | bottom: theme.spacing(-2) 35 | })); 36 | 37 | const InfoStyle = styled('div')(({ theme }) => ({ 38 | display: 'flex', 39 | flexWrap: 'wrap', 40 | justifyContent: 'flex-end', 41 | marginTop: theme.spacing(3), 42 | color: theme.palette.text.disabled 43 | })); 44 | 45 | const CoverImgStyle = styled('img')({ 46 | top: 0, 47 | width: '100%', 48 | height: '100%', 49 | objectFit: 'cover', 50 | position: 'absolute' 51 | }); 52 | 53 | interface Props { 54 | post: IPost; 55 | index: number; 56 | } 57 | 58 | const BlogPostCard = (props: Props): JSX.Element => { 59 | const { post, index } = props; 60 | const { cover, title, view, comment, share, author, createdAt } = post; 61 | const latestPostLarge = index === 0; 62 | const latestPost = index === 1 || index === 2; 63 | 64 | const POST_INFO = [ 65 | { number: comment, icon: messageCircleFill }, 66 | { number: view, icon: eyeFill }, 67 | { number: share, icon: shareFill } 68 | ]; 69 | 70 | return ( 71 | 72 | 73 | alpha(theme.palette.grey[900], 0.72) 83 | } 84 | }), 85 | ...(latestPostLarge && { 86 | pt: { 87 | xs: 'calc(100% * 4 / 3)', 88 | sm: 'calc(100% * 3 / 4.66)' 89 | } 90 | }) 91 | }} 92 | > 93 | 105 | 118 | 119 | 120 | 121 | 122 | 132 | 137 | {fDate(createdAt)} 138 | 139 | 140 | 151 | {title} 152 | 153 | 154 | 155 | {POST_INFO.map((info, index) => ( 156 | 167 | 172 | 173 | {fShortenNumber(info.number)} 174 | 175 | 176 | ))} 177 | 178 | 179 | 180 | 181 | ); 182 | }; 183 | 184 | export default BlogPostCard; 185 | -------------------------------------------------------------------------------- /src/components/_dashboard/blog/BlogPostsSearch.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import searchFill from '@iconify/icons-eva/search-fill'; 4 | import { styled } from '@mui/material/styles'; 5 | import { Box, TextField, Autocomplete, InputAdornment } from '@mui/material'; 6 | import { IPost } from '@/models'; 7 | 8 | const RootStyle = styled('div')(({ theme }) => ({ 9 | '& .MuiAutocomplete-root': { 10 | width: 200, 11 | transition: theme.transitions.create('width', { 12 | easing: theme.transitions.easing.easeInOut, 13 | duration: theme.transitions.duration.shorter 14 | }), 15 | '&.Mui-focused': { 16 | width: 240, 17 | '& .MuiAutocomplete-inputRoot': { 18 | // boxShadow: theme.customShadows.z12 19 | } 20 | } 21 | }, 22 | '& .MuiAutocomplete-inputRoot': { 23 | '& fieldset': { 24 | borderWidth: '1px !important', 25 | borderColor: `${theme.palette.grey[500_32]} !important` 26 | } 27 | }, 28 | '& .MuiAutocomplete-option': { 29 | '&:not(:last-child)': { 30 | borderBottom: `solid 1px ${theme.palette.divider}` 31 | } 32 | } 33 | })); 34 | 35 | interface Props { 36 | posts: IPost[]; 37 | } 38 | 39 | export const BlogPostsSearch = (props: Props): JSX.Element => { 40 | return ( 41 | 42 | post.title} 48 | renderInput={(params) => ( 49 | 56 | 57 | 67 | 68 | {params.InputProps.startAdornment} 69 | 70 | ) 71 | }} 72 | /> 73 | )} 74 | /> 75 | 76 | ); 77 | }; 78 | 79 | export default BlogPostsSearch; 80 | -------------------------------------------------------------------------------- /src/components/_dashboard/blog/BlogPostsSort.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MenuItem, TextField } from '@mui/material'; 3 | 4 | interface Menu { 5 | value: string; 6 | label: string; 7 | } 8 | 9 | interface Props { 10 | options: Menu[]; 11 | onSort?: () => void; 12 | } 13 | 14 | export const BlogPostsSort = (props: Props): JSX.Element => { 15 | return ( 16 | 17 | {props.options.map((option) => ( 18 | 19 | {option.label} 20 | 21 | ))} 22 | 23 | ); 24 | }; 25 | 26 | export default BlogPostsSort; 27 | -------------------------------------------------------------------------------- /src/components/_dashboard/blog/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BlogPostCard } from '@/components/_dashboard/blog/BlogPostCard'; 2 | export { default as BlogPostsSearch } from '@/components/_dashboard/blog/BlogPostsSearch'; 3 | export { default as BlogPostsSort } from '@/components/_dashboard/blog/BlogPostsSort'; 4 | -------------------------------------------------------------------------------- /src/components/_dashboard/products/ProductCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as RouterLink } from 'react-router-dom'; 3 | import { Box, Card, Link, Typography, Stack } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | import { fCurrency } from '@/utils/formatNumber'; 6 | import Label from '../../Label'; 7 | import ColorPreview from '../../ColorPreview'; 8 | import { IProduct } from '@/models'; 9 | 10 | const ProductImgStyle = styled('img')({ 11 | top: 0, 12 | width: '100%', 13 | height: '100%', 14 | objectFit: 'cover', 15 | position: 'absolute' 16 | }); 17 | 18 | interface Props { 19 | product: IProduct; 20 | } 21 | 22 | export const ShopProductCard = (props: Props): JSX.Element => { 23 | const { name, cover, price, colors, status, priceSale } = props.product; 24 | 25 | return ( 26 | 27 | 28 | {status && ( 29 | 42 | )} 43 | 44 | 45 | 46 | 47 | 48 | 49 | {name} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | {priceSale && fCurrency(priceSale)} 65 | 66 |   67 | {fCurrency(price)} 68 | 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | export default ShopProductCard; 76 | -------------------------------------------------------------------------------- /src/components/_dashboard/products/ProductCartWidget.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import shoppingCartFill from '@iconify/icons-eva/shopping-cart-fill'; 4 | import { styled } from '@mui/material/styles'; 5 | import { Badge } from '@mui/material'; 6 | 7 | const RootStyle = styled('div')(({ theme }) => ({ 8 | zIndex: 999, 9 | right: 0, 10 | display: 'flex', 11 | cursor: 'pointer', 12 | position: 'fixed', 13 | alignItems: 'center', 14 | top: theme.spacing(16), 15 | height: theme.spacing(5), 16 | paddingLeft: theme.spacing(2), 17 | paddingRight: theme.spacing(2), 18 | paddingTop: theme.spacing(1.25), 19 | // boxShadow: theme.customShadows.z20, 20 | color: theme.palette.text.primary, 21 | backgroundColor: theme.palette.background.paper, 22 | // borderTopLeftRadius: theme.shape.borderRadiusMd, 23 | // borderBottomLeftRadius: theme.shape.borderRadiusMd, 24 | transition: theme.transitions.create('opacity'), 25 | '&:hover': { opacity: 0.72 } 26 | })); 27 | 28 | const CartWidget = (): JSX.Element => { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default CartWidget; 39 | -------------------------------------------------------------------------------- /src/components/_dashboard/products/ProductList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid } from '@mui/material'; 3 | import ShopProductCard from '@/components/_dashboard/products/ProductCard'; 4 | 5 | interface Props { 6 | products; 7 | order?; 8 | } 9 | 10 | const ProductList = (props: Props): JSX.Element => { 11 | const { products, ...other } = props; 12 | return ( 13 | 14 | {products.map((product) => ( 15 | 16 | 17 | 18 | ))} 19 | 20 | ); 21 | }; 22 | 23 | export default ProductList; 24 | -------------------------------------------------------------------------------- /src/components/_dashboard/products/ProductSort.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@iconify/react'; 2 | import React, { useState } from 'react'; 3 | import chevronUpFill from '@iconify/icons-eva/chevron-up-fill'; 4 | import chevronDownFill from '@iconify/icons-eva/chevron-down-fill'; 5 | import { Menu, Button, MenuItem, Typography } from '@mui/material'; 6 | 7 | const SORT_BY_OPTIONS = [ 8 | { value: 'featured', label: 'Featured' }, 9 | { value: 'newest', label: 'Newest' }, 10 | { value: 'priceDesc', label: 'Price: High-Low' }, 11 | { value: 'priceAsc', label: 'Price: Low-High' } 12 | ]; 13 | 14 | const ShopProductSort = (): JSX.Element => { 15 | const [open, setOpen] = useState(null); 16 | 17 | const handleOpen = (event) => { 18 | setOpen(event.currentTarget); 19 | }; 20 | 21 | const handleClose = () => { 22 | setOpen(null); 23 | }; 24 | 25 | return ( 26 | <> 27 | 38 | 46 | {SORT_BY_OPTIONS.map((option) => ( 47 | 53 | {option.label} 54 | 55 | ))} 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default ShopProductSort; 62 | -------------------------------------------------------------------------------- /src/components/_dashboard/products/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProductFilterSidebar } from './ProductFilterSidebar'; 2 | export { default as ProductCartWidget } from './ProductCartWidget'; 3 | export { default as ProductCard } from './ProductCard'; 4 | export { default as ProductList } from './ProductList'; 5 | export { default as ProductSort } from './ProductSort'; 6 | -------------------------------------------------------------------------------- /src/components/_dashboard/user/UserListHead.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { visuallyHidden } from '@mui/utils'; 3 | import { Box, Checkbox, TableRow, TableCell, TableHead, TableSortLabel } from '@mui/material'; 4 | import { HeaderLabel } from '@/models'; 5 | 6 | interface Props { 7 | order?; 8 | orderBy: string; 9 | rowCount: number; 10 | headLabel: HeaderLabel[]; 11 | numSelected: number; 12 | onRequestSort; 13 | onSelectAllClick; 14 | } 15 | 16 | const UserListHead = (props: Props): JSX.Element => { 17 | const { order, orderBy, rowCount, headLabel, numSelected, onRequestSort, onSelectAllClick } = 18 | props; 19 | const createSortHandler = (property) => (event) => { 20 | onRequestSort(event, property); 21 | }; 22 | 23 | return ( 24 | 25 | 26 | 27 | 0 && numSelected < rowCount} 29 | checked={rowCount > 0 && numSelected === rowCount} 30 | onChange={onSelectAllClick} 31 | /> 32 | 33 | {headLabel.map((headCell) => ( 34 | 39 | 45 | {headCell.label} 46 | {orderBy === headCell.id ? ( 47 | 48 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 49 | 50 | ) : null} 51 | 52 | 53 | ))} 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default UserListHead; 60 | -------------------------------------------------------------------------------- /src/components/_dashboard/user/UserListToolbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Icon } from '@iconify/react'; 4 | import searchFill from '@iconify/icons-eva/search-fill'; 5 | import trash2Fill from '@iconify/icons-eva/trash-2-fill'; 6 | import roundFilterList from '@iconify/icons-ic/round-filter-list'; 7 | import { styled } from '@mui/material/styles'; 8 | import { 9 | Box, 10 | Toolbar, 11 | Tooltip, 12 | IconButton, 13 | Typography, 14 | OutlinedInput, 15 | InputAdornment 16 | } from '@mui/material'; 17 | 18 | const RootStyle = styled(Toolbar)(({ theme }) => ({ 19 | height: 96, 20 | display: 'flex', 21 | justifyContent: 'space-between', 22 | padding: theme.spacing(0, 1, 0, 3) 23 | })); 24 | 25 | const SearchStyle = styled(OutlinedInput)(({ theme }) => ({ 26 | width: 240, 27 | transition: theme.transitions.create(['box-shadow', 'width'], { 28 | easing: theme.transitions.easing.easeInOut, 29 | duration: theme.transitions.duration.shorter 30 | }), 31 | '&.Mui-focused': { width: 320 }, 32 | '& fieldset': { 33 | borderWidth: '1px !important', 34 | borderColor: `${theme.palette.grey[500_32]} !important` 35 | } 36 | })); 37 | 38 | interface Props { 39 | numSelected: number; 40 | filterName: string; 41 | onFilterName; 42 | } 43 | 44 | const UserListToolbar = (props: Props): JSX.Element => { 45 | const { numSelected, filterName, onFilterName } = props; 46 | return ( 47 | 0 && { 50 | color: 'primary.main', 51 | bgcolor: 'primary.lighter' 52 | }) 53 | }} 54 | > 55 | {numSelected > 0 ? ( 56 | 57 | {numSelected} selected 58 | 59 | ) : ( 60 | 66 | 71 | 72 | } 73 | /> 74 | )} 75 | 76 | {numSelected > 0 ? ( 77 | 78 | 79 | 80 | 81 | 82 | ) : ( 83 | 84 | 85 | 86 | 87 | 88 | )} 89 | 90 | ); 91 | }; 92 | 93 | export default UserListToolbar; 94 | -------------------------------------------------------------------------------- /src/components/_dashboard/user/UserMoreMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@iconify/react'; 2 | import React, { useRef, useState } from 'react'; 3 | import editFill from '@iconify/icons-eva/edit-fill'; 4 | import { Link as RouterLink } from 'react-router-dom'; 5 | import trash2Outline from '@iconify/icons-eva/trash-2-outline'; 6 | import moreVerticalFill from '@iconify/icons-eva/more-vertical-fill'; 7 | import { Menu, MenuItem, IconButton, ListItemIcon, ListItemText } from '@mui/material'; 8 | 9 | const UserMoreMenu = (): JSX.Element => { 10 | const ref = useRef(null); 11 | const [isOpen, setIsOpen] = useState(false); 12 | 13 | return ( 14 | <> 15 | setIsOpen(true)}> 16 | 17 | 18 | 19 | setIsOpen(false)} 23 | PaperProps={{ 24 | sx: { width: 200, maxWidth: '100%' } 25 | }} 26 | anchorOrigin={{ vertical: 'top', horizontal: 'right' }} 27 | transformOrigin={{ vertical: 'top', horizontal: 'right' }} 28 | > 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default UserMoreMenu; 48 | -------------------------------------------------------------------------------- /src/components/_dashboard/user/index.ts: -------------------------------------------------------------------------------- 1 | export { default as UserListHead } from './UserListHead'; 2 | export { default as UserListToolbar } from './UserListToolbar'; 3 | export { default as UserMoreMenu } from './UserMoreMenu'; 4 | -------------------------------------------------------------------------------- /src/components/animate/MotionContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { motion } from 'framer-motion'; 3 | import { Box } from '@mui/material'; 4 | import { varWrapEnter } from '@/components/animate/variants'; 5 | 6 | interface Props { 7 | open: boolean; 8 | children?: JSX.Element; 9 | other?; 10 | initial?; 11 | } 12 | 13 | const MotionContainer = (props: Props): JSX.Element => { 14 | const { open, children, ...other } = props; 15 | return ( 16 | 23 | {children} 24 | 25 | ); 26 | }; 27 | 28 | export default MotionContainer; 29 | -------------------------------------------------------------------------------- /src/components/animate/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@/components/animate/variants'; 2 | export { default as MotionContainer } from './MotionContainer'; 3 | -------------------------------------------------------------------------------- /src/components/animate/variants/Wrap.ts: -------------------------------------------------------------------------------- 1 | export const varWrapEnter = { 2 | animate: { 3 | transition: { staggerChildren: 0.1 } 4 | } 5 | }; 6 | 7 | export const varWrapExit = { 8 | exit: { 9 | transition: { staggerChildren: 0.1 } 10 | } 11 | }; 12 | 13 | export const varWrapBoth = { 14 | animate: { 15 | transition: { staggerChildren: 0.07, delayChildren: 0.1 } 16 | }, 17 | exit: { 18 | transition: { staggerChildren: 0.05, staggerDirection: -1 } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/animate/variants/bounce/BounceIn.ts: -------------------------------------------------------------------------------- 1 | import { 2 | varBounceOut, 3 | varBounceOutUp, 4 | varBounceOutDown, 5 | varBounceOutLeft, 6 | varBounceOutRight 7 | } from './BounceOut'; 8 | 9 | const TRANSITION_ENTER = { 10 | duration: 0.72, 11 | ease: [0.43, 0.13, 0.23, 0.96] 12 | }; 13 | 14 | const TRANSITION_EXIT = { 15 | duration: 0.48, 16 | ease: [0.43, 0.13, 0.23, 0.96] 17 | }; 18 | 19 | export const varBounceIn = { 20 | animate: { 21 | scale: [0.3, 1.1, 0.9, 1.03, 0.97, 1], 22 | opacity: [0, 1, 1, 1, 1, 1], 23 | transition: TRANSITION_ENTER 24 | }, 25 | exit: varBounceOut.animate 26 | }; 27 | 28 | export const varBounceInUp = { 29 | animate: { 30 | y: [720, -24, 12, -4, 0], 31 | scaleY: [4, 0.9, 0.95, 0.985, 1], 32 | opacity: [0, 1, 1, 1, 1], 33 | transition: { ...TRANSITION_ENTER } 34 | }, 35 | exit: { ...varBounceOutDown.animate, transition: TRANSITION_EXIT } 36 | }; 37 | 38 | export const varBounceInDown = { 39 | animate: { 40 | y: [-720, 24, -12, 4, 0], 41 | scaleY: [4, 0.9, 0.95, 0.985, 1], 42 | opacity: [0, 1, 1, 1, 1], 43 | transition: TRANSITION_ENTER 44 | }, 45 | exit: { ...varBounceOutUp.animate, transition: TRANSITION_EXIT } 46 | }; 47 | 48 | export const varBounceInLeft = { 49 | animate: { 50 | x: [-720, 24, -12, 4, 0], 51 | scaleX: [3, 1, 0.98, 0.995, 1], 52 | opacity: [0, 1, 1, 1, 1], 53 | transition: TRANSITION_ENTER 54 | }, 55 | exit: { ...varBounceOutLeft.animate, transition: TRANSITION_EXIT } 56 | }; 57 | 58 | export const varBounceInRight = { 59 | animate: { 60 | x: [720, -24, 12, -4, 0], 61 | scaleX: [3, 1, 0.98, 0.995, 1], 62 | opacity: [0, 1, 1, 1, 1], 63 | transition: TRANSITION_ENTER 64 | }, 65 | exit: { ...varBounceOutRight.animate, transition: TRANSITION_EXIT } 66 | }; 67 | -------------------------------------------------------------------------------- /src/components/animate/variants/bounce/BounceOut.ts: -------------------------------------------------------------------------------- 1 | export const varBounceOut = { 2 | animate: { 3 | scale: [0.9, 1.1, 0.3], 4 | opacity: [1, 1, 0] 5 | } 6 | }; 7 | 8 | export const varBounceOutUp = { 9 | animate: { 10 | y: [-12, 24, -720], 11 | scaleY: [0.985, 0.9, 3], 12 | opacity: [1, 1, 0] 13 | } 14 | }; 15 | 16 | export const varBounceOutDown = { 17 | animate: { 18 | y: [12, -24, 720], 19 | scaleY: [0.985, 0.9, 3], 20 | opacity: [1, 1, 0] 21 | } 22 | }; 23 | 24 | export const varBounceOutLeft = { 25 | animate: { 26 | x: [0, 24, -720], 27 | scaleX: [1, 0.9, 2], 28 | opacity: [1, 1, 0] 29 | } 30 | }; 31 | 32 | export const varBounceOutRight = { 33 | animate: { 34 | x: [0, -24, 720], 35 | scaleX: [1, 0.9, 2], 36 | opacity: [1, 1, 0] 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/animate/variants/bounce/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BounceIn'; 2 | export * from './BounceOut'; 3 | -------------------------------------------------------------------------------- /src/components/animate/variants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Wrap'; 2 | export * from './bounce'; 3 | -------------------------------------------------------------------------------- /src/components/authentication/AuthSocial.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import googleFill from '@iconify/icons-eva/google-fill'; 4 | import twitterFill from '@iconify/icons-eva/twitter-fill'; 5 | import facebookFill from '@iconify/icons-eva/facebook-fill'; 6 | // material 7 | import { Stack, Button, Divider, Typography } from '@mui/material'; 8 | 9 | const AuthSocial = (): JSX.Element => { 10 | return ( 11 | <> 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | OR 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default AuthSocial; 36 | -------------------------------------------------------------------------------- /src/components/authentication/login/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | import React, { useState } from 'react'; 3 | import { Link as RouterLink, useNavigate } from 'react-router-dom'; 4 | import { useFormik, Form, FormikProvider } from 'formik'; 5 | import { Icon } from '@iconify/react'; 6 | import eyeFill from '@iconify/icons-eva/eye-fill'; 7 | import eyeOffFill from '@iconify/icons-eva/eye-off-fill'; 8 | import { 9 | Link, 10 | Stack, 11 | Checkbox, 12 | TextField, 13 | IconButton, 14 | InputAdornment, 15 | FormControlLabel 16 | } from '@mui/material'; 17 | import { LoadingButton } from '@mui/lab'; 18 | 19 | // ---------------------------------------------------------------------- 20 | 21 | const LoginForm = (): JSX.Element => { 22 | const navigate = useNavigate(); 23 | const [showPassword, setShowPassword] = useState(false); 24 | 25 | const LoginSchema = Yup.object().shape({ 26 | email: Yup.string() 27 | .email('Email must be a valid email address') 28 | .required('Email is required'), 29 | password: Yup.string().required('Password is required') 30 | }); 31 | 32 | const formik = useFormik({ 33 | initialValues: { 34 | email: '', 35 | password: '', 36 | remember: true 37 | }, 38 | validationSchema: LoginSchema, 39 | onSubmit: () => { 40 | navigate('/dashboard', { replace: true }); 41 | } 42 | }); 43 | 44 | const { errors, touched, values, isSubmitting, handleSubmit, getFieldProps } = formik; 45 | 46 | const handleShowPassword = () => { 47 | setShowPassword((show) => !show); 48 | }; 49 | 50 | return ( 51 | 52 |
53 | 54 | 63 | 64 | 73 | 74 | 75 | 76 | 77 | ) 78 | }} 79 | error={Boolean(touched.password && errors.password)} 80 | helperText={touched.password && errors.password} 81 | /> 82 | 83 | 84 | 90 | 93 | } 94 | label="Remember me" 95 | /> 96 | 97 | 98 | Forgot password? 99 | 100 | 101 | 102 | 109 | Login 110 | 111 |
112 |
113 | ); 114 | }; 115 | 116 | export default LoginForm; 117 | -------------------------------------------------------------------------------- /src/components/authentication/login/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LoginForm } from './LoginForm'; 2 | -------------------------------------------------------------------------------- /src/components/authentication/register/RegisterForm.tsx: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | import React, { useState } from 'react'; 3 | import { Icon } from '@iconify/react'; 4 | import { useFormik, Form, FormikProvider } from 'formik'; 5 | import eyeFill from '@iconify/icons-eva/eye-fill'; 6 | import eyeOffFill from '@iconify/icons-eva/eye-off-fill'; 7 | import { useNavigate } from 'react-router-dom'; 8 | import { Stack, TextField, IconButton, InputAdornment } from '@mui/material'; 9 | import { LoadingButton } from '@mui/lab'; 10 | 11 | const RegisterForm = (): JSX.Element => { 12 | const navigate = useNavigate(); 13 | const [showPassword, setShowPassword] = useState(false); 14 | 15 | const RegisterSchema = Yup.object().shape({ 16 | firstName: Yup.string() 17 | .min(2, 'Too Short!') 18 | .max(50, 'Too Long!') 19 | .required('First name required'), 20 | lastName: Yup.string() 21 | .min(2, 'Too Short!') 22 | .max(50, 'Too Long!') 23 | .required('Last name required'), 24 | email: Yup.string() 25 | .email('Email must be a valid email address') 26 | .required('Email is required'), 27 | password: Yup.string().required('Password is required') 28 | }); 29 | 30 | const formik = useFormik({ 31 | initialValues: { 32 | firstName: '', 33 | lastName: '', 34 | email: '', 35 | password: '' 36 | }, 37 | validationSchema: RegisterSchema, 38 | onSubmit: () => { 39 | navigate('/dashboard', { replace: true }); 40 | } 41 | }); 42 | 43 | const { errors, touched, handleSubmit, isSubmitting, getFieldProps } = formik; 44 | 45 | return ( 46 | 47 |
48 | 49 | 50 | 57 | 58 | 65 | 66 | 67 | 76 | 77 | 86 | setShowPassword((prev) => !prev)} 89 | > 90 | 91 | 92 | 93 | ) 94 | }} 95 | error={Boolean(touched.password && errors.password)} 96 | helperText={touched.password && errors.password} 97 | /> 98 | 99 | 106 | Register 107 | 108 | 109 |
110 |
111 | ); 112 | }; 113 | 114 | export default RegisterForm; 115 | -------------------------------------------------------------------------------- /src/components/authentication/register/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RegisterForm } from './RegisterForm'; 2 | -------------------------------------------------------------------------------- /src/components/charts/BaseOptionChart.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@mui/styles'; 2 | import { useTheme } from '@mui/material/styles'; 3 | 4 | const useStyles = makeStyles(() => 5 | createStyles({ 6 | '@global': { 7 | // Tooltip 8 | '.apexcharts-tooltip,.apexcharts-xaxistooltip': { 9 | border: '0 !important' 10 | // boxShadow: `${theme.customShadows.z24} !important`, 11 | // color: `${theme.palette.text.primary} !important`, 12 | // borderRadius: `${theme.shape.borderRadiusSm}px !important`, 13 | // backgroundColor: `${theme.palette.background.default} !important` 14 | }, 15 | '.apexcharts-tooltip-title': { 16 | border: '0 !important' 17 | // fontWeight: theme.typography.fontWeightBold, 18 | // backgroundColor: `${theme.palette.grey[500_16]} !important`, 19 | // color: theme.palette.text.secondary 20 | }, 21 | '.apexcharts-xaxistooltip-bottom': { 22 | '&:before': { 23 | borderBottomColor: 'transparent !important' 24 | }, 25 | '&:after': { 26 | // borderBottomColor: `${theme.palette.background.paper} !important` 27 | } 28 | }, 29 | 30 | // Legend 31 | '.apexcharts-legend': { 32 | padding: '0 !important' 33 | }, 34 | '.apexcharts-legend-series': { 35 | alignItems: 'center', 36 | display: 'flex !important' 37 | }, 38 | '.apexcharts-legend-marker': { 39 | marginTop: '-2px !important', 40 | marginRight: '8px !important' 41 | }, 42 | '.apexcharts-legend-text': { 43 | lineHeight: '18px', 44 | textTransform: 'capitalize' 45 | } 46 | } 47 | }) 48 | ); 49 | 50 | const BaseOptionChart = () => { 51 | useStyles(); 52 | const theme = useTheme(); 53 | 54 | const LABEL_TOTAL = { 55 | show: true, 56 | label: 'Total', 57 | color: theme.palette.text.secondary, 58 | ...theme.typography.subtitle2 59 | }; 60 | 61 | const LABEL_VALUE = { 62 | offsetY: 8, 63 | color: theme.palette.text.primary, 64 | ...theme.typography.h3 65 | }; 66 | 67 | return { 68 | // Colors 69 | colors: [ 70 | theme.palette.primary.main, 71 | theme.palette.warning.main, 72 | theme.palette.info.main, 73 | theme.palette.error.main, 74 | theme.palette.success.main 75 | ], 76 | 77 | // Chart 78 | chart: { 79 | toolbar: { show: false }, 80 | zoom: { enabled: false }, 81 | // animations: { enabled: false }, 82 | foreColor: theme.palette.text.disabled, 83 | fontFamily: theme.typography.fontFamily 84 | }, 85 | 86 | // States 87 | states: { 88 | hover: { 89 | filter: { 90 | type: 'lighten', 91 | value: 0.04 92 | } 93 | }, 94 | active: { 95 | filter: { 96 | type: 'darken', 97 | value: 0.88 98 | } 99 | } 100 | }, 101 | 102 | // Fill 103 | fill: { 104 | opacity: 1, 105 | gradient: { 106 | type: 'vertical', 107 | shadeIntensity: 0, 108 | opacityFrom: 0.4, 109 | opacityTo: 0, 110 | stops: [0, 100] 111 | } 112 | }, 113 | 114 | // Datalabels 115 | dataLabels: { enabled: false }, 116 | 117 | // Stroke 118 | stroke: { 119 | width: 3, 120 | curve: 'smooth', 121 | lineCap: 'round' 122 | }, 123 | 124 | // Grid 125 | grid: { 126 | strokeDashArray: 3, 127 | borderColor: theme.palette.divider 128 | }, 129 | 130 | // Xaxis 131 | xaxis: { 132 | axisBorder: { show: false }, 133 | axisTicks: { show: false } 134 | }, 135 | 136 | // Markers 137 | markers: { 138 | size: 0, 139 | strokeColors: theme.palette.background.paper 140 | }, 141 | 142 | // Tooltip 143 | tooltip: { 144 | x: { 145 | show: false 146 | } 147 | }, 148 | 149 | // Legend 150 | legend: { 151 | show: true, 152 | fontSize: 13, 153 | position: 'top', 154 | horizontalAlign: 'right', 155 | markers: { radius: 12 }, 156 | itemMargin: { horizontal: 12 }, 157 | labels: { 158 | colors: theme.palette.text.primary 159 | } 160 | }, 161 | 162 | // plotOptions 163 | plotOptions: { 164 | // Pie + Donut 165 | pie: { 166 | donut: { 167 | labels: { 168 | show: true, 169 | value: LABEL_VALUE, 170 | total: LABEL_TOTAL 171 | } 172 | } 173 | }, 174 | // Radialbar 175 | radialBar: { 176 | track: { 177 | strokeWidth: '100%', 178 | background: theme.palette.grey[500_16] 179 | }, 180 | dataLabels: { 181 | value: LABEL_VALUE, 182 | total: LABEL_TOTAL 183 | } 184 | }, 185 | // Radar 186 | radar: { 187 | polygons: { 188 | strokeWidth: 1, 189 | fill: { colors: ['transparent'] }, 190 | strokeColors: theme.palette.divider, 191 | connectorColors: theme.palette.divider 192 | } 193 | } 194 | } 195 | }; 196 | }; 197 | 198 | export default BaseOptionChart; 199 | -------------------------------------------------------------------------------- /src/components/charts/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BaseOptionChart } from '@/components/charts/BaseOptionChart'; 2 | -------------------------------------------------------------------------------- /src/layouts/AuthLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as RouterLink } from 'react-router-dom'; 3 | import { styled } from '@mui/material/styles'; 4 | import { Typography } from '@mui/material'; 5 | import Logo from '@/components/Logo'; 6 | import { MHidden } from '@/components/@material-extend'; 7 | 8 | const HeaderStyle = styled('header')(({ theme }) => ({ 9 | top: 0, 10 | zIndex: 9, 11 | lineHeight: 0, 12 | width: '100%', 13 | display: 'flex', 14 | alignItems: 'center', 15 | position: 'absolute', 16 | padding: theme.spacing(3), 17 | justifyContent: 'space-between', 18 | [theme.breakpoints.up('md')]: { 19 | alignItems: 'flex-start', 20 | padding: theme.spacing(7, 5, 0, 7) 21 | } 22 | })); 23 | 24 | interface Props { 25 | children?; 26 | } 27 | 28 | const AuthLayout = (props: Props): JSX.Element => { 29 | const { children } = props; 30 | return ( 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | {children} 44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default AuthLayout; 51 | -------------------------------------------------------------------------------- /src/layouts/LogoOnlyLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Link as RouterLink, Outlet } from 'react-router-dom'; 2 | import React from 'react'; 3 | import { styled } from '@mui/material/styles'; 4 | import Logo from '@/components/Logo'; 5 | 6 | const HeaderStyle = styled('header')(({ theme }) => ({ 7 | top: 0, 8 | left: 0, 9 | lineHeight: 0, 10 | width: '100%', 11 | position: 'absolute', 12 | padding: theme.spacing(3, 3, 0), 13 | [theme.breakpoints.up('sm')]: { 14 | padding: theme.spacing(5, 5, 0) 15 | } 16 | })); 17 | 18 | const LogoOnlyLayout = (): JSX.Element => { 19 | return ( 20 | <> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default LogoOnlyLayout; 32 | -------------------------------------------------------------------------------- /src/layouts/dashboard/AccountPopover.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@iconify/react'; 2 | import React, { useRef, useState } from 'react'; 3 | import homeFill from '@iconify/icons-eva/home-fill'; 4 | import personFill from '@iconify/icons-eva/person-fill'; 5 | import settings2Fill from '@iconify/icons-eva/settings-2-fill'; 6 | import { Link as RouterLink } from 'react-router-dom'; 7 | import { alpha } from '@mui/material/styles'; 8 | import { Button, Box, Divider, MenuItem, Typography, Avatar, IconButton } from '@mui/material'; 9 | import MenuPopover from '@/components/MenuPopover'; 10 | import account from '@/_mocks_/account'; 11 | 12 | const MENU_OPTIONS = [ 13 | { 14 | label: 'Home', 15 | icon: homeFill, 16 | linkTo: '/' 17 | }, 18 | { 19 | label: 'Profile', 20 | icon: personFill, 21 | linkTo: '#' 22 | }, 23 | { 24 | label: 'Settings', 25 | icon: settings2Fill, 26 | linkTo: '#' 27 | } 28 | ]; 29 | 30 | const AccountPopover = (): JSX.Element => { 31 | const anchorRef = useRef(null); 32 | const [open, setOpen] = useState(false); 33 | 34 | const handleOpen = () => { 35 | setOpen(true); 36 | }; 37 | const handleClose = () => { 38 | setOpen(false); 39 | }; 40 | 41 | return ( 42 | <> 43 | alpha(theme.palette.grey[900], 0.72) 59 | } 60 | }) 61 | }} 62 | > 63 | 64 | 65 | 66 | 72 | 73 | 74 | {account.displayName} 75 | 76 | 77 | {account.email} 78 | 79 | 80 | 81 | 82 | 83 | {MENU_OPTIONS.map((option) => ( 84 | 91 | 100 | 101 | {option.label} 102 | 103 | ))} 104 | 105 | 106 | 109 | 110 | 111 | 112 | ); 113 | }; 114 | 115 | export default AccountPopover; 116 | -------------------------------------------------------------------------------- /src/layouts/dashboard/DashboardNavbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Icon } from '@iconify/react'; 4 | import menu2Fill from '@iconify/icons-eva/menu-2-fill'; 5 | import { alpha, styled } from '@mui/material/styles'; 6 | import { Box, Stack, AppBar, Toolbar, IconButton } from '@mui/material'; 7 | import { MHidden } from '@/components/@material-extend'; 8 | import Searchbar from './Searchbar'; 9 | import AccountPopover from './AccountPopover'; 10 | import LanguagePopover from './LanguagePopover'; 11 | import NotificationsPopover from './NotificationsPopover'; 12 | 13 | const DRAWER_WIDTH = 280; 14 | const APPBAR_MOBILE = 64; 15 | const APPBAR_DESKTOP = 92; 16 | 17 | const RootStyle = styled(AppBar)(({ theme }) => ({ 18 | boxShadow: 'none', 19 | backdropFilter: 'blur(6px)', 20 | WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile 21 | backgroundColor: alpha(theme.palette.background.default, 0.72), 22 | [theme.breakpoints.up('lg')]: { 23 | width: `calc(100% - ${DRAWER_WIDTH + 1}px)` 24 | } 25 | })); 26 | 27 | const ToolbarStyle = styled(Toolbar)(({ theme }) => ({ 28 | minHeight: APPBAR_MOBILE, 29 | [theme.breakpoints.up('lg')]: { 30 | minHeight: APPBAR_DESKTOP, 31 | padding: theme.spacing(0, 5) 32 | } 33 | })); 34 | 35 | interface Props { 36 | onOpenSidebar; 37 | } 38 | 39 | const DashboardNavbar = (props: Props): JSX.Element => { 40 | const { onOpenSidebar } = props; 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default DashboardNavbar; 64 | -------------------------------------------------------------------------------- /src/layouts/dashboard/DashboardSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Link as RouterLink, useLocation } from 'react-router-dom'; 3 | import { styled } from '@mui/material/styles'; 4 | import { Box, Link, Drawer, Typography, Avatar } from '@mui/material'; 5 | import Logo from '@/components/Logo'; 6 | import Scrollbar from '@/components/Scrollbar'; 7 | import NavSection from '@/components/NavSection'; 8 | import { MHidden } from '@/components/@material-extend'; 9 | import sidebarConfig from './SidebarConfig'; 10 | import account from '@/_mocks_/account'; 11 | 12 | const DRAWER_WIDTH = 280; 13 | 14 | const RootStyle = styled('div')(({ theme }) => ({ 15 | [theme.breakpoints.up('lg')]: { 16 | flexShrink: 0, 17 | width: DRAWER_WIDTH 18 | } 19 | })); 20 | 21 | const AccountStyle = styled('div')(({ theme }) => ({ 22 | display: 'flex', 23 | alignItems: 'center', 24 | padding: theme.spacing(2, 2.5), 25 | // borderRadius: theme.shape.borderRadiusSm, 26 | backgroundColor: theme.palette.grey[200] 27 | })); 28 | 29 | interface Props { 30 | isOpenSidebar?; 31 | onCloseSidebar?; 32 | } 33 | 34 | const DashboardSidebar = (props: Props): JSX.Element => { 35 | const { isOpenSidebar, onCloseSidebar } = props; 36 | const { pathname } = useLocation(); 37 | 38 | useEffect(() => { 39 | if (isOpenSidebar) { 40 | onCloseSidebar(); 41 | } 42 | }, [pathname]); 43 | 44 | const renderContent = ( 45 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {account.displayName} 64 | 65 | 66 | {account.role} 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ); 78 | 79 | return ( 80 | 81 | 82 | 89 | {renderContent} 90 | 91 | 92 | 93 | 94 | 104 | {renderContent} 105 | 106 | 107 | 108 | ); 109 | }; 110 | 111 | export default DashboardSidebar; 112 | -------------------------------------------------------------------------------- /src/layouts/dashboard/LanguagePopover.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import { alpha } from '@mui/material/styles'; 3 | import { Box, MenuItem, ListItemIcon, ListItemText, IconButton } from '@mui/material'; 4 | import MenuPopover from '@/components/MenuPopover'; 5 | 6 | import flagEn from '@/assets/images/icons/ic_flag_en.svg'; 7 | import flagDe from '@/assets/images/icons/ic_flag_de.svg'; 8 | import flagFr from '@/assets/images/icons/ic_flag_fr.svg'; 9 | 10 | const LANGS = [ 11 | { 12 | value: 'en', 13 | label: 'English', 14 | icon: flagEn 15 | }, 16 | { 17 | value: 'de', 18 | label: 'German', 19 | icon: flagDe 20 | }, 21 | { 22 | value: 'fr', 23 | label: 'French', 24 | icon: flagFr 25 | } 26 | ]; 27 | 28 | const LanguagePopover = (): JSX.Element => { 29 | const anchorRef = useRef(null); 30 | const [open, setOpen] = useState(false); 31 | 32 | const handleOpen = () => { 33 | setOpen(true); 34 | }; 35 | 36 | const handleClose = () => { 37 | setOpen(false); 38 | }; 39 | 40 | return ( 41 | <> 42 | 51 | alpha(theme.palette.primary.main, theme.palette.action.focusOpacity) 52 | }) 53 | }} 54 | > 55 | {LANGS[0].label} 56 | 57 | 58 | 59 | 60 | {LANGS.map((option) => ( 61 | 67 | 68 | 69 | 70 | 71 | {option.label} 72 | 73 | 74 | ))} 75 | 76 | 77 | 78 | ); 79 | }; 80 | 81 | export default LanguagePopover; 82 | -------------------------------------------------------------------------------- /src/layouts/dashboard/Searchbar.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@iconify/react'; 2 | import React, { useState } from 'react'; 3 | import searchFill from '@iconify/icons-eva/search-fill'; 4 | import { styled, alpha } from '@mui/material/styles'; 5 | import { 6 | Box, 7 | Input, 8 | Slide, 9 | Button, 10 | InputAdornment, 11 | ClickAwayListener, 12 | IconButton 13 | } from '@mui/material'; 14 | 15 | const APPBAR_MOBILE = 64; 16 | const APPBAR_DESKTOP = 92; 17 | 18 | const SearchbarStyle = styled('div')(({ theme }) => ({ 19 | top: 0, 20 | left: 0, 21 | zIndex: 99, 22 | width: '100%', 23 | display: 'flex', 24 | position: 'absolute', 25 | alignItems: 'center', 26 | height: APPBAR_MOBILE, 27 | backdropFilter: 'blur(6px)', 28 | WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile 29 | padding: theme.spacing(0, 3), 30 | // boxShadow: theme.customShadows.z8, 31 | backgroundColor: `${alpha(theme.palette.background.default, 0.72)}`, 32 | [theme.breakpoints.up('md')]: { 33 | height: APPBAR_DESKTOP, 34 | padding: theme.spacing(0, 5) 35 | } 36 | })); 37 | 38 | const Searchbar = (): JSX.Element => { 39 | const [isOpen, setOpen] = useState(false); 40 | 41 | const handleOpen = () => { 42 | setOpen((prev) => !prev); 43 | }; 44 | 45 | const handleClose = () => { 46 | setOpen(false); 47 | }; 48 | 49 | return ( 50 | 51 |
52 | {!isOpen && ( 53 | 54 | 55 | 56 | )} 57 | 58 | 59 | 60 | 67 | 72 | 73 | } 74 | sx={{ mr: 1, fontWeight: 'fontWeightBold' }} 75 | /> 76 | 79 | 80 | 81 |
82 |
83 | ); 84 | }; 85 | 86 | export default Searchbar; 87 | -------------------------------------------------------------------------------- /src/layouts/dashboard/SidebarConfig.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import pieChart2Fill from '@iconify/icons-eva/pie-chart-2-fill'; 4 | import peopleFill from '@iconify/icons-eva/people-fill'; 5 | import shoppingBagFill from '@iconify/icons-eva/shopping-bag-fill'; 6 | import fileTextFill from '@iconify/icons-eva/file-text-fill'; 7 | import lockFill from '@iconify/icons-eva/lock-fill'; 8 | import personAddFill from '@iconify/icons-eva/person-add-fill'; 9 | import alertTriangleFill from '@iconify/icons-eva/alert-triangle-fill'; 10 | import { NavItemConfig } from '@/models'; 11 | 12 | const getIcon = (name) => ; 13 | 14 | const sidebarConfig: NavItemConfig[] = [ 15 | { 16 | title: 'dashboard', 17 | path: '/dashboard/app', 18 | icon: getIcon(pieChart2Fill) 19 | }, 20 | { 21 | title: 'user', 22 | path: '/dashboard/user', 23 | icon: getIcon(peopleFill) 24 | }, 25 | { 26 | title: 'product', 27 | path: '/dashboard/products', 28 | icon: getIcon(shoppingBagFill) 29 | }, 30 | { 31 | title: 'blog', 32 | path: '/dashboard/blog', 33 | icon: getIcon(fileTextFill) 34 | }, 35 | { 36 | title: 'login', 37 | path: '/login', 38 | icon: getIcon(lockFill) 39 | }, 40 | { 41 | title: 'register', 42 | path: '/register', 43 | icon: getIcon(personAddFill) 44 | }, 45 | { 46 | title: 'Not found', 47 | path: '/404', 48 | icon: getIcon(alertTriangleFill) 49 | } 50 | ]; 51 | 52 | export default sidebarConfig; 53 | -------------------------------------------------------------------------------- /src/layouts/dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import { styled } from '@mui/material/styles'; 4 | import DashboardNavbar from '@/layouts/dashboard/DashboardNavbar'; 5 | import DashboardSidebar from '@/layouts/dashboard/DashboardSidebar'; 6 | 7 | const APP_BAR_MOBILE = 64; 8 | const APP_BAR_DESKTOP = 92; 9 | 10 | const RootStyle = styled('div')({ 11 | display: 'flex', 12 | minHeight: '100%', 13 | overflow: 'hidden' 14 | }); 15 | 16 | const MainStyle = styled('div')(({ theme }) => ({ 17 | flexGrow: 1, 18 | overflow: 'auto', 19 | minHeight: '100%', 20 | paddingTop: APP_BAR_MOBILE + 24, 21 | paddingBottom: theme.spacing(10), 22 | [theme.breakpoints.up('lg')]: { 23 | paddingTop: APP_BAR_DESKTOP + 24, 24 | paddingLeft: theme.spacing(2), 25 | paddingRight: theme.spacing(2) 26 | } 27 | })); 28 | 29 | const DashboardLayout = () => { 30 | const [open, setOpen] = useState(false); 31 | 32 | return ( 33 | 34 | setOpen(true)} /> 35 | setOpen(false)} /> 36 | 37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | export default DashboardLayout; 44 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import 'simplebar'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | import { BrowserRouter } from 'react-router-dom'; 6 | import { HelmetProvider } from 'react-helmet-async'; 7 | import ThemeConfig from '@/theme'; 8 | import ScrollToTop from '@/components/ScrollToTop'; 9 | import Router from '@/routes'; 10 | 11 | const App = (): JSX.Element => { 12 | return ( 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); 21 | 22 | root.render( 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/models.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: string; 3 | avatarUrl: string; 4 | name: string; 5 | company: string; 6 | isVerified: boolean; 7 | status: string | undefined; 8 | role: string | undefined; 9 | } 10 | 11 | export interface IPost { 12 | id: string; 13 | cover: string; 14 | title: string; 15 | view: number; 16 | comment: number; 17 | share: number; 18 | favorite: number; 19 | createdAt: Date; 20 | author: { 21 | name: string; 22 | avatarUrl: string; 23 | }; 24 | } 25 | 26 | export interface IAccount { 27 | displayName: string; 28 | email: string; 29 | photoURL: string; 30 | role: string | undefined; 31 | } 32 | 33 | export interface IProduct { 34 | id: string; 35 | cover: string; 36 | name: string; 37 | price: number; 38 | priceSale: number | null; 39 | colors: string[]; 40 | status: string | undefined; 41 | } 42 | 43 | export interface NavItemConfig { 44 | title: string; 45 | path: string; 46 | icon: JSX.Element; 47 | info?: string; 48 | children?; 49 | } 50 | 51 | export interface News { 52 | image; 53 | title; 54 | description; 55 | postedAt; 56 | } 57 | 58 | export interface Site { 59 | icon; 60 | value; 61 | name; 62 | } 63 | 64 | export interface HeaderLabel { 65 | id: string; 66 | label: string; 67 | alignRight: boolean; 68 | } 69 | -------------------------------------------------------------------------------- /src/pages/Blog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import plusFill from '@iconify/icons-eva/plus-fill'; 4 | import { Link as RouterLink } from 'react-router-dom'; 5 | import { Grid, Button, Container, Stack, Typography } from '@mui/material'; 6 | import Page from '@/components/Page'; 7 | import { BlogPostCard, BlogPostsSort, BlogPostsSearch } from '@/components/_dashboard/blog'; 8 | import POSTS from '@/_mocks_/blog'; 9 | 10 | const SORT_OPTIONS = [ 11 | { value: 'latest', label: 'Latest' }, 12 | { value: 'popular', label: 'Popular' }, 13 | { value: 'oldest', label: 'Oldest' } 14 | ]; 15 | 16 | // ---------------------------------------------------------------------- 17 | 18 | const Blog = (): JSX.Element => { 19 | return ( 20 | 21 | 22 | 23 | 24 | Blog 25 | 26 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {POSTS.map((post, index) => ( 43 | 44 | ))} 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default Blog; 52 | -------------------------------------------------------------------------------- /src/pages/DashboardApp.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, Grid, Container, Typography } from '@mui/material'; 3 | import Page from '@/components/Page'; 4 | import { 5 | AppTasks, 6 | AppNewUsers, 7 | AppBugReports, 8 | AppItemOrders, 9 | AppNewsUpdate, 10 | AppWeeklySales, 11 | AppOrderTimeline, 12 | AppCurrentVisits, 13 | AppWebsiteVisits, 14 | AppTrafficBySite, 15 | AppCurrentSubject, 16 | AppConversionRates 17 | } from '@/components/_dashboard/app'; 18 | 19 | const DashboardApp = (): JSX.Element => { 20 | return ( 21 | 22 | 23 | 24 | Hi, Welcome back 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | }; 76 | 77 | export default DashboardApp; 78 | -------------------------------------------------------------------------------- /src/pages/Login.tsx: -------------------------------------------------------------------------------- 1 | import { Link as RouterLink } from 'react-router-dom'; 2 | import React from 'react'; 3 | import { styled } from '@mui/material/styles'; 4 | import { Card, Stack, Link, Container, Typography } from '@mui/material'; 5 | import AuthLayout from '../layouts/AuthLayout'; 6 | import Page from '@/components/Page'; 7 | import { MHidden } from '@/components/@material-extend'; 8 | import { LoginForm } from '@/components/authentication/login'; 9 | import AuthSocial from '@/components/authentication/AuthSocial'; 10 | import IllustrationLoginImage from '@/assets/images/illustrations/illustration_login.png'; 11 | 12 | const RootStyle = styled(Page)(({ theme }) => ({ 13 | [theme.breakpoints.up('md')]: { 14 | display: 'flex' 15 | } 16 | })); 17 | 18 | const SectionStyle = styled(Card)(({ theme }) => ({ 19 | width: '100%', 20 | maxWidth: 464, 21 | display: 'flex', 22 | flexDirection: 'column', 23 | justifyContent: 'center', 24 | margin: theme.spacing(2, 0, 2, 2) 25 | })); 26 | 27 | const ContentStyle = styled('div')(({ theme }) => ({ 28 | maxWidth: 480, 29 | margin: 'auto', 30 | display: 'flex', 31 | minHeight: '100vh', 32 | flexDirection: 'column', 33 | justifyContent: 'center', 34 | padding: theme.spacing(12, 0) 35 | })); 36 | 37 | const Login = (): JSX.Element => { 38 | return ( 39 | 40 | 41 | Don’t have an account?   42 | 43 | Get started 44 | 45 | 46 | 47 | 48 | 49 | 50 | Hi, Welcome Back 51 | 52 | login 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Sign in to Minimal 61 | 62 | 63 | Enter your details below. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Don’t have an account?  73 | 74 | Get started 75 | 76 | 77 | 78 | 79 | 80 | 81 | ); 82 | }; 83 | 84 | export default Login; 85 | -------------------------------------------------------------------------------- /src/pages/Page404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { motion } from 'framer-motion'; 3 | import { Link as RouterLink } from 'react-router-dom'; 4 | import { styled } from '@mui/material/styles'; 5 | import { Box, Button, Typography, Container } from '@mui/material'; 6 | import { MotionContainer, varBounceIn } from '@/components/animate'; 7 | import Page from '@/components/Page'; 8 | import Illustration404Image from '@/assets/images/illustrations/illustration_404.svg'; 9 | 10 | const RootStyle = styled(Page)(({ theme }) => ({ 11 | display: 'flex', 12 | minHeight: '100%', 13 | alignItems: 'center', 14 | paddingTop: theme.spacing(15), 15 | paddingBottom: theme.spacing(10) 16 | })); 17 | 18 | const Page404 = (): JSX.Element => { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | Sorry, page not found! 27 | 28 | 29 | 30 | Sorry, we couldn’t find the page you’re looking for. Perhaps you’ve 31 | mistyped the URL? Be sure to check your spelling. 32 | 33 | 34 | 35 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | ); 50 | }; 51 | 52 | export default Page404; 53 | -------------------------------------------------------------------------------- /src/pages/Products.tsx: -------------------------------------------------------------------------------- 1 | import { useFormik } from 'formik'; 2 | import React, { useState } from 'react'; 3 | import { Container, Stack, Typography } from '@mui/material'; 4 | import Page from '@/components/Page'; 5 | import { 6 | ProductSort, 7 | ProductList, 8 | ProductCartWidget, 9 | ProductFilterSidebar 10 | } from '@/components/_dashboard/products'; 11 | import PRODUCTS from '@/_mocks_/products'; 12 | 13 | const EcommerceShop = (): JSX.Element => { 14 | const [openFilter, setOpenFilter] = useState(false); 15 | 16 | const formik = useFormik({ 17 | initialValues: { 18 | gender: '', 19 | category: '', 20 | colors: '', 21 | priceRange: '', 22 | rating: '' 23 | }, 24 | onSubmit: () => { 25 | setOpenFilter(false); 26 | } 27 | }); 28 | 29 | const { resetForm, handleSubmit } = formik; 30 | 31 | const handleOpenFilter = () => { 32 | setOpenFilter(true); 33 | }; 34 | 35 | const handleCloseFilter = () => { 36 | setOpenFilter(false); 37 | }; 38 | 39 | const handleResetFilter = () => { 40 | handleSubmit(); 41 | resetForm(); 42 | }; 43 | 44 | return ( 45 | 46 | 47 | 48 | Products 49 | 50 | 51 | 58 | 59 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | }; 76 | 77 | export default EcommerceShop; 78 | -------------------------------------------------------------------------------- /src/pages/Register.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as RouterLink } from 'react-router-dom'; 3 | import { styled } from '@mui/material/styles'; 4 | import { Box, Card, Link, Container, Typography } from '@mui/material'; 5 | import AuthLayout from '../layouts/AuthLayout'; 6 | import Page from '@/components/Page'; 7 | import { MHidden } from '@/components/@material-extend'; 8 | import { RegisterForm } from '@/components/authentication/register'; 9 | import AuthSocial from '@/components/authentication/AuthSocial'; 10 | import IllustrationRegisterImage from '@/assets/images/illustrations/illustration_register.png'; 11 | 12 | const RootStyle = styled(Page)(({ theme }) => ({ 13 | [theme.breakpoints.up('md')]: { 14 | display: 'flex' 15 | } 16 | })); 17 | 18 | const SectionStyle = styled(Card)(({ theme }) => ({ 19 | width: '100%', 20 | maxWidth: 464, 21 | display: 'flex', 22 | flexDirection: 'column', 23 | justifyContent: 'center', 24 | margin: theme.spacing(2, 0, 2, 2) 25 | })); 26 | 27 | const ContentStyle = styled('div')(({ theme }) => ({ 28 | maxWidth: 480, 29 | margin: 'auto', 30 | display: 'flex', 31 | minHeight: '100vh', 32 | flexDirection: 'column', 33 | justifyContent: 'center', 34 | padding: theme.spacing(12, 0) 35 | })); 36 | 37 | const Register = (): JSX.Element => { 38 | return ( 39 | 40 | 41 | Already have an account?   42 | 43 | Login 44 | 45 | 46 | 47 | 48 | 49 | 50 | Manage the job more effectively with Minimal 51 | 52 | register 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Get started absolutely free. 61 | 62 | 63 | Free forever. No credit card needed. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | By registering, I agree to Minimal  77 | 78 | Terms of Service 79 | 80 |  and  81 | 82 | Privacy Policy 83 | 84 | . 85 | 86 | 87 | 88 | 89 | Already have an account?  90 | 91 | Login 92 | 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | }; 100 | 101 | export default Register; 102 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate, Route, Routes } from 'react-router-dom'; 2 | import React, { ReactElement } from 'react'; 3 | import DashboardLayout from '@/layouts/dashboard'; 4 | import LogoOnlyLayout from '@/layouts/LogoOnlyLayout'; 5 | import Login from '@/pages/Login'; 6 | import Register from '@/pages/Register'; 7 | import DashboardApp from '@/pages/DashboardApp'; 8 | import Products from '@/pages/Products'; 9 | import Blog from '@/pages/Blog'; 10 | import User from '@/pages/User'; 11 | import NotFound from '@/pages/Page404'; 12 | 13 | export const Router = (): ReactElement => { 14 | return ( 15 | 16 | }> 17 | } /> 18 | } /> 19 | } /> 20 | } /> 21 | } /> 22 | 23 | }> 24 | } /> 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default Router; 35 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/theme/breakpoints.ts: -------------------------------------------------------------------------------- 1 | const breakpoints = { 2 | values: { 3 | xs: 0, 4 | sm: 600, 5 | md: 900, 6 | lg: 1200, 7 | xl: 1536 8 | } 9 | }; 10 | 11 | export default breakpoints; 12 | -------------------------------------------------------------------------------- /src/theme/globalStyles.ts: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@mui/styles'; 2 | 3 | const GlobalStyles = withStyles((theme) => ({ 4 | '@global': { 5 | '*': { 6 | margin: 0, 7 | padding: 0, 8 | boxSizing: 'border-box' 9 | }, 10 | html: { 11 | width: '100%', 12 | height: '100%', 13 | '-ms-text-size-adjust': '100%', 14 | '-webkit-overflow-scrolling': 'touch' 15 | }, 16 | body: { 17 | width: '100%', 18 | height: '100%' 19 | }, 20 | '#root': { 21 | width: '100%', 22 | height: '100%' 23 | }, 24 | input: { 25 | '&[type=number]': { 26 | MozAppearance: 'textfield', 27 | '&::-webkit-outer-spin-button': { margin: 0, WebkitAppearance: 'none' }, 28 | '&::-webkit-inner-spin-button': { margin: 0, WebkitAppearance: 'none' } 29 | } 30 | }, 31 | textarea: { 32 | '&::-webkit-input-placeholder': { color: theme?.palette?.text?.disabled }, 33 | '&::-moz-placeholder': { opacity: 1, color: theme?.palette?.text?.disabled }, 34 | '&:-ms-input-placeholder': { color: theme?.palette?.text?.disabled }, 35 | '&::placeholder': { color: theme?.palette?.text?.disabled } 36 | }, 37 | a: { color: theme.palette?.primary?.main }, 38 | img: { display: 'block', maxWidth: '100%' } 39 | } 40 | }))(() => null); 41 | 42 | export default GlobalStyles; 43 | -------------------------------------------------------------------------------- /src/theme/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { CssBaseline, ThemeOptions } from '@mui/material'; 3 | import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles'; 4 | import shape from '@/theme/shape'; 5 | import palette from '@/theme/palette'; 6 | import typography from '@/theme/typography'; 7 | import GlobalStyles from '@/theme/globalStyles'; 8 | import componentsOverride from '@/theme/overrides'; 9 | import shadows, { customShadows } from '@/theme/shadows'; 10 | 11 | interface Props { 12 | children; 13 | } 14 | 15 | export const ThemeConfig = (props: Props): JSX.Element => { 16 | const { children } = props; 17 | const themeOptions = useMemo( 18 | () => ({ 19 | palette, 20 | shape, 21 | // typography, 22 | // shadows, 23 | customShadows 24 | }), 25 | [] 26 | ); 27 | 28 | const theme = createTheme(themeOptions); 29 | theme.components = componentsOverride(theme); 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | {children} 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default ThemeConfig; 43 | -------------------------------------------------------------------------------- /src/theme/overrides/Autocomplete.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const Autocomplete = (theme: Theme) => { 4 | return { 5 | MuiAutocomplete: { 6 | styleOverrides: { 7 | paper: { 8 | // boxShadow: theme.customShadows.z20 9 | } 10 | } 11 | } 12 | }; 13 | }; 14 | 15 | export default Autocomplete; 16 | -------------------------------------------------------------------------------- /src/theme/overrides/Backdrop.ts: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles'; 2 | import { Theme } from '@mui/material'; 3 | 4 | const Backdrop = (theme: Theme) => { 5 | const varLow = alpha(theme.palette.grey[900], 0.48); 6 | const varHigh = alpha(theme.palette.grey[900], 1); 7 | 8 | return { 9 | MuiBackdrop: { 10 | styleOverrides: { 11 | root: { 12 | background: [ 13 | 'rgb(22,28,36)', 14 | `-moz-linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`, 15 | `-webkit-linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)`, 16 | `linear-gradient(75deg, ${varLow} 0%, ${varHigh} 100%)` 17 | ], 18 | '&.MuiBackdrop-invisible': { 19 | background: 'transparent' 20 | } 21 | } 22 | } 23 | } 24 | }; 25 | }; 26 | 27 | export default Backdrop; 28 | -------------------------------------------------------------------------------- /src/theme/overrides/Button.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const Button = (theme: Theme) => { 4 | return { 5 | MuiButton: { 6 | styleOverrides: { 7 | root: { 8 | '&:hover': { 9 | boxShadow: 'none' 10 | } 11 | }, 12 | sizeLarge: { 13 | height: 48 14 | }, 15 | containedInherit: { 16 | color: theme.palette.grey[800], 17 | // boxShadow: theme.customShadows.z8, 18 | '&:hover': { 19 | backgroundColor: theme.palette.grey[400] 20 | } 21 | }, 22 | containedPrimary: { 23 | // boxShadow: theme.customShadows.primary 24 | }, 25 | containedSecondary: { 26 | // boxShadow: theme.customShadows.secondary 27 | }, 28 | outlinedInherit: { 29 | border: `1px solid ${theme.palette.grey[500_32]}`, 30 | '&:hover': { 31 | backgroundColor: theme.palette.action.hover 32 | } 33 | }, 34 | textInherit: { 35 | '&:hover': { 36 | backgroundColor: theme.palette.action.hover 37 | } 38 | } 39 | } 40 | } 41 | }; 42 | }; 43 | 44 | export default Button; 45 | -------------------------------------------------------------------------------- /src/theme/overrides/Card.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const Card = (theme: Theme) => { 4 | return { 5 | MuiCard: { 6 | styleOverrides: { 7 | root: { 8 | // boxShadow: theme.customShadows.z16, 9 | borderRadius: theme.shape.borderRadius, 10 | position: 'relative', 11 | zIndex: 0 // Fix Safari overflow: hidden with border radius 12 | } 13 | } 14 | }, 15 | MuiCardHeader: { 16 | defaultProps: { 17 | titleTypographyProps: { variant: 'h6' }, 18 | subheaderTypographyProps: { variant: 'body2' } 19 | }, 20 | styleOverrides: { 21 | root: { 22 | padding: theme.spacing(3, 3, 0) 23 | } 24 | } 25 | }, 26 | MuiCardContent: { 27 | styleOverrides: { 28 | root: { 29 | padding: theme.spacing(3) 30 | } 31 | } 32 | } 33 | }; 34 | }; 35 | 36 | export default Card; 37 | -------------------------------------------------------------------------------- /src/theme/overrides/IconButton.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const IconButton = (theme: Theme) => { 4 | return { 5 | MuiIconButton: { 6 | variants: [ 7 | { 8 | props: { color: 'default' }, 9 | style: { 10 | '&:hover': { backgroundColor: theme.palette.action.hover } 11 | } 12 | }, 13 | { 14 | props: { color: 'inherit' }, 15 | style: { 16 | '&:hover': { backgroundColor: theme.palette.action.hover } 17 | } 18 | } 19 | ], 20 | 21 | styleOverrides: { 22 | root: {} 23 | } 24 | } 25 | }; 26 | }; 27 | 28 | export default IconButton; 29 | -------------------------------------------------------------------------------- /src/theme/overrides/Input.ts: -------------------------------------------------------------------------------- 1 | import { ThemeOptions } from '@mui/material'; 2 | 3 | const Input = (theme: ThemeOptions): any => { 4 | return { 5 | MuiInputBase: { 6 | styleOverrides: { 7 | root: { 8 | '&.Mui-disabled': { 9 | '& svg': { color: theme.palette?.text?.disabled } 10 | } 11 | }, 12 | input: { 13 | '&::placeholder': { 14 | opacity: 1, 15 | color: theme.palette?.text?.disabled 16 | } 17 | } 18 | } 19 | }, 20 | MuiInput: { 21 | styleOverrides: { 22 | underline: { 23 | '&:before': { 24 | borderBottomColor: theme.palette?.grey?.['500'] 25 | } 26 | } 27 | } 28 | }, 29 | MuiFilledInput: { 30 | styleOverrides: { 31 | root: { 32 | backgroundColor: theme.palette?.grey?.['500'], 33 | '&:hover': { 34 | backgroundColor: theme.palette?.grey?.['500'] 35 | }, 36 | '&.Mui-focused': { 37 | backgroundColor: theme.palette?.action?.focus 38 | }, 39 | '&.Mui-disabled': { 40 | backgroundColor: theme.palette?.action?.disabledBackground 41 | } 42 | }, 43 | underline: { 44 | '&:before': { 45 | borderBottomColor: theme.palette?.grey?.['500'] 46 | } 47 | } 48 | } 49 | }, 50 | MuiOutlinedInput: { 51 | styleOverrides: { 52 | root: { 53 | '& .MuiOutlinedInput-notchedOutline': { 54 | borderColor: theme.palette?.grey?.['500'] 55 | }, 56 | '&.Mui-disabled': { 57 | '& .MuiOutlinedInput-notchedOutline': { 58 | borderColor: theme.palette?.action?.disabledBackground 59 | } 60 | } 61 | } 62 | } 63 | } 64 | }; 65 | }; 66 | 67 | export default Input; 68 | -------------------------------------------------------------------------------- /src/theme/overrides/Lists.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const Lists = (theme: Theme) => { 4 | return { 5 | MuiListItemIcon: { 6 | styleOverrides: { 7 | root: { 8 | color: 'inherit', 9 | minWidth: 'auto', 10 | marginRight: theme.spacing(2) 11 | } 12 | } 13 | }, 14 | MuiListItemAvatar: { 15 | styleOverrides: { 16 | root: { 17 | minWidth: 'auto', 18 | marginRight: theme.spacing(2) 19 | } 20 | } 21 | }, 22 | MuiListItemText: { 23 | styleOverrides: { 24 | root: { 25 | marginTop: 0, 26 | marginBottom: 0 27 | }, 28 | multiline: { 29 | marginTop: 0, 30 | marginBottom: 0 31 | } 32 | } 33 | } 34 | }; 35 | }; 36 | 37 | export default Lists; 38 | -------------------------------------------------------------------------------- /src/theme/overrides/Paper.ts: -------------------------------------------------------------------------------- 1 | const Paper = () => { 2 | return { 3 | MuiPaper: { 4 | defaultProps: { 5 | elevation: 0 6 | }, 7 | 8 | styleOverrides: { 9 | root: { 10 | backgroundImage: 'none' 11 | } 12 | } 13 | } 14 | }; 15 | }; 16 | export default Paper; 17 | -------------------------------------------------------------------------------- /src/theme/overrides/Tooltip.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const Tooltip = (theme: Theme) => { 4 | return { 5 | MuiTooltip: { 6 | styleOverrides: { 7 | tooltip: { 8 | backgroundColor: theme.palette.grey[800] 9 | }, 10 | arrow: { 11 | color: theme.palette.grey[800] 12 | } 13 | } 14 | } 15 | }; 16 | }; 17 | 18 | export default Tooltip; 19 | -------------------------------------------------------------------------------- /src/theme/overrides/Typography.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '@mui/material'; 2 | 3 | const Typography = (theme: Theme) => { 4 | return { 5 | MuiTypography: { 6 | styleOverrides: { 7 | paragraph: { 8 | marginBottom: theme.spacing(2) 9 | }, 10 | gutterBottom: { 11 | marginBottom: theme.spacing(1) 12 | } 13 | } 14 | } 15 | }; 16 | }; 17 | 18 | export default Typography; 19 | -------------------------------------------------------------------------------- /src/theme/overrides/index.ts: -------------------------------------------------------------------------------- 1 | import { merge } from 'lodash'; 2 | import Card from '@/theme/overrides/Card'; 3 | import Lists from '@/theme/overrides/Lists'; 4 | import Paper from '@/theme/overrides/Paper'; 5 | import Input from '@/theme/overrides/Input'; 6 | import Button from '@/theme/overrides/Button'; 7 | import Tooltip from '@/theme/overrides/Tooltip'; 8 | import Backdrop from '@/theme/overrides/Backdrop'; 9 | import Typography from '@/theme/overrides/Typography'; 10 | import IconButton from '@/theme/overrides/IconButton'; 11 | import Autocomplete from '@/theme/overrides/Autocomplete'; 12 | import { Theme } from '@mui/material'; 13 | 14 | const ComponentsOverrides = (theme: Theme) => { 15 | return merge( 16 | Card(theme), 17 | Lists(theme), 18 | Paper(), 19 | Input(theme), 20 | Button(theme), 21 | Tooltip(theme), 22 | Backdrop(theme), 23 | Typography(theme), 24 | IconButton(theme), 25 | Autocomplete(theme) 26 | ); 27 | }; 28 | 29 | export default ComponentsOverrides; 30 | -------------------------------------------------------------------------------- /src/theme/palette.ts: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles'; 2 | 3 | export const createGradient = (color1: string, color2: string): string => { 4 | return `linear-gradient(to bottom, ${color1}, ${color2})`; 5 | }; 6 | 7 | // SETUP COLORS 8 | const GREY = { 9 | 0: '#FFFFFF', 10 | 100: '#F9FAFB', 11 | 200: '#F4F6F8', 12 | 300: '#DFE3E8', 13 | 400: '#C4CDD5', 14 | 500: '#919EAB', 15 | 600: '#637381', 16 | 700: '#454F5B', 17 | 800: '#212B36', 18 | 900: '#161C24', 19 | 500_8: alpha('#919EAB', 0.08), 20 | 500_12: alpha('#919EAB', 0.12), 21 | 500_16: alpha('#919EAB', 0.16), 22 | 500_24: alpha('#919EAB', 0.24), 23 | 500_32: alpha('#919EAB', 0.32), 24 | 500_48: alpha('#919EAB', 0.48), 25 | 500_56: alpha('#919EAB', 0.56), 26 | 500_80: alpha('#919EAB', 0.8) 27 | }; 28 | 29 | const PRIMARY = { 30 | lighter: '#C8FACD', 31 | light: '#5BE584', 32 | main: '#00AB55', 33 | dark: '#007B55', 34 | darker: '#005249', 35 | contrastText: '#fff' 36 | }; 37 | const SECONDARY = { 38 | lighter: '#D6E4FF', 39 | light: '#84A9FF', 40 | main: '#3366FF', 41 | dark: '#1939B7', 42 | darker: '#091A7A', 43 | contrastText: '#fff' 44 | }; 45 | const INFO = { 46 | lighter: '#D0F2FF', 47 | light: '#74CAFF', 48 | main: '#1890FF', 49 | dark: '#0C53B7', 50 | darker: '#04297A', 51 | contrastText: '#fff' 52 | }; 53 | const SUCCESS = { 54 | lighter: '#E9FCD4', 55 | light: '#AAF27F', 56 | main: '#54D62C', 57 | dark: '#229A16', 58 | darker: '#08660D', 59 | contrastText: GREY[800] 60 | }; 61 | const WARNING = { 62 | lighter: '#FFF7CD', 63 | light: '#FFE16A', 64 | main: '#FFC107', 65 | dark: '#B78103', 66 | darker: '#7A4F01', 67 | contrastText: GREY[800] 68 | }; 69 | const ERROR = { 70 | lighter: '#FFE7D9', 71 | light: '#FFA48D', 72 | main: '#FF4842', 73 | dark: '#B72136', 74 | darker: '#7A0C2E', 75 | contrastText: '#fff' 76 | }; 77 | 78 | const GRADIENTS = { 79 | primary: createGradient(PRIMARY.light, PRIMARY.main), 80 | info: createGradient(INFO.light, INFO.main), 81 | success: createGradient(SUCCESS.light, SUCCESS.main), 82 | warning: createGradient(WARNING.light, WARNING.main), 83 | error: createGradient(ERROR.light, ERROR.main) 84 | }; 85 | 86 | const palette = { 87 | common: { black: '#000', white: '#fff' }, 88 | primary: { ...PRIMARY }, 89 | secondary: { ...SECONDARY }, 90 | info: { ...INFO }, 91 | success: { ...SUCCESS }, 92 | warning: { ...WARNING }, 93 | error: { ...ERROR }, 94 | grey: GREY, 95 | gradients: GRADIENTS, 96 | divider: GREY[500_24], 97 | text: { primary: GREY[800], secondary: GREY[600], disabled: GREY[500] }, 98 | background: { paper: '#fff', default: '#fff', neutral: GREY[200] }, 99 | action: { 100 | active: GREY[600], 101 | hover: GREY[500_8], 102 | selected: GREY[500_16], 103 | disabled: GREY[500_80], 104 | disabledBackground: GREY[500_24], 105 | focus: GREY[500_24], 106 | hoverOpacity: 0.08, 107 | disabledOpacity: 0.48 108 | } 109 | }; 110 | 111 | export default palette; 112 | -------------------------------------------------------------------------------- /src/theme/shadows.ts: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles'; 2 | import palette from './palette'; 3 | 4 | const LIGHT_MODE = palette.grey[500]; 5 | 6 | const createShadow = (color) => { 7 | const transparent1 = alpha(color, 0.2); 8 | const transparent2 = alpha(color, 0.14); 9 | const transparent3 = alpha(color, 0.12); 10 | return [ 11 | 'none', 12 | `0px 2px 1px -1px ${transparent1},0px 1px 1px 0px ${transparent2},0px 1px 3px 0px ${transparent3}`, 13 | `0px 3px 1px -2px ${transparent1},0px 2px 2px 0px ${transparent2},0px 1px 5px 0px ${transparent3}`, 14 | `0px 3px 3px -2px ${transparent1},0px 3px 4px 0px ${transparent2},0px 1px 8px 0px ${transparent3}`, 15 | `0px 2px 4px -1px ${transparent1},0px 4px 5px 0px ${transparent2},0px 1px 10px 0px ${transparent3}`, 16 | `0px 3px 5px -1px ${transparent1},0px 5px 8px 0px ${transparent2},0px 1px 14px 0px ${transparent3}`, 17 | `0px 3px 5px -1px ${transparent1},0px 6px 10px 0px ${transparent2},0px 1px 18px 0px ${transparent3}`, 18 | `0px 4px 5px -2px ${transparent1},0px 7px 10px 1px ${transparent2},0px 2px 16px 1px ${transparent3}`, 19 | `0px 5px 5px -3px ${transparent1},0px 8px 10px 1px ${transparent2},0px 3px 14px 2px ${transparent3}`, 20 | `0px 5px 6px -3px ${transparent1},0px 9px 12px 1px ${transparent2},0px 3px 16px 2px ${transparent3}`, 21 | `0px 6px 6px -3px ${transparent1},0px 10px 14px 1px ${transparent2},0px 4px 18px 3px ${transparent3}`, 22 | `0px 6px 7px -4px ${transparent1},0px 11px 15px 1px ${transparent2},0px 4px 20px 3px ${transparent3}`, 23 | `0px 7px 8px -4px ${transparent1},0px 12px 17px 2px ${transparent2},0px 5px 22px 4px ${transparent3}`, 24 | `0px 7px 8px -4px ${transparent1},0px 13px 19px 2px ${transparent2},0px 5px 24px 4px ${transparent3}`, 25 | `0px 7px 9px -4px ${transparent1},0px 14px 21px 2px ${transparent2},0px 5px 26px 4px ${transparent3}`, 26 | `0px 8px 9px -5px ${transparent1},0px 15px 22px 2px ${transparent2},0px 6px 28px 5px ${transparent3}`, 27 | `0px 8px 10px -5px ${transparent1},0px 16px 24px 2px ${transparent2},0px 6px 30px 5px ${transparent3}`, 28 | `0px 8px 11px -5px ${transparent1},0px 17px 26px 2px ${transparent2},0px 6px 32px 5px ${transparent3}`, 29 | `0px 9px 11px -5px ${transparent1},0px 18px 28px 2px ${transparent2},0px 7px 34px 6px ${transparent3}`, 30 | `0px 9px 12px -6px ${transparent1},0px 19px 29px 2px ${transparent2},0px 7px 36px 6px ${transparent3}`, 31 | `0px 10px 13px -6px ${transparent1},0px 20px 31px 3px ${transparent2},0px 8px 38px 7px ${transparent3}`, 32 | `0px 10px 13px -6px ${transparent1},0px 21px 33px 3px ${transparent2},0px 8px 40px 7px ${transparent3}`, 33 | `0px 10px 14px -6px ${transparent1},0px 22px 35px 3px ${transparent2},0px 8px 42px 7px ${transparent3}`, 34 | `0px 11px 14px -7px ${transparent1},0px 23px 36px 3px ${transparent2},0px 9px 44px 8px ${transparent3}`, 35 | `0px 11px 15px -7px ${transparent1},0px 24px 38px 3px ${transparent2},0px 9px 46px 8px ${transparent3}` 36 | ]; 37 | }; 38 | 39 | const createCustomShadow = (color) => { 40 | const transparent = alpha(color, 0.24); 41 | 42 | return { 43 | z1: `0 1px 2px 0 ${transparent}`, 44 | z8: `0 8px 16px 0 ${transparent}`, 45 | z12: `0 0 2px 0 ${transparent}, 0 12px 24px 0 ${transparent}`, 46 | z16: `0 0 2px 0 ${transparent}b, 0 16px 32px -4px ${transparent}`, 47 | z20: `0 0 2px 0 ${transparent}, 0 20px 40px -4px ${transparent}`, 48 | z24: `0 0 4px 0 ${transparent}, 0 24px 48px 0 ${transparent}`, 49 | primary: `0 8px 16px 0 ${alpha(palette.primary.main, 0.24)}`, 50 | secondary: `0 8px 16px 0 ${alpha(palette.secondary.main, 0.24)}`, 51 | info: `0 8px 16px 0 ${alpha(palette.info.main, 0.24)}`, 52 | success: `0 8px 16px 0 ${alpha(palette.success.main, 0.24)}`, 53 | warning: `0 8px 16px 0 ${alpha(palette.warning.main, 0.24)}`, 54 | error: `0 8px 16px 0 ${alpha(palette.error.main, 0.24)}` 55 | }; 56 | }; 57 | 58 | export const customShadows = createCustomShadow(LIGHT_MODE); 59 | 60 | const shadows = createShadow(LIGHT_MODE); 61 | 62 | export default shadows; 63 | -------------------------------------------------------------------------------- /src/theme/shape.ts: -------------------------------------------------------------------------------- 1 | const shape = { 2 | borderRadius: 8, 3 | borderRadiusSm: 12, 4 | borderRadiusMd: 16 5 | }; 6 | 7 | export default shape; 8 | -------------------------------------------------------------------------------- /src/theme/typography.ts: -------------------------------------------------------------------------------- 1 | export const pxToRem = (value: number): string => { 2 | return `${value / 16}rem`; 3 | }; 4 | 5 | function responsiveFontSizes({ sm, md, lg }) { 6 | return { 7 | '@media (min-width:600px)': { 8 | fontSize: pxToRem(sm) 9 | }, 10 | '@media (min-width:900px)': { 11 | fontSize: pxToRem(md) 12 | }, 13 | '@media (min-width:1200px)': { 14 | fontSize: pxToRem(lg) 15 | } 16 | }; 17 | } 18 | 19 | const FONT_PRIMARY = 'Public Sans, sans-serif'; 20 | 21 | const typography = { 22 | fontFamily: FONT_PRIMARY, 23 | fontWeightRegular: 400, 24 | fontWeightMedium: 600, 25 | fontWeightBold: 700, 26 | h1: { 27 | fontWeight: 700, 28 | lineHeight: 80 / 64, 29 | fontSize: pxToRem(40), 30 | ...responsiveFontSizes({ sm: 52, md: 58, lg: 64 }) 31 | }, 32 | h2: { 33 | fontWeight: 700, 34 | lineHeight: 64 / 48, 35 | fontSize: pxToRem(32), 36 | ...responsiveFontSizes({ sm: 40, md: 44, lg: 48 }) 37 | }, 38 | h3: { 39 | fontWeight: 700, 40 | lineHeight: 1.5, 41 | fontSize: pxToRem(24), 42 | ...responsiveFontSizes({ sm: 26, md: 30, lg: 32 }) 43 | }, 44 | h4: { 45 | fontWeight: 700, 46 | lineHeight: 1.5, 47 | fontSize: pxToRem(20), 48 | ...responsiveFontSizes({ sm: 20, md: 24, lg: 24 }) 49 | }, 50 | h5: { 51 | fontWeight: 700, 52 | lineHeight: 1.5, 53 | fontSize: pxToRem(18), 54 | ...responsiveFontSizes({ sm: 19, md: 20, lg: 20 }) 55 | }, 56 | h6: { 57 | fontWeight: 700, 58 | lineHeight: 28 / 18, 59 | fontSize: pxToRem(17), 60 | ...responsiveFontSizes({ sm: 18, md: 18, lg: 18 }) 61 | }, 62 | subtitle1: { 63 | fontWeight: 600, 64 | lineHeight: 1.5, 65 | fontSize: pxToRem(16) 66 | }, 67 | subtitle2: { 68 | fontWeight: 600, 69 | lineHeight: 22 / 14, 70 | fontSize: pxToRem(14) 71 | }, 72 | body1: { 73 | lineHeight: 1.5, 74 | fontSize: pxToRem(16) 75 | }, 76 | body2: { 77 | lineHeight: 22 / 14, 78 | fontSize: pxToRem(14) 79 | }, 80 | caption: { 81 | lineHeight: 1.5, 82 | fontSize: pxToRem(12) 83 | }, 84 | overline: { 85 | fontWeight: 700, 86 | lineHeight: 1.5, 87 | fontSize: pxToRem(12), 88 | letterSpacing: 1.1, 89 | textTransform: 'uppercase' 90 | }, 91 | button: { 92 | fontWeight: 700, 93 | lineHeight: 24 / 14, 94 | fontSize: pxToRem(14), 95 | textTransform: 'capitalize' 96 | } 97 | }; 98 | 99 | export default typography; 100 | -------------------------------------------------------------------------------- /src/utils/formatNumber.ts: -------------------------------------------------------------------------------- 1 | import { replace } from 'lodash'; 2 | import numeral from 'numeral'; 3 | 4 | export function fCurrency(number: number): string { 5 | return numeral(number).format(Number.isInteger(number) ? '$0,0' : '$0,0.00'); 6 | } 7 | 8 | export function fPercent(number: number): string { 9 | return numeral(number / 100).format('0.0%'); 10 | } 11 | 12 | export function fNumber(number: number): string { 13 | return numeral(number).format(); 14 | } 15 | 16 | export function fShortenNumber(number: number): string { 17 | return replace(numeral(number).format('0.00a'), '.00', ''); 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/formatTime.ts: -------------------------------------------------------------------------------- 1 | import { format, formatDistanceToNow } from 'date-fns'; 2 | 3 | export function fDate(date: string): string { 4 | return format(new Date(date), 'dd MMMM yyyy'); 5 | } 6 | 7 | export function fDateTime(date: string): string { 8 | return format(new Date(date), 'dd MMM yyyy HH:mm'); 9 | } 10 | 11 | export function fDateTimeSuffix(date: string): string { 12 | return format(new Date(date), 'dd/MM/yyyy hh:mm p'); 13 | } 14 | 15 | export function fToNow(date: string): string { 16 | return formatDistanceToNow(new Date(date), { 17 | addSuffix: true 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/mockImages.ts: -------------------------------------------------------------------------------- 1 | const baseUrl = 'https://image.xiaomo.info/mock'; 2 | export const mockImgCover = (index: number): string => `${baseUrl}/cover/cover_${index}.jpg`; 3 | export const mockImgProduct = (index: number): string => `${baseUrl}/product/product_${index}.jpg`; 4 | export const mockImgAvatar = (index: number): string => `${baseUrl}/avatar/avatar_${index}.jpg`; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": [ 5 | "DOM", 6 | "DOM.Iterable", 7 | "ESNext" 8 | ], 9 | "types": [ 10 | "vite/client" 11 | ], 12 | "allowJs": false, 13 | "skipLibCheck": true, 14 | "esModuleInterop": false, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "noImplicitAny": false, 18 | "forceConsistentCasingInFileNames": true, 19 | "module": "ESNext", 20 | "moduleResolution": "Node", 21 | "resolveJsonModule": true, 22 | "isolatedModules": true, 23 | "noEmit": true, 24 | "jsx": "react", 25 | "baseUrl": ".", 26 | "paths": { 27 | "@/*": [ 28 | "./src/*" 29 | ] 30 | } 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import * as path from 'path'; 4 | import { existsSync } from 'fs'; 5 | import * as dotenv from 'dotenv'; 6 | 7 | // Dotenv 是一个零依赖的模块,它能将环境变量中的变量从 .env 文件加载到 process.env 中 8 | dotenv.config({ 9 | path: existsSync('.env') ? '.env' : path.resolve('envs', `.env.${process.env.NODE_ENV}`) 10 | }); 11 | 12 | // https://vitejs.dev/config/ 13 | export default defineConfig({ 14 | plugins: [react()], 15 | resolve: { 16 | alias: { 17 | '@@': path.resolve(__dirname), 18 | '@': path.resolve(__dirname, 'src') 19 | } 20 | }, 21 | server: { 22 | cors: true, 23 | port: process.env.VITE_PORT as unknown as number, 24 | hmr: { 25 | host: 'localhost', 26 | protocol: 'ws', 27 | port: process.env.VITE_PORT as unknown as number 28 | } 29 | } 30 | }); 31 | --------------------------------------------------------------------------------