├── .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 | 
10 | 
11 | 
12 | 
13 | 
14 | 
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 |
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 | }
77 | >
78 | View all
79 |
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 |
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 |
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 | }
32 | >
33 | Sort By:
34 |
35 | Newest
36 |
37 |
38 |
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 |
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 |
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 |
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 |
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 |
56 |
57 |
58 |
59 |
60 | {LANGS.map((option) => (
61 |
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 | }
31 | >
32 | New Post
33 |
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 |
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 |
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 |
--------------------------------------------------------------------------------