Full Code of m6v3l9/react-material-admin for AI

main 32a2764e44f4 cached
125 files
225.7 KB
57.5k tokens
71 symbols
1 requests
Download .txt
Showing preview only (255K chars total). Download the full file or copy to clipboard to get everything.
Repository: m6v3l9/react-material-admin
Branch: main
Commit: 32a2764e44f4
Files: 125
Total size: 225.7 KB

Directory structure:
gitextract_ux3vinbg/

├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── public/
│   ├── 404.html
│   ├── index.html
│   ├── locales/
│   │   ├── en/
│   │   │   └── translation.json
│   │   └── fr/
│   │       └── translation.json
│   ├── manifest.json
│   └── robots.txt
├── src/
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── AppRoutes.tsx
│   ├── admin/
│   │   ├── components/
│   │   │   ├── AdminAppBar.tsx
│   │   │   ├── AdminDrawer.tsx
│   │   │   ├── AdminToolbar.tsx
│   │   │   └── RecentNotifications.tsx
│   │   ├── config/
│   │   │   ├── activity.ts
│   │   │   └── notification.ts
│   │   ├── hooks/
│   │   │   ├── useActivityLogs.ts
│   │   │   ├── useNotifications.ts
│   │   │   ├── useProfileInfo.ts
│   │   │   └── useUpdateProfileInfo.ts
│   │   ├── pages/
│   │   │   ├── Admin.tsx
│   │   │   ├── Dashboard.tsx
│   │   │   ├── Faq.tsx
│   │   │   ├── HelpCenter.tsx
│   │   │   ├── Home.tsx
│   │   │   ├── Profile.tsx
│   │   │   ├── ProfileActivity.tsx
│   │   │   ├── ProfileInformation.tsx
│   │   │   └── ProfilePassword.tsx
│   │   ├── types/
│   │   │   ├── activityLog.ts
│   │   │   ├── notification.ts
│   │   │   └── profileInfo.ts
│   │   └── widgets/
│   │       ├── AchievementWidget.tsx
│   │       ├── ActivityWidget.tsx
│   │       ├── BudgetWidget.tsx
│   │       ├── CircleProgressWidget.tsx
│   │       ├── FollowersWidget.tsx
│   │       ├── MeetingWidgets.tsx
│   │       ├── OverviewWidget.tsx
│   │       ├── PersonalTargetsWidget.tsx
│   │       ├── ProgressWidget.tsx
│   │       ├── SalesByAgeWidget.tsx
│   │       ├── SalesByCategoryWidget.tsx
│   │       ├── SalesHistoryWidget.tsx
│   │       ├── TeamProgressWidget.tsx
│   │       ├── UsersWidget.tsx
│   │       ├── ViewsWidget.tsx
│   │       └── WelcomeWidget.tsx
│   ├── auth/
│   │   ├── contexts/
│   │   │   └── AuthProvider.tsx
│   │   ├── hooks/
│   │   │   ├── useForgotPassword.ts
│   │   │   ├── useForgotPasswordSubmit.ts
│   │   │   ├── useLogin.ts
│   │   │   ├── useLogout.ts
│   │   │   ├── useRegister.ts
│   │   │   ├── useUpdatePassword.ts
│   │   │   └── useUserInfo.ts
│   │   ├── pages/
│   │   │   ├── ForgotPassword.tsx
│   │   │   ├── ForgotPasswordSubmit.tsx
│   │   │   ├── Login.tsx
│   │   │   └── Register.tsx
│   │   └── types/
│   │       └── userInfo.ts
│   ├── calendar/
│   │   ├── components/
│   │   │   ├── Calendar.tsx
│   │   │   └── EventDialog.tsx
│   │   ├── hooks/
│   │   │   ├── useAddEvent.ts
│   │   │   ├── useDeleteEvent.ts
│   │   │   ├── useEvents.ts
│   │   │   └── useUpdateEvent.ts
│   │   ├── pages/
│   │   │   └── CalendarApp.tsx
│   │   └── types/
│   │       └── event.ts
│   ├── core/
│   │   ├── components/
│   │   │   ├── BoxedLayout.tsx
│   │   │   ├── ConfirmDialog.tsx
│   │   │   ├── Empty.tsx
│   │   │   ├── Footer.tsx
│   │   │   ├── Loader.tsx
│   │   │   ├── Logo.tsx
│   │   │   ├── PrivateRoute.tsx
│   │   │   ├── QueryWrapper.tsx
│   │   │   ├── Result.tsx
│   │   │   ├── SelectToolbar.tsx
│   │   │   ├── SettingsDrawer.tsx
│   │   │   └── SvgContainer.tsx
│   │   ├── config/
│   │   │   ├── i18n.ts
│   │   │   └── layout.ts
│   │   ├── contexts/
│   │   │   ├── SettingsProvider.tsx
│   │   │   └── SnackbarProvider.tsx
│   │   ├── hooks/
│   │   │   ├── useDateLocale.ts
│   │   │   ├── useLocalStorage.ts
│   │   │   └── usePageTracking.ts
│   │   ├── pages/
│   │   │   ├── Forbidden.tsx
│   │   │   ├── NotFound.tsx
│   │   │   └── UnderConstructions.tsx
│   │   ├── theme/
│   │   │   ├── components.tsx
│   │   │   ├── index.ts
│   │   │   ├── mixins.ts
│   │   │   ├── palette.ts
│   │   │   ├── shape.ts
│   │   │   ├── transitions.ts
│   │   │   └── typography.ts
│   │   └── utils/
│   │       ├── crudUtils.ts
│   │       └── selectUtils.ts
│   ├── index.tsx
│   ├── landing/
│   │   ├── components/
│   │   │   └── LandingLayout.tsx
│   │   └── pages/
│   │       └── Landing.tsx
│   ├── mocks/
│   │   ├── activityLogs.json
│   │   ├── events.json
│   │   ├── notifications.json
│   │   ├── profileInfo.json
│   │   ├── server.ts
│   │   ├── userInfo.json
│   │   └── users.json
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   ├── setupTests.ts
│   └── users/
│       ├── components/
│       │   ├── UserDialog.tsx
│       │   └── UserTable.tsx
│       ├── hooks/
│       │   ├── useAddUser.ts
│       │   ├── useDeleteUsers.ts
│       │   ├── useUpdateUser.ts
│       │   └── useUsers.ts
│       ├── pages/
│       │   └── UserManagement.tsx
│       └── types/
│           └── user.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*


================================================
FILE: LICENSE
================================================
Copyright 2021 Vaniya

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README.md
================================================
<p align="center">
  <a href="https://m6v3l9.github.io/react-material-admin/" rel="noopener" target="_blank"><img width="120" src="https://m6v3l9.github.io/react-material-admin/logo.svg" alt="React Material Admin logo"></a></p>
</p>

<h1 align="center">React Material Admin</h1>
<p align="center">
<b>react-material-admin</b> is a free and open-source admin  application including many real-world examples. It is based on React and Material-UI.
</p>

[![react-material-admin-demo](https://cdn.dribbble.com/users/6538082/screenshots/15805144/media/5687464c7190019afb748863ac6957d3.png?compress=1&resize=1200x900)](https://m6v3l9.github.io/react-material-admin/)

## Getting Started

```
# Install dependencies
yarn install

# Run the app
yarn start
```

This will automatically open [http://localhost:3000](http://localhost:3000).

## Features

```
- Admin
  - Home
  - Dashboard/Charts
  - FAQ
  - Help Center
  - Profile Activity
  - Profile Information
  - Profile Password
- Auth
  - Forgot Password
  - Forgot Password Submit
  - Login
  - Register
- Calendar App
- Core
  - Forbidden
  - Not Found
  - Under Constructions
- Landing
- User Management
```

## Technologies

| Package               | Description                                    | Docs                                                                            |
| --------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------- |
| Analytics             | Google Analytics                               | [Docs](https://analytics.google.com/analytics/web/react-ga)                     |
| Bundle Size Analyzer  | Source map explorer                            | [Docs](https://create-react-app.dev/docs/analyzing-the-bundle-size)             |
| Charts                | Recharts                                       | [Docs](https://recharts.org/)                                                   |
| CI                    | Github CI                                      | [Docs]()                                                                        |
| Code Splitting        | Route-based code splitting (included in React) | [Docs](https://reactjs.org/docs/code-splitting.html#route-based-code-splitting) |
| Components            | Material-UI                                    | [Docs](https://material-ui.com/)                                                |
| Data Fetching         | React Query Toolkit                            | [Docs](https://react-query.tanstack.com/)                                       |
| Deployment            | Github Pages                                   | [Docs](https://create-react-app.dev/docs/deployment#github-pages)               |
| Environment Variables | Dotenv (included in Create React App)          | [Docs](https://create-react-app.dev/docs/adding-custom-environment-variables)   |
| Error Monitoring      | Sentry                                         | [Docs](https://docs.sentry.io/platforms/javascript/guides/react/)               |
| Form                  | Formik                                         | [Docs](https://formik.org/)                                                     |
| I18N                  | react-i18next                                  | [Docs](https://react.i18next.com/)                                              |
| Routing               | React Router                                   | [Docs](https://reactrouter.com/)                                                |
| Theming (+ dark mode) | Material-UI                                    | [Docs](https://material-ui.com/customization/theming/)                          |
| Toolchain             | Create React App                               | [Docs](https://create-react-app.dev/)                                           |
| TypeScript            | TypeScript                                     | [Docs](https://create-react-app.dev/docs/adding-typescript/)                    |
| Validation            | Yup                                            | [Docs](https://github.com/jquense/yup)                                          |

## Coming Soon

| Package      | Description                                 | Docs                            |
| ------------ | ------------------------------------------- | ------------------------------- |
| Drag & Drop  | Add Projects page with Drag & Drop features |                                 |
| E2E Testing  | Cypress                                     | [Docs](https://www.cypress.io/) |
| Unit Testing | Jest                                        | [Docs](https://jestjs.io/)      |

## License

This project is licensed under the terms of the
[MIT license](/LICENSE).


================================================
FILE: package.json
================================================
{
  "name": "react-material-admin",
  "version": "0.1.0-alpha",
  "description": "Free and open-source admin application made with React",
  "author": "m6v3l9 <m6v3l9@gmail.com>",
  "homepage": "https://m6v3l9.github.io/react-material-admin",
  "license": "MIT",
  "dependencies": {
    "@emotion/react": "^11.1.5",
    "@emotion/styled": "^11.1.5",
    "@fullcalendar/daygrid": "5.7.0",
    "@fullcalendar/react": "5.7.0",
    "@material-ui/core": "^5.0.0-alpha.35",
    "@material-ui/icons": "^5.0.0-alpha.35",
    "@material-ui/lab": "5.0.0-alpha.35",
    "@sentry/react": "^6.4.1",
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "axios": "^0.21.1",
    "axios-mock-adapter": "^1.19.0",
    "date-fns": "^2.19.0",
    "env-cmd": "^10.1.0",
    "formik": "^2.2.6",
    "gh-pages": "^3.1.0",
    "history": "^5.0.0",
    "i18next": "^20.1.0",
    "i18next-browser-languagedetector": "^6.1.0",
    "i18next-xhr-backend": "^3.2.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-error-boundary": "^3.1.3",
    "react-i18next": "^11.8.11",
    "react-query": "^3.16.0",
    "react-router": "6.0.0-beta.0",
    "react-router-dom": "6.0.0-beta.0",
    "react-scripts": "4.0.3",
    "recharts": "^2.0.9",
    "source-map-explorer": "^2.5.2",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1",
    "yup": "^0.32.9"
  },
  "scripts": {
    "analyze": "source-map-explorer 'build/static/js/*.js'",
    "build": "react-scripts build",
    "build:staging": "env-cmd -f .env.staging npm run build",
    "build:production": "env-cmd -f .env.production npm run build",
    "predeploy": "yarn run build:production",
    "deploy": "gh-pages -d build",
    "start": "react-scripts start",
    "test": "react-scripts test"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}


================================================
FILE: public/404.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Single Page Apps for GitHub Pages</title>
    <script type="text/javascript">
      // Single Page Apps for GitHub Pages
      // MIT License
      // https://github.com/rafgraph/spa-github-pages
      // This script takes the current url and converts the path and query
      // string into just a query string, and then redirects the browser
      // to the new url with only a query string and hash fragment,
      // e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
      // https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
      // Note: this 404.html file must be at least 512 bytes for it to work
      // with Internet Explorer (it is currently > 512 bytes)

      // If you're creating a Project Pages site and NOT using a custom domain,
      // then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).
      // This way the code will only replace the route part of the path, and not
      // the real directory in which the app resides, for example:
      // https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
      // https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe
      // Otherwise, leave pathSegmentsToKeep as 0.
      var pathSegmentsToKeep = 1;

      var l = window.location;
      l.replace(
        l.protocol +
          "//" +
          l.hostname +
          (l.port ? ":" + l.port : "") +
          l.pathname
            .split("/")
            .slice(0, 1 + pathSegmentsToKeep)
            .join("/") +
          "/?/" +
          l.pathname
            .slice(1)
            .split("/")
            .slice(pathSegmentsToKeep)
            .join("/")
            .replace(/&/g, "~and~") +
          (l.search ? "&" + l.search.slice(1).replace(/&/g, "~and~") : "") +
          l.hash
      );
    </script>
  </head>
  <body></body>
</html>


================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="React Admin dashboard made with React" />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <base href="%PUBLIC_URL%/" />
    <title>%REACT_APP_NAME%</title>

    <link rel="preconnect" href="https://fonts.gstatic.com" />
    <link
      href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
      rel="stylesheet"
    />

    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script
      async
      src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_GA_TRACKING_ID%"
    ></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag() {
        dataLayer.push(arguments);
      }
      gtag("js", new Date());

      gtag("config", "%REACT_APP_GA_TRACKING_ID%");
    </script>

    <!-- Start Single Page Apps for GitHub Pages -->
    <script type="text/javascript">
      // Single Page Apps for GitHub Pages
      // MIT License
      // https://github.com/rafgraph/spa-github-pages
      // This script checks to see if a redirect is present in the query string,
      // converts it back into the correct url and adds it to the
      // browser's history using window.history.replaceState(...),
      // which won't cause the browser to attempt to load the new url.
      // When the single page app is loaded further down in this file,
      // the correct url will be waiting in the browser's history for
      // the single page app to route accordingly.
      (function (l) {
        if (l.search[1] === "/") {
          var decoded = l.search
            .slice(1)
            .split("&")
            .map(function (s) {
              return s.replace(/~and~/g, "&");
            })
            .join("?");
          window.history.replaceState(
            null,
            null,
            l.pathname.slice(0, -1) + decoded + l.hash
          );
        }
      })(window.location);
    </script>
    <!-- End Single Page Apps for GitHub Pages -->
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

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

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


================================================
FILE: public/locales/en/translation.json
================================================
{
  "admin": {
    "drawer": {
      "menu": {
        "calendar": "Calendar",
        "dashboard": "Dashboard",
        "help": "Help",
        "home": "Home",
        "projects": "Projects",
        "settings": "Settings",
        "userManagement": "Users"
      }
    },
    "header": {
      "notifications": {
        "empty": {
          "title": "Your notification list is empty"
        },
        "seeAll": "See all notifications"
      }
    },
    "home": {
      "achievement": {
        "action": "View Profile",
        "description": "You have completed {{progress}}% of your profile. Your current progress is great.",
        "title": "Congratulations {{name}}"
      },
      "followers": {
        "units": {
          "likes": "Likes",
          "love": "Love",
          "smiles": "Smiles"
        }
      },
      "meeting": {
        "title": "Meetings"
      },
      "targets": {
        "income": "Income",
        "followers": "Followers",
        "title": "Targets",
        "views": "Views"
      },
      "views": {
        "action": "View Dashboard",
        "unit": "Views"
      },
      "welcome": {
        "message": "This page is designed to give some important information about the application. Let's make someting together!",
        "subTitle": "Welcome back!",
        "title": "Hi {{name}},"
      }
    }
  },
  "auth": {
    "forgotPassword": {
      "form": {
        "action": "Send Confirmation",
        "back": "Back to login",
        "email": {
          "label": "E-mail Address"
        }
      },
      "notifications": {
        "success": "Email has been sent!"
      },
      "subTitle": "To get a validation code, enter the e-mail address associated to your account.",
      "title": "Get validation code"
    },
    "forgotPasswordSubmit": {
      "form": {
        "action": "Reset Password",
        "back": "Back to login",
        "code": {
          "label": "Code"
        },
        "confirmPassword": {
          "label": "Confirm Password"
        },
        "newPassword": {
          "label": "New Password"
        }
      },
      "notifications": {
        "success": "Your password has been changed!"
      },
      "subTitle": "Enter the code you received by mail and choose a new password.",
      "title": "Change your password"
    },
    "login": {
      "forgotPasswordLink": "Forgot password?",
      "form": {
        "email": {
          "label": "E-mail Address"
        },
        "password": {
          "label": "Password"
        }
      },
      "newAccountLink": "Don't have an account? Sign Up!",
      "submit": "Sign in",
      "title": "Sign in"
    },
    "register": {
      "back": "Back to login",
      "form": {
        "email": {
          "label": "E-mail Address"
        },
        "firstName": {
          "label": "First Name"
        },
        "gender": {
          "label": "Gender",
          "options": {
            "f": "F",
            "m": "M",
            "n": "NC"
          }
        },
        "lastName": {
          "label": "Last Name"
        }
      },
      "notifications": {
        "success": "Your account has been created successfully!"
      },
      "submit": "Register",
      "title": "Register"
    }
  },
  "calendar": {
    "confirmations": {
      "delete": "Are you sure you want to delete this event?"
    },
    "form": {
      "color": {
        "label": "Color"
      },
      "description": {
        "label": "Description"
      },
      "end": {
        "label": "End Date"
      },
      "start": {
        "label": "Start Date"
      },
      "title": {
        "label": "Title"
      }
    },
    "modal": {
      "add": {
        "action": "Add",
        "title": "Add Event"
      },
      "edit": {
        "action": "Edit",
        "title": "Edit Event"
      }
    },
    "notifications": {
      "addSuccess": "{{event}} has been added!",
      "deleteSuccess": "Event has been deleted!",
      "updateSuccess": "{{event}} has been updated!"
    },
    "title": "Calendar"
  },
  "common": {
    "backHome": "Back to Home",
    "cancel": "Cancel",
    "confirm": "Confirm",
    "confirmation": "Confirmation",
    "delete": "Delete",
    "edit": "Edit",
    "errors": {
      "forbidden": {
        "subTitle": "You don't have access to view this page"
      },
      "notFound": {
        "subTitle": "Sorry, we couldn’t find the page you’re looking for. Perhaps you’ve mistyped the URL? Be sure to check your spelling.",
        "title": "Oops!"
      },
      "unexpected": {
        "subTitle": "Something went wrong! If the problem persists, contact us!",
        "title": "Oops!"
      },
      "underConstructions": {
        "subTitle": "We are actively working on this page.",
        "title": "Under constructions!"
      }
    },
    "reset": "Reset",
    "retry": "Try Again",
    "selected": "Selected",
    "snackbar": {
      "error": "Error",
      "success": "Success"
    },
    "today": "Today",
    "update": "Update",
    "validations": {
      "email": "Invalid email address",
      "max": "Must be {{size}} characters or less",
      "min": "Must be {{size}} characters or more",
      "passwordMatch": "Password does not match",
      "required": "Required"
    }
  },
  "dashboard": {
    "activity": {
      "title": "Activity"
    },
    "budget": {
      "legend": {
        "unit": "Budget ($K)"
      },
      "title": "Budget"
    },
    "orderProgress": {
      "title": "Orders"
    },
    "overview": {
      "orders": "Orders",
      "sales": "Sales",
      "users": "Users",
      "visits": "Visits"
    },
    "progress": {
      "title": "Progress"
    },
    "salesByAge": {
      "title": "Sales by Age"
    },
    "salesByCategory": {
      "legend": {
        "books": "Books",
        "movies": "Movies & TV",
        "software": "Software"
      },
      "title": "Sales by Category"
    },
    "salesHistory": {
      "title": "Sales History",
      "unit": "$ today"
    },
    "salesProgress": {
      "title": "Sales"
    },
    "teams": {
      "columns": {
        "progress": "Progress",
        "team": "Team",
        "value": "Value"
      },
      "title": "Teams Progress"
    },
    "users": {
      "title": "Recent Users"
    },
    "visitProgress": {
      "title": "Visits"
    },
    "title": "Dashboard"
  },
  "faq": {
    "noAnswerLink": "Can't find it here? Check out our Help Center.",
    "questions": {
      "title1": "What is React Admin?",
      "answer1": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title2": "How does it work?",
      "answer2": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title3": "What are the features available?",
      "answer3": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title4": "Who is maintaining the project?",
      "answer4": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title5": "What is the license of the project?",
      "answer5": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title6": "How can I contribute to the project?",
      "answer6": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget."
    },
    "title": "Frequently Asked Questions"
  },
  "help": {
    "menu": {
      "contact": "Contact",
      "faq": "FAQ",
      "guide": "Guide",
      "support": "Support"
    },
    "title": "Help Center"
  },
  "landing": {
    "cta": {
      "main": "Sign in",
      "mainAuth": "Continue as {{name}}",
      "secondary": "Source code"
    },
    "features": {
      "more": "More on Github",
      "title": "Main Features"
    },
    "title": "Free and open-source admin application made with React and Material-UI"
  },
  "notifications": {
    "newComment": "<bold>{{user}}</bold> has written a new comment on your profile",
    "unreadMessages": "You have <bold>{{quantity}}</bold> unread messages"
  },
  "profile": {
    "activity": {
      "empty": "Logs of your activity will show up here!",
      "logs": {
        "eventAdded": "<bold>You</bold> added a new event: <bold>{{resource}}</bold>",
        "eventUpdated": "<bold>You</bold> updated event: <bold>{{resource}}</bold>",
        "userAdded": "<bold>You</bold> added a new user: <bold>{{resource}}</bold>",
        "userDeleted": "<bold>You</bold> deleted user: <bold>{{resource}}</bold>",
        "userUpdated": "<bold>You</bold> updated user: <bold>{{resource}}</bold>"
      }
    },
    "completion": {
      "title": "Profile Completion"
    },
    "info": {
      "form": {
        "email": {
          "label": "Email Address"
        },
        "firstName": {
          "label": "First Name"
        },
        "gender": {
          "label": "Gender",
          "options": {
            "f": "Female",
            "m": "Male",
            "n": "NC"
          }
        },
        "lastName": {
          "label": "Last Name"
        }
      },
      "title": "Update profile"
    },
    "menu": {
      "activity": "Activity",
      "info": "Information",
      "password": "Password"
    },
    "notifications": {
      "informationUpdated": "Information updated!",
      "passwordChanged": "Password changed!"
    },
    "password": {
      "form": {
        "current": {
          "label": "Current Password"
        },
        "confirm": {
          "label": "Confirm Password"
        },
        "new": {
          "label": "New Password"
        }
      },
      "title": "Change your password"
    }
  },
  "settings": {
    "drawer": {
      "direction": {
        "label": "Direction",
        "options": {
          "ltr": "LTR",
          "rtl": "RTL"
        }
      },
      "language": {
        "label": "Language",
        "options": {
          "en": "English",
          "fr": "Français"
        }
      },
      "mode": {
        "label": "Mode",
        "options": {
          "dark": "Dark",
          "light": "Light"
        }
      },
      "sidebar": {
        "label": "Sidebar",
        "options": {
          "collapsed": "Collapsed",
          "full": "Full"
        }
      },
      "title": "Settings"
    }
  },
  "userManagement": {
    "confirmations": {
      "delete": "Are you sure you want to delete this user?"
    },
    "form": {
      "disabled": {
        "label": "Disabled"
      },
      "email": {
        "label": "Email Address"
      },
      "firstName": {
        "label": "First Name"
      },
      "gender": {
        "label": "Gender",
        "options": {
          "f": "Female",
          "m": "Male",
          "n": "NC"
        }
      },
      "lastName": {
        "label": "Last Name"
      },
      "role": {
        "label": "Role"
      }
    },
    "modal": {
      "add": {
        "action": "Add",
        "title": "Add User"
      },
      "edit": {
        "action": "Edit",
        "title": "Edit User"
      }
    },
    "notifications": {
      "addSuccess": "{{user}} has been added!",
      "deleteSuccess": "User(s) have been deleted!",
      "updateSuccess": "{{user}} has been updated!"
    },
    "table": {
      "headers": {
        "actions": "Actions",
        "gender": "Gender",
        "role": "Role",
        "status": "Status",
        "user": "User"
      }
    },
    "toolbar": {
      "title": "Users"
    }
  }
}


================================================
FILE: public/locales/fr/translation.json
================================================
{
  "admin": {
    "drawer": {
      "menu": {
        "calendar": "Calendrier",
        "dashboard": "Dashboard",
        "help": "Aide",
        "home": "Home",
        "projects": "Projets",
        "settings": "Paramètres",
        "userManagement": "Utilisateurs"
      }
    },
    "header": {
      "notifications": {
        "empty": {
          "title": "Votre liste de notifications est vide"
        },
        "seeAll": "Voir toutes les notifications"
      }
    },
    "home": {
      "achievement": {
        "action": "Voir mon profil",
        "description": "Vous avez complété {{progress}}% de votre profile. Vos progrès sont fantastiques.",
        "title": "Félicitations {{name}}"
      },
      "followers": {
        "units": {
          "likes": "Likes",
          "love": "Love",
          "smiles": "Smiles"
        }
      },
      "meeting": {
        "title": "Réunions"
      },
      "targets": {
        "income": "Revenus",
        "followers": "Followers",
        "title": "Objectifs",
        "views": "Vues"
      },
      "views": {
        "action": "Voir le dashboard",
        "unit": "Vues"
      },
      "welcome": {
        "message": "Cette page a été conçue pour afficher les informations importantes de l'application",
        "subTitle": "Heureux de vous revoir!",
        "title": "Bonjour {{name}},"
      }
    }
  },
  "auth": {
    "forgotPassword": {
      "form": {
        "action": "Envoyer le code",
        "back": "Retourner sur la page de login",
        "email": {
          "label": "Adresse E-mail"
        }
      },
      "notifications": {
        "success": "Un e-mail a été envoyé!"
      },
      "subTitle": "Pour obtenir un code de validation, entrez d'abord l'adresse e-mail que vous avez ajoutée à votre compte.",
      "title": "Obtenir un code de validation"
    },
    "forgotPasswordSubmit": {
      "form": {
        "action": "Réinitialiser le mot de passe",
        "back": "Retourner sur la page de login",
        "code": {
          "label": "Code"
        },
        "confirmPassword": {
          "label": "Confirmer le mot de passe"
        },
        "newPassword": {
          "label": "Nouveau mot de passe"
        }
      },
      "notifications": {
        "success": "Votre mot de passe a été modifié!"
      },
      "subTitle": "Entrez le code reçu via e-mail et choisissez votre nouveau mot de passe.",
      "title": "Changer le mot de passe"
    },
    "login": {
      "forgotPasswordLink": "Mot de passe oublié?",
      "form": {
        "email": {
          "label": "Adresse E-mail"
        },
        "password": {
          "label": "Mot de passe"
        }
      },
      "newAccountLink": "Vous n'avez pas encore de compte? Enregistrez-vous!",
      "submit": "Se connecter",
      "title": "Se connecter"
    },
    "register": {
      "back": "Retourner sur la page de login",
      "form": {
        "email": {
          "label": "Adresse E-mail"
        },
        "firstName": {
          "label": "Prénom"
        },
        "gender": {
          "label": "Genre",
          "options": {
            "f": "F",
            "m": "M",
            "n": "NC"
          }
        },
        "lastName": {
          "label": "Nom"
        }
      },
      "notifications": {
        "success": "Votre compte a été créé avec succès!"
      },
      "submit": "S'enregistrer",
      "title": "S'enregistrer"
    }
  },
  "calendar": {
    "confirmations": {
      "delete": "Etes-vous certain de vouloir supprimer cet évènement?"
    },
    "form": {
      "color": {
        "label": "Couleur"
      },
      "description": {
        "label": "Description"
      },
      "end": {
        "label": "Date de fin"
      },
      "start": {
        "label": "Date de début"
      },
      "title": {
        "label": "Titre"
      }
    },
    "modal": {
      "add": {
        "action": "Ajouter",
        "title": "Ajouter un évènement"
      },
      "edit": {
        "action": "Modifier",
        "title": "Modifier l'évènement"
      }
    },
    "notifications": {
      "addSuccess": "{{event}} a été ajouté!",
      "deleteSuccess": "L'évènement a été supprimé!",
      "updateSuccess": "{{event}} a été modifié!"
    },
    "title": "Calendrier"
  },
  "common": {
    "backHome": "Retourner sur la page d'accueil",
    "cancel": "Annuler",
    "confirm": "Confirmer",
    "confirmation": "Confirmation",
    "delete": "Supprimer",
    "edit": "Editer",
    "errors": {
      "forbidden": {
        "subTitle": "Vous n'avez pas les accès suffisant pour accéder à cette page"
      },
      "notFound": {
        "subTitle": "Désolé, nous n'avons pas pu trouver la page que vous cherchez. L'URL peut être incorrect.",
        "title": "Oups!"
      },
      "unexpected": {
        "subTitle": "Une erreur s'est produite! Si le problème persiste, contactez-nous!",
        "title": "Oups!"
      },
      "underConstructions": {
        "subTitle": "Nous travaillons activement sur cette page",
        "title": "Chantier en cours!"
      }
    },
    "reset": "Reset",
    "retry": "Réessayer",
    "selected": "sélectionné(s)",
    "snackbar": {
      "error": "Erreur",
      "success": "Succès"
    },
    "today": "Aujourd'hui",
    "update": "Modifier",
    "validations": {
      "email": "Adresse e-mail invalide",
      "max": "Maximum {{size}} caractères autorisés",
      "min": "Minimum {{size}} caractères autorisés",
      "passwordMatch": "Les mots de passe ne correspondent pas",
      "required": "Requis"
    }
  },
  "dashboard": {
    "activity": {
      "title": "Activité"
    },
    "budget": {
      "legend": {
        "unit": "Budget ($K)"
      },
      "title": "Budget"
    },
    "orderProgress": {
      "title": "Commandes"
    },
    "overview": {
      "orders": "Commandes",
      "sales": "Ventes",
      "users": "Utilisateurs",
      "visits": "Visites"
    },
    "progress": {
      "title": "Progrès"
    },
    "salesByAge": {
      "title": "Ventes par tranche d'âge"
    },
    "salesByCategory": {
      "legend": {
        "books": "Livres",
        "movies": "Films et TV",
        "software": "Software"
      },
      "title": "Ventes par catégorie"
    },
    "salesHistory": {
      "title": "Historique des ventes",
      "unit": "$ aujourd'hui"
    },
    "salesProgress": {
      "title": "Ventes"
    },
    "teams": {
      "columns": {
        "progress": "Progrès",
        "team": "Equipe",
        "value": "Valeur"
      },
      "title": "Progrès des équipes"
    },
    "users": {
      "title": "Utilisateurs récents"
    },
    "visitProgress": {
      "title": "Visites"
    },
    "title": "Dashboard"
  },
  "faq": {
    "noAnswerLink": "Vous ne trouvez pas la réponse à votre question? Retournez à la page d'aide.",
    "questions": {
      "title1": "Qu'est ce que React Admin?",
      "answer1": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title2": "Comment fonctionne-t-il?",
      "answer2": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title3": "Quelles sont les fonctionnalités disponibles?",
      "answer3": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title4": "Qui maintient le projet?",
      "answer4": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title5": "Quelle est la licence utilisée?",
      "answer5": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget.",
      "title6": "Comment puis-je contribuer au projet?",
      "answer6": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, sit amet blandit leo lobortiseget."
    },
    "title": "Questions Fréquemment Posées"
  },
  "help": {
    "menu": {
      "contact": "Contact",
      "faq": "FAQ",
      "guide": "Guide",
      "support": "Support"
    },
    "title": "Centre d'aide"
  },
  "landing": {
    "cta": {
      "main": "Se connecter",
      "mainAuth": "Continuer en tant que {{name}}",
      "secondary": "Code source"
    },
    "features": {
      "more": "Plus sur Github",
      "title": "Principales fonctionnalités"
    },
    "title": "Open-source dashboard application fait avec React"
  },
  "notifications": {
    "newComment": "<bold>{{user}}</bold> a écrit un commentaire sur votre profil",
    "unreadMessages": "Vous avez <bold>{{quantity}}</bold> messages non-lus"
  },
  "profile": {
    "activity": {
      "empty": "Les logs de votre activité seront affichés ici!",
      "logs": {
        "eventAdded": "<bold>Vous</bold> avez ajouté un nouvel évènement: <bold>{{resource}}</bold>",
        "eventUpdated": "<bold>Vous</bold> avez modifié l'évènement: <bold>{{resource}}</bold>",
        "userAdded": "<bold>Vous</bold> avez ajouté un nouvel utilisateur: <bold>{{resource}}</bold>",
        "userDeleted": "<bold>Vous</bold> avez supprimé l'utilisateur: <bold>{{resource}}</bold>",
        "userUpdated": "<bold>Vous</bold> avez modifié l'utilisateur: <bold>{{resource}}</bold>"
      }
    },
    "completion": {
      "title": "Avancée du profil"
    },
    "info": {
      "form": {
        "email": {
          "label": "Adresse Email"
        },
        "firstName": {
          "label": "Prénom"
        },
        "gender": {
          "label": "Genre",
          "options": {
            "f": "Femme",
            "m": "Homme",
            "n": "NC"
          }
        },
        "lastName": {
          "label": "Nom"
        }
      },
      "title": "Modifier le profil"
    },
    "menu": {
      "activity": "Activité",
      "info": "Information",
      "password": "Mot de passe"
    },
    "notifications": {
      "informationUpdated": "Information modifiée!",
      "passwordChanged": "Mot de passe modifié!"
    },
    "password": {
      "form": {
        "current": {
          "label": "Mot de passe actuel"
        },
        "confirm": {
          "label": "Confirmer le mot de passe"
        },
        "new": {
          "label": "Nouveau mot de passe"
        }
      },
      "title": "Changer votre mot de passe"
    }
  },
  "settings": {
    "drawer": {
      "direction": {
        "label": "Direction",
        "options": {
          "ltr": "LTR",
          "rtl": "RTL"
        }
      },
      "language": {
        "label": "Langage",
        "options": {
          "en": "English",
          "fr": "Français"
        }
      },
      "mode": {
        "label": "Mode",
        "options": {
          "dark": "Foncé",
          "light": "Clair"
        }
      },
      "sidebar": {
        "label": "Barre latérale",
        "options": {
          "collapsed": "Réduite",
          "full": "Complète"
        }
      },
      "title": "Paramètres"
    }
  },
  "userManagement": {
    "confirmations": {
      "delete": "Etes-vous certain de vouloir supprimer cet utilisateur?"
    },
    "form": {
      "disabled": {
        "label": "Désactivé"
      },
      "email": {
        "label": "Addresse E-mail"
      },
      "firstName": {
        "label": "Prénom"
      },
      "gender": {
        "label": "Genre",
        "options": {
          "f": "Femme",
          "m": "Homme",
          "n": "NC"
        }
      },
      "lastName": {
        "label": "Nom"
      },
      "role": {
        "label": "Rôle"
      }
    },
    "modal": {
      "add": {
        "action": "Ajouter",
        "title": "Ajouter un utilisateur"
      },
      "edit": {
        "action": "Modifier",
        "title": "Modifier l'utilisateur"
      }
    },
    "notifications": {
      "addSuccess": "{{user}} a été ajouté!",
      "deleteSuccess": "L(es) utilisateur(s) ont été supprimés!",
      "updateSuccess": "{{user}} a été modifié!"
    },
    "table": {
      "headers": {
        "actions": "Actions",
        "gender": "Genre",
        "role": "Rôle",
        "status": "Statut",
        "user": "Utilisateur"
      }
    },
    "toolbar": {
      "title": "Utilisateurs"
    }
  }
}


================================================
FILE: public/manifest.json
================================================
{
  "short_name": "React Admin",
  "name": "React Material Admin",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}


================================================
FILE: public/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:


================================================
FILE: src/App.test.tsx
================================================
import { render, screen } from "@testing-library/react";
import React from "react";
import App from "./App";

test("renders learn react link", () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});


================================================
FILE: src/App.tsx
================================================
import * as Sentry from "@sentry/react";
import React from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import AppRoutes from "./AppRoutes";
import AuthProvider from "./auth/contexts/AuthProvider";
import Loader from "./core/components/Loader";
import QueryWrapper from "./core/components/QueryWrapper";
import SettingsProvider from "./core/contexts/SettingsProvider";
import SnackbarProvider from "./core/contexts/SnackbarProvider";
import usePageTracking from "./core/hooks/usePageTracking";

if (process.env.NODE_ENV === "production") {
  Sentry.init({
    dsn: process.env.REACT_APP_SENTRY_DSN,
  });
}

// Create a client
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: 0,
      suspense: true,
    },
  },
});

function App() {
  usePageTracking();

  return (
    <React.Suspense fallback={<Loader />}>
      <Sentry.ErrorBoundary fallback={"An error has occurred"}>
        <QueryClientProvider client={queryClient}>
          <SettingsProvider>
            <QueryWrapper>
              <SnackbarProvider>
                <AuthProvider>
                  <AppRoutes />
                </AuthProvider>
              </SnackbarProvider>
            </QueryWrapper>
          </SettingsProvider>
          <ReactQueryDevtools initialIsOpen />
        </QueryClientProvider>
      </Sentry.ErrorBoundary>
    </React.Suspense>
  );
}

export default App;


================================================
FILE: src/AppRoutes.tsx
================================================
import { lazy } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import PrivateRoute from "./core/components/PrivateRoute";

// Admin
const Admin = lazy(() => import("./admin/pages/Admin"));
const Dashboard = lazy(() => import("./admin/pages/Dashboard"));
const Faq = lazy(() => import("./admin/pages/Faq"));
const HelpCenter = lazy(() => import("./admin/pages/HelpCenter"));
const Home = lazy(() => import("./admin/pages/Home"));
const Profile = lazy(() => import("./admin/pages/Profile"));
const ProfileActivity = lazy(() => import("./admin/pages/ProfileActivity"));
const ProfileInformation = lazy(
  () => import("./admin/pages/ProfileInformation")
);
const ProfilePassword = lazy(() => import("./admin/pages/ProfilePassword"));

// Auth
const ForgotPassword = lazy(() => import("./auth/pages/ForgotPassword"));
const ForgotPasswordSubmit = lazy(
  () => import("./auth/pages/ForgotPasswordSubmit")
);
const Login = lazy(() => import("./auth/pages/Login"));
const Register = lazy(() => import("./auth/pages/Register"));

// Calendar
const CalendarApp = lazy(() => import("./calendar/pages/CalendarApp"));

// Core
const Forbidden = lazy(() => import("./core/pages/Forbidden"));
const NotFound = lazy(() => import("./core/pages/NotFound"));
const UnderConstructions = lazy(
  () => import("./core/pages/UnderConstructions")
);

// Landing
const Landing = lazy(() => import("./landing/pages/Landing"));

// Users
const UserManagement = lazy(() => import("./users/pages/UserManagement"));

const AppRoutes = () => {
  return (
    <Routes basename={process.env.PUBLIC_URL}>
      <Route path="/" element={<Landing />} />
      <PrivateRoute path="admin" element={<Admin />}>
        <PrivateRoute path="/" element={<Home />} />
        <PrivateRoute path="calendar" element={<CalendarApp />} />
        <PrivateRoute path="dashboard" element={<Dashboard />} />
        <PrivateRoute path="faq" element={<Faq />} />
        <PrivateRoute path="help" element={<HelpCenter />} />
        <PrivateRoute path="profile" element={<Profile />}>
          <PrivateRoute path="/" element={<ProfileActivity />} />
          <PrivateRoute path="information" element={<ProfileInformation />} />
          <PrivateRoute path="password" element={<ProfilePassword />} />
        </PrivateRoute>
        <PrivateRoute
          path="projects"
          element={
            <Navigate
              to={`/${process.env.PUBLIC_URL}/under-construction`}
              replace
            />
          }
        />
        <PrivateRoute path="user-management" element={<UserManagement />} />
      </PrivateRoute>
      <Route path="forgot-password" element={<ForgotPassword />} />
      <Route path="forgot-password-submit" element={<ForgotPasswordSubmit />} />
      <Route path="login" element={<Login />} />
      <Route path="register" element={<Register />} />
      <Route path="under-construction" element={<UnderConstructions />} />
      <Route path="403" element={<Forbidden />} />
      <Route path="404" element={<NotFound />} />
      <Route
        path="*"
        element={<Navigate to={`/${process.env.PUBLIC_URL}/404`} replace />}
      />
    </Routes>
  );
};

export default AppRoutes;


================================================
FILE: src/admin/components/AdminAppBar.tsx
================================================
import AppBar from "@material-ui/core/AppBar";
import { drawerCollapsedWidth, drawerWidth } from "../../core/config/layout";
import { useSettings } from "../../core/contexts/SettingsProvider";

type AdminAppBarProps = {
  children: React.ReactNode;
};

const AdminAppBar = ({ children }: AdminAppBarProps) => {
  const { collapsed } = useSettings();
  const width = collapsed ? drawerCollapsedWidth : drawerWidth;

  return (
    <AppBar
      color="default"
      position="fixed"
      sx={{
        width: { lg: `calc(100% - ${width}px)` },
        marginLeft: { lg: width },
      }}
    >
      {children}
    </AppBar>
  );
};

export default AdminAppBar;


================================================
FILE: src/admin/components/AdminDrawer.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Box from "@material-ui/core/Box";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
import ListItemText from "@material-ui/core/ListItemText";
import AccountTreeIcon from "@material-ui/icons/AccountTree";
import BarChartIcon from "@material-ui/icons/BarChart";
import EventIcon from "@material-ui/icons/Event";
import HelpCenterIcon from "@material-ui/icons/HelpCenter";
import HomeIcon from "@material-ui/icons/Home";
import PeopleIcon from "@material-ui/icons/People";
import PersonIcon from "@material-ui/icons/Person";
import SettingsIcon from "@material-ui/icons/Settings";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import { useAuth } from "../../auth/contexts/AuthProvider";
import Logo from "../../core/components/Logo";
import { drawerCollapsedWidth, drawerWidth } from "../../core/config/layout";

type AdminDrawerProps = {
  collapsed: boolean;
  mobileOpen: boolean;
  onDrawerToggle: () => void;
  onSettingsToggle: () => void;
};

export const menuItems = [
  {
    icon: HomeIcon,
    key: "admin.drawer.menu.home",
    path: "/admin",
  },
  {
    icon: BarChartIcon,
    key: "admin.drawer.menu.dashboard",
    path: "/admin/dashboard",
  },
  {
    icon: PeopleIcon,
    key: "admin.drawer.menu.userManagement",
    path: "/admin/user-management",
  },
  {
    icon: EventIcon,
    key: "admin.drawer.menu.calendar",
    path: "/admin/calendar",
  },
  {
    icon: AccountTreeIcon,
    key: "admin.drawer.menu.projects",
    path: "/admin/projects",
  },
  {
    icon: HelpCenterIcon,
    key: "admin.drawer.menu.help",
    path: "/admin/help",
  },
];

const AdminDrawer = ({
  collapsed,
  mobileOpen,
  onDrawerToggle,
  onSettingsToggle,
}: AdminDrawerProps) => {
  const { userInfo } = useAuth();
  const { t } = useTranslation();

  const width = collapsed ? drawerCollapsedWidth : drawerWidth;

  const drawer = (
    <Box sx={{ display: "flex", flexDirection: "column", minHeight: "100%" }}>
      <Logo sx={{ display: "flex", p: 4 }} />
      <List component="nav" sx={{ px: 2 }}>
        {menuItems.map((item) => (
          <ListItem
            button
            component={NavLink}
            key={item.path}
            activeClassName="Mui-selected"
            end={true}
            to={`/${process.env.PUBLIC_URL}${item.path}`}
          >
            <ListItemAvatar>
              <Avatar sx={{ color: "inherit", bgcolor: "transparent" }}>
                <item.icon />
              </Avatar>
            </ListItemAvatar>
            <ListItemText
              primary={t(item.key)}
              sx={{
                display: collapsed ? "none" : "block",
              }}
            />
          </ListItem>
        ))}
      </List>
      <Box sx={{ flexGrow: 1 }} />
      <List component="nav" sx={{ p: 2 }}>
        <ListItem
          button
          component={NavLink}
          to={`/${process.env.PUBLIC_URL}/admin/profile`}
        >
          <ListItemAvatar>
            <Avatar>
              <PersonIcon />
            </Avatar>
          </ListItemAvatar>
          {userInfo && (
            <ListItemText
              primary={`${userInfo.firstName} ${userInfo.lastName}`}
              sx={{
                display: collapsed ? "none" : "block",
              }}
            />
          )}
        </ListItem>
        <ListItem button onClick={onSettingsToggle}>
          <ListItemAvatar>
            <Avatar>
              <SettingsIcon />
            </Avatar>
          </ListItemAvatar>
          <ListItemText
            primary={t("admin.drawer.menu.settings")}
            sx={{
              display: collapsed ? "none" : "block",
            }}
          />
        </ListItem>
      </List>
    </Box>
  );

  return (
    <Box
      aria-label="Admin drawer"
      component="nav"
      sx={{
        width: { lg: width },
        flexShrink: { lg: 0 },
      }}
    >
      {/* The implementation can be swapped with js to avoid SEO duplication of links. */}
      <Drawer
        variant="temporary"
        open={mobileOpen}
        onClose={onDrawerToggle}
        ModalProps={{
          keepMounted: true, // Better open performance on mobile.
        }}
        sx={{
          display: { xs: "block", lg: "none" },
          "& .MuiDrawer-paper": {
            boxSizing: "border-box",
            width: width,
          },
        }}
      >
        {drawer}
      </Drawer>
      <Drawer
        variant="permanent"
        open
        sx={{
          display: { xs: "none", lg: "block" },
          "& .MuiDrawer-paper": {
            boxSizing: "border-box",
            width: width,
          },
        }}
      >
        {drawer}
      </Drawer>
    </Box>
  );
};

export default AdminDrawer;


================================================
FILE: src/admin/components/AdminToolbar.tsx
================================================
import IconButton from "@material-ui/core/IconButton";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import MenuIcon from "@material-ui/icons/Menu";
import { useSettings } from "../../core/contexts/SettingsProvider";

type AdminToolbarProps = {
  children?: React.ReactNode;
  title?: string;
};

const AdminToolbar = ({ children, title }: AdminToolbarProps) => {
  const { toggleDrawer } = useSettings();

  return (
    <Toolbar sx={{ px: { xs: 3, sm: 6 } }}>
      <IconButton
        color="inherit"
        aria-label="open drawer"
        edge="start"
        onClick={toggleDrawer}
        sx={{
          display: { lg: "none" },
          marginRight: 2,
        }}
      >
        <MenuIcon />
      </IconButton>
      <Typography variant="h2" component="h1" sx={{ flexGrow: 1 }}>
        {title}
      </Typography>
      {children}
    </Toolbar>
  );
};

export default AdminToolbar;


================================================
FILE: src/admin/components/RecentNotifications.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Badge from "@material-ui/core/Badge";
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
import ListItemText from "@material-ui/core/ListItemText";
import Popover from "@material-ui/core/Popover";
import NotificationsIcon from "@material-ui/icons/Notifications";
import PersonIcon from "@material-ui/icons/Person";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useMemo, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import Empty from "../../core/components/Empty";
import Loader from "../../core/components/Loader";
import Result from "../../core/components/Result";
import { useDateLocale } from "../../core/hooks/useDateLocale";
import { notificationKeys } from "../config/notification";
import { useNotifications } from "../hooks/useNotifications";

const RecentNotifications = () => {
  const locale = useDateLocale();
  const { t } = useTranslation();

  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

  const { data, isError, isLoading } = useNotifications();

  const open = Boolean(anchorEl);

  const unreadCount = useMemo(
    () => data && data.filter((notification) => notification.unread).length,
    [data]
  );

  const handleClick = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <Box>
      <IconButton
        id="notifications-button"
        aria-controls="notifications-popover"
        aria-haspopup="true"
        aria-expanded={open ? "true" : "false"}
        aria-label="show recent notifications"
        color="inherit"
        onClick={handleClick}
      >
        <Badge color="error" variant="dot" invisible={!unreadCount}>
          <NotificationsIcon />
        </Badge>
      </IconButton>
      <Popover
        id="notifications-popover"
        open={open}
        anchorEl={anchorEl}
        onClose={handleClose}
        anchorOrigin={{
          vertical: "bottom",
          horizontal: "right",
        }}
        transformOrigin={{
          vertical: "top",
          horizontal: "right",
        }}
      >
        <Box sx={{ width: 360 }}>
          {!isLoading && !isError && data && data.length > 0 && (
            <List
              component="nav"
              aria-label="notifications popover"
              sx={{ px: 2 }}
            >
              {data.map((notification) => (
                <ListItem
                  button
                  component={NavLink}
                  key={notification.id}
                  to={""}
                >
                  <ListItemAvatar>
                    <Avatar>
                      <PersonIcon />
                    </Avatar>
                  </ListItemAvatar>
                  <ListItemText
                    primary={
                      <Trans
                        components={{ bold: <strong /> }}
                        defaults="<bold>{{ user }}</bold> did someting <bold>{{ quantity }}</bold> times"
                        i18nKey={notificationKeys[notification.code]}
                        values={notification.params}
                      />
                    }
                    secondary={formatDistanceToNow(
                      new Date(notification.createdAt),
                      { addSuffix: true, locale }
                    )}
                  />
                </ListItem>
              ))}
            </List>
          )}
          {!isLoading && !isError && (!data || data.length === 0) && (
            <Empty title={t("admin.header.notifications.empty.title")} />
          )}
          {isError && (
            <Result
              status="error"
              subTitle={t("common.errors.unexpected.subTitle")}
              title={t("common.errors.unexpected.title")}
            />
          )}
          {isLoading && <Loader />}
          <Box sx={{ px: 2, pb: 2 }}>
            <Button
              color="secondary"
              fullWidth
              sx={{ bgcolor: "background.default" }}
              variant="contained"
            >
              {t("admin.header.notifications.seeAll")}
            </Button>
          </Box>
        </Box>
      </Popover>
    </Box>
  );
};

export default RecentNotifications;


================================================
FILE: src/admin/config/activity.ts
================================================
export const logKeys: { [key: string]: string } = {
  eventAdded: "profile.activity.logs.eventAdded",
  eventUpdated: "profile.activity.logs.eventUpdated",
  userAdded: "profile.activity.logs.eventAdded",
  userDeleted: "profile.activity.logs.userDeleted",
  userUpdated: "profile.activity.logs.userUpdated",
};


================================================
FILE: src/admin/config/notification.ts
================================================
export const notificationKeys: { [key: string]: string } = {
  newComment: "notifications.newComment",
  unreadMessages: "notifications.unreadMessages",
};


================================================
FILE: src/admin/hooks/useActivityLogs.ts
================================================
import axios from "axios";
import { useQuery } from "react-query";
import { ActivityLog } from "../types/activityLog";

const fetchActivityLogs = async (): Promise<ActivityLog[]> => {
  const { data } = await axios.get("/api/activity-logs");
  return data;
};

export function useActivityLogs() {
  return useQuery("activity-logs", () => fetchActivityLogs());
}


================================================
FILE: src/admin/hooks/useNotifications.ts
================================================
import axios from "axios";
import { useQuery } from "react-query";
import { Notification } from "../types/notification";

const fetchNotifications = async (): Promise<Notification[]> => {
  const { data } = await axios.get("/api/notifications");
  return data;
};

export function useNotifications() {
  return useQuery("notifications", () => fetchNotifications(), {
    suspense: false,
  });
}


================================================
FILE: src/admin/hooks/useProfileInfo.ts
================================================
import axios from "axios";
import { useQuery } from "react-query";
import { ProfileInfo } from "../types/profileInfo";

const fetchProfileInfo = async (): Promise<ProfileInfo> => {
  const { data } = await axios.get("/api/profile-info");
  return data;
};

export function useProfileInfo() {
  return useQuery("profile-info", () => fetchProfileInfo());
}


================================================
FILE: src/admin/hooks/useUpdateProfileInfo.ts
================================================
import axios from "axios";
import { useMutation, useQueryClient } from "react-query";
import { ProfileInfo } from "../types/profileInfo";

const updateProfileInfo = async (
  profileInfo: ProfileInfo
): Promise<ProfileInfo> => {
  const { data } = await axios.put("/api/profile-info", profileInfo);
  return data;
};

export function useUpdateProfileInfo() {
  const queryClient = useQueryClient();

  const { isLoading, mutateAsync } = useMutation(updateProfileInfo, {
    onSuccess: (profileInfo: ProfileInfo) => {
      queryClient.setQueryData(["profile-info"], profileInfo);
    },
  });

  return { isUpdating: isLoading, updateProfileInfo: mutateAsync };
}


================================================
FILE: src/admin/pages/Admin.tsx
================================================
import Box from "@material-ui/core/Box";
import Toolbar from "@material-ui/core/Toolbar";
import { useState } from "react";
import { Outlet } from "react-router-dom";
import QueryWrapper from "../../core/components/QueryWrapper";
import SettingsDrawer from "../../core/components/SettingsDrawer";
import { useSettings } from "../../core/contexts/SettingsProvider";
import AdminDrawer from "../components/AdminDrawer";

const AdminLayout = () => {
  const [settingsOpen, setSettingsOpen] = useState(false);

  const { collapsed, open, toggleDrawer } = useSettings();

  const handleSettingsToggle = () => {
    setSettingsOpen(!settingsOpen);
  };

  return (
    <Box sx={{ display: "flex" }}>
      <AdminDrawer
        collapsed={collapsed}
        mobileOpen={open}
        onDrawerToggle={toggleDrawer}
        onSettingsToggle={handleSettingsToggle}
      />
      <SettingsDrawer
        onDrawerToggle={handleSettingsToggle}
        open={settingsOpen}
      />
      <Box component="main" sx={{ flexGrow: 1, pb: 3, px: { xs: 3, sm: 6 } }}>
        <Toolbar />
        <QueryWrapper>
          <Outlet />
        </QueryWrapper>
      </Box>
    </Box>
  );
};

export default AdminLayout;


================================================
FILE: src/admin/pages/Dashboard.tsx
================================================
import Grid from "@material-ui/core/Grid";
import AttachMoneyIcon from "@material-ui/icons/AttachMoney";
import ShoppingBasketIcon from "@material-ui/icons/ShoppingBasket";
import SupervisorAccountIcon from "@material-ui/icons/SupervisorAccount";
import React from "react";
import { useTranslation } from "react-i18next";
import AdminAppBar from "../components/AdminAppBar";
import AdminToolbar from "../components/AdminToolbar";
import ActivityWidget from "../widgets/ActivityWidget";
import BudgetWidget from "../widgets/BudgetWidget";
import CircleProgressWidget from "../widgets/CircleProgressWidget";
import OverviewWidget from "../widgets/OverviewWidget";
import ProgressWidget from "../widgets/ProgressWidget";
import SalesByAgeWidget from "../widgets/SalesByAgeWidget";
import SalesByCategoryWidget from "../widgets/SalesByCategoryWidget";
import SalesHistoryWidget from "../widgets/SalesHistoryWidget";
import TeamProgressWidget from "../widgets/TeamProgressWidget";
import UsersWidget from "../widgets/UsersWidget";

const overviewItems = [
  {
    unit: "dashboard.overview.visits",
    value: "20 700",
  },
  {
    unit: "dashboard.overview.sales",
    value: "$ 1 550",
  },
  {
    unit: "dashboard.overview.orders",
    value: "149",
  },
  {
    unit: "dashboard.overview.users",
    value: "657",
  },
];

const Dashboard = () => {
  const { t } = useTranslation();

  return (
    <React.Fragment>
      <AdminAppBar>
        <AdminToolbar title={t("dashboard.title")} />
      </AdminAppBar>
      <Grid container spacing={2}>
        {overviewItems.map((item, index) => (
          <Grid key={index} item xs={6} md={3}>
            <OverviewWidget description={t(item.unit)} title={item.value} />
          </Grid>
        ))}
        <Grid item xs={12} md={8}>
          <ActivityWidget />
        </Grid>
        <Grid item xs={12} md={4}>
          <BudgetWidget />
        </Grid>
        <Grid item xs={12} md={4}>
          <SalesHistoryWidget value={567} />
        </Grid>
        <Grid item xs={12} md={4}>
          <ProgressWidget
            avatar={<SupervisorAccountIcon />}
            mb={2}
            title={t("dashboard.visitProgress.title")}
            value={75}
          />
          <ProgressWidget
            avatar={<ShoppingBasketIcon />}
            mb={2}
            title={t("dashboard.orderProgress.title")}
            value={50}
          />
          <ProgressWidget
            avatar={<AttachMoneyIcon />}
            title={t("dashboard.salesProgress.title")}
            value={25}
          />
        </Grid>
        <Grid item xs={12} md={4}>
          <CircleProgressWidget
            height={204}
            title={t("dashboard.progress.title")}
            value={75}
          />
        </Grid>
        <Grid item xs={12} md={4}>
          <UsersWidget />
        </Grid>
        <Grid item xs={12} md={8}>
          <TeamProgressWidget />
        </Grid>
        <Grid item xs={12} md={4}>
          <SalesByCategoryWidget />
        </Grid>
        <Grid item xs={12} md={8}>
          <SalesByAgeWidget />
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

export default Dashboard;


================================================
FILE: src/admin/pages/Faq.tsx
================================================
import Accordion from "@material-ui/core/Accordion";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import Container from "@material-ui/core/Container";
import Link from "@material-ui/core/Link";
import Typography from "@material-ui/core/Typography";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import AdminAppBar from "../components/AdminAppBar";
import AdminToolbar from "../components/AdminToolbar";

const questions = [
  {
    title: "faq.questions.title1",
    answer: "faq.questions.answer1",
  },
  {
    title: "faq.questions.title2",
    answer: "faq.questions.answer2",
  },
  {
    title: "faq.questions.title3",
    answer: "faq.questions.answer3",
  },
  {
    title: "faq.questions.title4",
    answer: "faq.questions.answer4",
  },
  {
    title: "faq.questions.title5",
    answer: "faq.questions.answer5",
  },
  {
    title: "faq.questions.title6",
    answer: "faq.questions.answer6",
  },
];

const Faq = () => {
  const { t } = useTranslation();

  return (
    <React.Fragment>
      <AdminAppBar>
        <AdminToolbar />
      </AdminAppBar>
      <Container maxWidth="sm">
        <Typography align="center" marginBottom={6} variant="h2">
          {t("faq.title")}
        </Typography>
        {questions.map((question, index) => (
          <Accordion key={index}>
            <AccordionSummary expandIcon={<ExpandMoreIcon />}>
              <Typography component="p" variant="h6">
                {t(question.title)}
              </Typography>
            </AccordionSummary>
            <AccordionDetails>
              <Typography color="text.secondary">
                {t(question.answer)}
              </Typography>
            </AccordionDetails>
          </Accordion>
        ))}
        <Link
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/admin/help`}
          variant="body2"
        >
          {t("faq.noAnswerLink")}
        </Link>
      </Container>
    </React.Fragment>
  );
};

export default Faq;


================================================
FILE: src/admin/pages/HelpCenter.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Badge from "@material-ui/core/Badge";
import Card from "@material-ui/core/Card";
import CardActionArea from "@material-ui/core/CardActionArea";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import Container from "@material-ui/core/Container";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import HelpIcon from "@material-ui/icons/Help";
import MailIcon from "@material-ui/icons/Mail";
import SchoolIcon from "@material-ui/icons/School";
import SupportIcon from "@material-ui/icons/Support";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import { ReactComponent as HelpSvg } from "../../core/assets/help.svg";
import SvgContainer from "../../core/components/SvgContainer";
import AdminAppBar from "../components/AdminAppBar";
import AdminToolbar from "../components/AdminToolbar";

const HelpCenter = () => {
  const { t } = useTranslation();

  return (
    <React.Fragment>
      <AdminAppBar>
        <AdminToolbar title={t("help.title")} />
      </AdminAppBar>
      <Container maxWidth="xs" sx={{ mt: 3 }}>
        <SvgContainer>
          <HelpSvg />
        </SvgContainer>
      </Container>
      <Grid container spacing={2} sx={{ mt: 3 }}>
        <Grid item xs={6} lg={3}>
          <Card>
            <CardActionArea disabled={true}>
              <CardHeader
                avatar={
                  <Avatar aria-label="Guides icon">
                    <SchoolIcon />
                  </Avatar>
                }
              />
              <CardContent>
                <Badge
                  badgeContent="Coming soon"
                  color="primary"
                  sx={{
                    "& .MuiBadge-badge": {
                      top: -8,
                      right: 10,
                      whiteSpace: "nowrap",
                    },
                  }}
                >
                  <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                    {t("help.menu.guide")}
                  </Typography>
                </Badge>
              </CardContent>
            </CardActionArea>
          </Card>
        </Grid>
        <Grid item xs={6} lg={3}>
          <Card>
            <CardActionArea
              component={RouterLink}
              to={`/${process.env.PUBLIC_URL}/admin/faq`}
            >
              <CardHeader
                avatar={
                  <Avatar aria-label="FAQ icon">
                    <HelpIcon />
                  </Avatar>
                }
              />
              <CardContent>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                  {t("help.menu.faq")}
                </Typography>
              </CardContent>
            </CardActionArea>
          </Card>
        </Grid>
        <Grid item xs={6} lg={3}>
          <Card>
            <CardActionArea
              component="a"
              href={process.env.REACT_APP_SUPPORT_LINK}
              rel="noopener noreferrer"
              target="_blank"
            >
              <CardHeader
                avatar={
                  <Avatar aria-label="Support icon">
                    <SupportIcon />
                  </Avatar>
                }
              />
              <CardContent>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                  {t("help.menu.support")}
                </Typography>
              </CardContent>
            </CardActionArea>
          </Card>
        </Grid>
        <Grid item xs={6} lg={3}>
          <Card>
            <CardActionArea
              component="a"
              href={`mailto:${process.env.REACT_APP_CONTACT_MAIL}`}
            >
              <CardHeader
                avatar={
                  <Avatar aria-label="Mail icon">
                    <MailIcon />
                  </Avatar>
                }
              />
              <CardContent>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                  {t("help.menu.contact")}
                </Typography>
              </CardContent>
            </CardActionArea>
          </Card>
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

export default HelpCenter;


================================================
FILE: src/admin/pages/Home.tsx
================================================
import Grid from "@material-ui/core/Grid";
import React from "react";
import AdminAppBar from "../components/AdminAppBar";
import AdminToolbar from "../components/AdminToolbar";
import RecentNotifications from "../components/RecentNotifications";
import AchievementWidget from "../widgets/AchievementWidget";
import FollowersWidget from "../widgets/FollowersWidget";
import MeetingWidgets from "../widgets/MeetingWidgets";
import PersonalTargetsWidget from "../widgets/PersonalTargetsWidget";
import ViewsWidget from "../widgets/ViewsWidget";
import WelcomeWidget from "../widgets/WelcomeWidget";

const Home = () => {
  return (
    <React.Fragment>
      <AdminAppBar>
        <AdminToolbar>
          <RecentNotifications />
        </AdminToolbar>
      </AdminAppBar>
      <Grid container spacing={2}>
        <Grid item xs={12} md={6} lg={4}>
          <WelcomeWidget />
          <AchievementWidget />
        </Grid>
        <Grid item xs={12} md={6} lg={4}>
          <FollowersWidget />
          <ViewsWidget />
        </Grid>
        <Grid item xs={12} md={6} lg={4}>
          <PersonalTargetsWidget />
          <MeetingWidgets />
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

export default Home;


================================================
FILE: src/admin/pages/Profile.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Box from "@material-ui/core/Box";
import Fab from "@material-ui/core/Fab";
import Grid from "@material-ui/core/Grid";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
import Typography from "@material-ui/core/Typography";
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import PersonIcon from "@material-ui/icons/Person";
import React from "react";
import { useTranslation } from "react-i18next";
import { NavLink, Outlet } from "react-router-dom";
import { useAuth } from "../../auth/contexts/AuthProvider";
import QueryWrapper from "../../core/components/QueryWrapper";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import AdminAppBar from "../components/AdminAppBar";
import AdminToolbar from "../components/AdminToolbar";
import CircleProgressWidget from "../widgets/CircleProgressWidget";

const profileMenuItems = [
  {
    key: "profile.menu.activity",
    path: "",
  },
  {
    key: "profile.menu.info",
    path: "./information",
  },
  {
    key: "profile.menu.password",
    path: "./password",
  },
];

const Profile = () => {
  const { isLoggingOut, logout, userInfo } = useAuth();
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const handleLogout = () => {
    logout().catch(() =>
      snackbar.error(t("common.errors.unexpected.subTitle"))
    );
  };

  return (
    <React.Fragment>
      <AdminAppBar>
        <AdminToolbar>
          <Fab
            aria-label="logout"
            color="secondary"
            disabled={isLoggingOut}
            onClick={handleLogout}
          >
            <ExitToAppIcon />
          </Fab>
        </AdminToolbar>
      </AdminAppBar>
      <Grid container spacing={12}>
        <Grid item xs={12} md={4} marginTop={3}>
          <Box
            sx={{
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              textAlign: "center",
              mb: 6,
            }}
          >
            <Avatar
              sx={{
                bgcolor: "background.paper",
                mb: 3,
                height: 160,
                width: 160,
              }}
            >
              <PersonIcon sx={{ fontSize: 120 }} />
            </Avatar>
            <Typography
              component="div"
              variant="h4"
            >{`${userInfo?.firstName} ${userInfo?.lastName}`}</Typography>
            <Typography variant="body2">{userInfo?.role}</Typography>
          </Box>
          <CircleProgressWidget
            height={244}
            title={t("profile.completion.title")}
            value={75}
          />
        </Grid>
        <Grid item xs={12} md={8} marginTop={3}>
          <Box sx={{ mb: 4 }}>
            <Tabs aria-label="profile nav tabs" value={false}>
              {profileMenuItems.map((item) => (
                <Tab
                  key={item.key}
                  activeClassName="Mui-selected"
                  end={true}
                  component={NavLink}
                  label={t(item.key)}
                  to={item.path}
                />
              ))}
            </Tabs>
          </Box>
          <QueryWrapper>
            <Outlet />
          </QueryWrapper>
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

export default Profile;


================================================
FILE: src/admin/pages/ProfileActivity.tsx
================================================
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import Timeline from "@material-ui/lab/Timeline";
import TimelineConnector from "@material-ui/lab/TimelineConnector";
import TimelineContent from "@material-ui/lab/TimelineContent";
import TimelineDot from "@material-ui/lab/TimelineDot";
import TimelineItem from "@material-ui/lab/TimelineItem";
import TimelineSeparator from "@material-ui/lab/TimelineSeparator";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { Trans, useTranslation } from "react-i18next";
import Empty from "../../core/components/Empty";
import { useDateLocale } from "../../core/hooks/useDateLocale";
import { logKeys } from "../config/activity";
import { useActivityLogs } from "../hooks/useActivityLogs";

const ProfileActivity = () => {
  const locale = useDateLocale();
  const { t } = useTranslation();

  const { data } = useActivityLogs();

  if (!data || data.length === 0) {
    return <Empty title={t("profile.activity.empty")} />;
  }

  return (
    <Box sx={{ "& .MuiTimelineItem-root:before": { content: "none" } }}>
      <Timeline>
        {data.map((log) => (
          <TimelineItem key={log.id}>
            <TimelineSeparator>
              <TimelineDot color="grey" />
              <TimelineConnector color="grey" />
            </TimelineSeparator>
            <TimelineContent>
              <Card>
                <CardContent>
                  <Trans
                    components={{ bold: <strong /> }}
                    defaults="<bold>You</bold> modify resource <bold>{{ resouce }}</bold>"
                    i18nKey={logKeys[log.code]}
                    values={log.params}
                  />
                  <Typography component="div" marginTop={1} variant="caption">
                    {formatDistanceToNow(new Date(log.createdAt), {
                      addSuffix: true,
                      locale,
                    })}
                  </Typography>
                </CardContent>
              </Card>
            </TimelineContent>
          </TimelineItem>
        ))}
      </Timeline>
    </Box>
  );
};

export default ProfileActivity;


================================================
FILE: src/admin/pages/ProfileInformation.tsx
================================================
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardActions from "@material-ui/core/CardActions";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormLabel from "@material-ui/core/FormLabel";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import TextField from "@material-ui/core/TextField";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
import { useUpdateProfileInfo } from "../../admin/hooks/useUpdateProfileInfo";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import { useProfileInfo } from "../hooks/useProfileInfo";
import { ProfileInfo } from "../types/profileInfo";

const genders = [
  { label: "profile.info.form.gender.options.f", value: "F" },
  { label: "profile.info.form.gender.options.m", value: "M" },
  { label: "profile.info.form.gender.options.n", value: "NC" },
];

const ProfileInformation = () => {
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const { data } = useProfileInfo();
  const { isUpdating, updateProfileInfo } = useUpdateProfileInfo();

  const formik = useFormik({
    initialValues: {
      email: data ? data.email : "",
      firstName: data ? data.firstName : "",
      gender: data ? data.gender : undefined,
      job: data ? data.job : "",
      lastName: data ? data.lastName : "",
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .email(t("common.validations.email"))
        .required(t("common.validations.required")),
      firstName: Yup.string()
        .max(20, t("common.validations.max", { size: 20 }))
        .required(t("common.validations.required")),
      lastName: Yup.string()
        .max(30, t("common.validations.max", { size: 30 }))
        .required(t("common.validations.required")),
    }),
    onSubmit: (values) => handleSubmit(values),
  });

  const handleSubmit = async (values: Partial<ProfileInfo>) => {
    updateProfileInfo({ ...values, id: data?.id } as ProfileInfo)
      .then(() => {
        snackbar.success(t("profile.notifications.informationUpdated"));
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  return (
    <form onSubmit={formik.handleSubmit} noValidate>
      <Card>
        <CardHeader title={t("profile.info.title")} />
        <CardContent>
          <TextField
            margin="normal"
            required
            fullWidth
            id="lastName"
            label={t("profile.info.form.lastName.label")}
            name="lastName"
            autoComplete="family-name"
            autoFocus
            disabled={isUpdating}
            value={formik.values.lastName}
            onChange={formik.handleChange}
            error={formik.touched.lastName && Boolean(formik.errors.lastName)}
            helperText={formik.touched.lastName && formik.errors.lastName}
          />
          <TextField
            margin="normal"
            required
            fullWidth
            id="firstName"
            label={t("profile.info.form.firstName.label")}
            name="firstName"
            autoComplete="given-name"
            disabled={isUpdating}
            value={formik.values.firstName}
            onChange={formik.handleChange}
            error={formik.touched.firstName && Boolean(formik.errors.firstName)}
            helperText={formik.touched.firstName && formik.errors.firstName}
          />
          <FormControl component="fieldset" margin="normal">
            <FormLabel component="legend">
              {t("profile.info.form.gender.label")}
            </FormLabel>
            <RadioGroup
              row
              aria-label="gender"
              name="gender"
              value={formik.values.gender}
              onChange={formik.handleChange}
            >
              {genders.map((gender) => (
                <FormControlLabel
                  key={gender.value}
                  value={gender.value}
                  control={<Radio />}
                  label={t(gender.label)}
                />
              ))}
            </RadioGroup>
          </FormControl>
          <TextField
            margin="normal"
            required
            fullWidth
            id="email"
            label={t("profile.info.form.email.label")}
            name="email"
            autoComplete="email"
            disabled={isUpdating}
            value={formik.values.email}
            onChange={formik.handleChange}
            error={formik.touched.email && Boolean(formik.errors.email)}
            helperText={formik.touched.email && formik.errors.email}
          />
        </CardContent>
        <CardActions>
          <Button onClick={() => formik.resetForm()}>
            {t("common.reset")}
          </Button>
          <LoadingButton loading={isUpdating} type="submit" variant="contained">
            {t("common.update")}
          </LoadingButton>
        </CardActions>
      </Card>
    </form>
  );
};

export default ProfileInformation;


================================================
FILE: src/admin/pages/ProfilePassword.tsx
================================================
import Card from "@material-ui/core/Card";
import CardActions from "@material-ui/core/CardActions";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import TextField from "@material-ui/core/TextField";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
import { useUpdatePassword } from "../../auth/hooks/useUpdatePassword";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";

const ProfilePassword = () => {
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const { isUpdating, updatePassword } = useUpdatePassword();

  const formik = useFormik({
    initialValues: {
      oldPassword: "",
      newPassword: "",
      confirmPassword: "",
    },
    validationSchema: Yup.object({
      oldPassword: Yup.string()
        .min(8, t("common.validations.min", { size: 8 }))
        .required(t("common.validations.required")),
      newPassword: Yup.string()
        .min(8, t("common.validations.min", { size: 8 }))
        .required(t("common.validations.required")),
      confirmPassword: Yup.string()
        .oneOf([Yup.ref("newPassword")], t("common.validations.passwordMatch"))
        .required(t("common.validations.required")),
    }),
    onSubmit: (values) =>
      handleUpdatePassword(values.oldPassword, values.newPassword),
  });

  const handleUpdatePassword = async (
    oldPassword: string,
    newPassword: string
  ) => {
    updatePassword({ oldPassword, newPassword })
      .then(() => {
        formik.resetForm();
        snackbar.success(t("profile.notifications.passwordChanged"));
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  return (
    <form onSubmit={formik.handleSubmit} noValidate>
      <Card>
        <CardHeader title={t("profile.password.title")} />
        <CardContent>
          <TextField
            margin="normal"
            required
            fullWidth
            name="oldPassword"
            label={t("profile.password.form.current.label")}
            type="password"
            id="oldPassword"
            autoComplete="current-password"
            disabled={isUpdating}
            value={formik.values.oldPassword}
            onChange={formik.handleChange}
            error={
              formik.touched.oldPassword && Boolean(formik.errors.oldPassword)
            }
            helperText={formik.touched.oldPassword && formik.errors.oldPassword}
          />
          <TextField
            margin="normal"
            required
            fullWidth
            name="newPassword"
            label={t("profile.password.form.new.label")}
            type="password"
            id="newPassword"
            disabled={isUpdating}
            value={formik.values.newPassword}
            onChange={formik.handleChange}
            error={
              formik.touched.newPassword && Boolean(formik.errors.newPassword)
            }
            helperText={formik.touched.newPassword && formik.errors.newPassword}
          />
          <TextField
            margin="normal"
            required
            fullWidth
            name="confirmPassword"
            label={t("profile.password.form.confirm.label")}
            type="password"
            id="confirmPassword"
            disabled={isUpdating}
            value={formik.values.confirmPassword}
            onChange={formik.handleChange}
            error={
              formik.touched.confirmPassword &&
              Boolean(formik.errors.confirmPassword)
            }
            helperText={
              formik.touched.confirmPassword && formik.errors.confirmPassword
            }
          />
        </CardContent>
        <CardActions>
          <LoadingButton type="submit" loading={isUpdating} variant="contained">
            {t("common.update")}
          </LoadingButton>
        </CardActions>
      </Card>
    </form>
  );
};

export default ProfilePassword;


================================================
FILE: src/admin/types/activityLog.ts
================================================
export interface ActivityLog {
  id: string;
  actor: string;
  code: string;
  createdAt: number;
  params?: { [key: string]: string };
}


================================================
FILE: src/admin/types/notification.ts
================================================
export interface Notification {
  id: string;
  code: string;
  createdAt: number;
  params?: {
    quantity?: string;
    user?: string;
  };
  unread: boolean;
}


================================================
FILE: src/admin/types/profileInfo.ts
================================================
export interface ProfileInfo {
  id: string;
  avatar?: string;
  email: string;
  firstName: string;
  gender?: "F" | "M" | "NC";
  job: string;
  lastName: string;
}


================================================
FILE: src/admin/widgets/AchievementWidget.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import StarIcon from "@material-ui/icons/Star";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import { useAuth } from "../../auth/contexts/AuthProvider";

const AchievementWidget = () => {
  const { userInfo } = useAuth();
  const { t } = useTranslation();

  return (
    <Card sx={{ bgcolor: "primary.main", color: "primary.contrastText" }}>
      <CardContent
        sx={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          textAlign: "center",
        }}
      >
        <Avatar sx={{ bgcolor: "secondary.main", mb: 3 }}>
          <StarIcon color="primary" />
        </Avatar>
        <Typography gutterBottom variant="h5" component="div">
          {t("admin.home.achievement.title", { name: userInfo?.firstName })}
        </Typography>
        <Typography marginBottom={3} variant="body2">
          {t("admin.home.achievement.description", {
            progress: userInfo?.progress,
          })}
        </Typography>
        <Button
          color="secondary"
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/admin/profile`}
          variant="contained"
        >
          {t("admin.home.achievement.action")}
        </Button>
      </CardContent>
    </Card>
  );
};

export default AchievementWidget;


================================================
FILE: src/admin/widgets/ActivityWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import { useTheme } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis } from "recharts";

const data = [
  {
    name: "Jan",
    pv: 2400,
  },
  {
    name: "Feb",
    pv: 1398,
  },
  {
    name: "Mar",
    pv: 9800,
  },
  {
    name: "Apr",
    pv: 3908,
  },
  {
    name: "May",
    pv: 4800,
  },
  {
    name: "Jun",
    pv: 3800,
  },
  {
    name: "Jul",
    pv: 4300,
  },
];

const ActivityWidget = () => {
  const { t } = useTranslation();
  const theme = useTheme();

  return (
    <Card>
      <CardHeader title={t("dashboard.activity.title")} />
      <CardContent>
        <ResponsiveContainer width="99%" height={244}>
          <LineChart
            width={500}
            height={300}
            data={data}
            margin={{
              top: 5,
              right: 16,
              left: 16,
              bottom: 5,
            }}
          >
            <XAxis
              axisLine={false}
              tick={{ fill: theme.palette.text.secondary, fontSize: 12 }}
              tickLine={false}
              dataKey="name"
            />
            <Tooltip
              contentStyle={{
                borderRadius: 16,
                boxShadow: theme.shadows[3],
                backgroundColor: theme.palette.background.paper,
                borderColor: theme.palette.background.paper,
              }}
            />
            <Line
              name="Value"
              type="monotone"
              dataKey="pv"
              stroke={theme.palette.primary.main}
              strokeWidth={6}
              activeDot={{ r: 8 }}
            />
          </LineChart>
        </ResponsiveContainer>
      </CardContent>
    </Card>
  );
};

export default ActivityWidget;


================================================
FILE: src/admin/widgets/BudgetWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import { useTheme } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
import {
  PolarAngleAxis,
  Radar,
  RadarChart,
  ResponsiveContainer,
  Tooltip,
} from "recharts";

const data = [
  {
    subject: "Marketing",
    A: 110,
  },
  {
    subject: "Research",
    A: 98,
  },
  {
    subject: "Sales",
    A: 86,
  },
  {
    subject: "Ops",
    A: 99,
  },
  {
    subject: "HR",
    A: 85,
  },
  {
    subject: "Dev",
    A: 65,
  },
];

const BudgetWidget = () => {
  const { t } = useTranslation();
  const theme = useTheme();

  return (
    <Card>
      <CardHeader title={t("dashboard.budget.title")} />
      <CardContent>
        <ResponsiveContainer width="99%" height={244}>
          <RadarChart cx="50%" cy="50%" outerRadius="80%" data={data}>
            <PolarAngleAxis
              dataKey="subject"
              tick={{ fill: theme.palette.text.secondary, fontSize: 14 }}
            />
            <Radar
              name={t("dashboard.budget.legend.unit")}
              dataKey="A"
              stroke={theme.palette.primary.main}
              strokeWidth={8}
              fill={theme.palette.primary.main}
            />
            <Tooltip
              contentStyle={{
                borderRadius: 16,
                boxShadow: theme.shadows[3],
                backgroundColor: theme.palette.background.paper,
                borderColor: theme.palette.background.paper,
              }}
            />
          </RadarChart>
        </ResponsiveContainer>
      </CardContent>
    </Card>
  );
};

export default BudgetWidget;


================================================
FILE: src/admin/widgets/CircleProgressWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import useTheme from "@material-ui/core/styles/useTheme";
import {
  PolarAngleAxis,
  RadialBar,
  RadialBarChart,
  ResponsiveContainer,
} from "recharts";

type CircleProgressWidgetProps = {
  height?: number;
  title: string;
  value: number;
};

const CircleProgressWidget = ({
  height = 120,
  title,
  value,
}: CircleProgressWidgetProps) => {
  const theme = useTheme();

  return (
    <Card>
      <CardHeader title={title} />
      <CardContent>
        <ResponsiveContainer width="99%" height={height}>
          <RadialBarChart
            innerRadius="85%"
            outerRadius="85%"
            barSize={32}
            data={[{ fill: theme.palette.primary.main, value }]}
            startAngle={90}
            endAngle={-270}
          >
            <PolarAngleAxis
              type="number"
              domain={[0, 100]}
              dataKey={"value"}
              angleAxisId={0}
              tick={false}
            />
            <RadialBar
              cornerRadius={16}
              label={{
                fill: theme.palette.text.primary,
                fontSize: theme.typography.h1.fontSize,
                fontWeight: theme.typography.h1.fontWeight,
                position: "center",
              }}
              background={{ fill: theme.palette.background.default }}
              dataKey="value"
            />
          </RadialBarChart>
        </ResponsiveContainer>
      </CardContent>
    </Card>
  );
};

export default CircleProgressWidget;


================================================
FILE: src/admin/widgets/FollowersWidget.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
import ArrowDropUpIcon from "@material-ui/icons/ArrowDropUp";
import ArrowRightIcon from "@material-ui/icons/ArrowRight";
import EmojiEmotionsIcon from "@material-ui/icons/EmojiEmotions";
import FavoriteIcon from "@material-ui/icons/Favorite";
import ThumbUpIcon from "@material-ui/icons/ThumbUp";
import React from "react";
import { useTranslation } from "react-i18next";

const socials = [
  {
    bgcolor: "primary.main",
    icon: <ThumbUpIcon sx={{ color: "#fff" }} />,
    name: "Likes",
    trend: <ArrowDropUpIcon sx={{ color: "success.main" }} />,
    unitKey: "admin.home.followers.units.likes",
    value: "26,789",
  },
  {
    bgcolor: "error.main",
    icon: <FavoriteIcon style={{ color: "#fff" }} />,
    name: "Love",
    trend: <ArrowRightIcon sx={{ color: "action.disabled" }} />,
    unitKey: "admin.home.followers.units.love",
    value: "6,754",
  },
  {
    bgcolor: "warning.main",
    icon: <EmojiEmotionsIcon style={{ color: "#fff" }} />,
    name: "Smiles",
    trend: <ArrowDropDownIcon sx={{ color: "error.main" }} />,
    unitKey: "admin.home.followers.units.smiles",
    value: "52,789",
  },
];

const FollowersWidget = () => {
  const { t } = useTranslation();

  return (
    <React.Fragment>
      {socials.map((social) => (
        <Card key={social.name} sx={{ mb: 2 }}>
          <CardContent sx={{ display: "flex", alignItems: "center" }}>
            <Avatar
              aria-label={`${social.name} avatar`}
              sx={{ bgcolor: social.bgcolor, mr: 2 }}
            >
              {social.icon}
            </Avatar>
            <Box sx={{ flexGrow: 1 }}>
              <Typography component="div" variant="h6">
                {social.value}
              </Typography>
              <Typography variant="body2" color="textSecondary" component="div">
                {t(social.name)}
              </Typography>
            </Box>
            {social.trend}
          </CardContent>
        </Card>
      ))}
    </React.Fragment>
  );
};

export default FollowersWidget;


================================================
FILE: src/admin/widgets/MeetingWidgets.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import IconButton from "@material-ui/core/IconButton";
import Typography from "@material-ui/core/Typography";
import AddIcon from "@material-ui/icons/Add";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";

const meetings = [
  {
    id: "1",
    person: "Emmy Anderson",
    date: "8:00 - 10:00",
    image: "img/portrait-1.jpg",
  },
  {
    id: "2",
    person: "Joy McGlynn",
    date: "11:00 - 12:00",
    image: "img/portrait-2.jpg",
  },
  {
    id: "3",
    person: "Mara Dach",
    date: "14:00 - 15:00",
    image: "img/portrait-3.jpg",
  },
];

const MeetingWidgets = () => {
  const { t } = useTranslation();

  return (
    <React.Fragment>
      <Typography component="h2" marginBottom={3} variant="h4">
        {t("admin.home.meeting.title")}
      </Typography>
      {meetings.map((meeting) => (
        <Card key={meeting.id} sx={{ mb: 2 }}>
          <CardContent sx={{ display: "flex", alignItems: "center" }}>
            <Avatar
              alt={`${meeting.person} avatar`}
              src={meeting.image}
              sx={{ mr: 2 }}
            />
            <Box sx={{ flexGrow: 1 }}>
              <Typography component="div" variant="h6">
                {meeting.person}
              </Typography>
              <Typography variant="body2" color="textSecondary" component="div">
                {meeting.date}
              </Typography>
            </Box>
            <IconButton
              aria-label="Go to meeting details"
              component={RouterLink}
              to={`/${process.env.PUBLIC_URL}/admin/calendar`}
            >
              <ChevronRightIcon />
            </IconButton>
          </CardContent>
        </Card>
      ))}
      <Button
        aria-label="Add new meeting"
        color="secondary"
        component={RouterLink}
        fullWidth
        to={`/${process.env.PUBLIC_URL}/admin/calendar`}
        variant="contained"
      >
        <AddIcon />
      </Button>
    </React.Fragment>
  );
};

export default MeetingWidgets;


================================================
FILE: src/admin/widgets/OverviewWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";

type OverviewWidgetProps = {
  color?: "primary" | "warning" | "error";
  description: string;
  title: string;
};

const OverviewWidget = ({ description, title }: OverviewWidgetProps) => {
  return (
    <Card>
      <CardContent sx={{ textAlign: "center" }}>
        <Typography gutterBottom component="div" variant="h3">
          {title}
        </Typography>
        <Typography variant="body2" color="textSecondary" component="p">
          {description}
        </Typography>
      </CardContent>
    </Card>
  );
};

export default OverviewWidget;


================================================
FILE: src/admin/widgets/PersonalTargetsWidget.tsx
================================================
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import LinearProgress from "@material-ui/core/LinearProgress";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next";

const targets = [
  { name: "Views", nameKey: "admin.home.targets.views", value: 75 },
  { name: "Followers", nameKey: "admin.home.targets.followers", value: 50 },
  { name: "Income", nameKey: "admin.home.targets.income", value: 25 },
];

const PersonalTargetsWidget = () => {
  const { t } = useTranslation();

  return (
    <Card sx={{ mb: 4 }}>
      <CardHeader title={t("admin.home.targets.title")} />
      <CardContent>
        <List>
          {targets.map((target) => (
            <ListItem disableGutters key={target.name}>
              <ListItemText>
                <Box sx={{ display: "flex", mb: 1 }}>
                  <Typography component="div" variant="h6">
                    {t(target.nameKey)}
                  </Typography>
                  <Box sx={{ flexGrow: 1 }} />
                  <Typography component="div" variant="h6">
                    {`${target.value}%`}
                  </Typography>
                </Box>
                <LinearProgress
                  aria-label={`${t(target.nameKey)} progress`}
                  sx={{
                    color:
                      target.value >= 75
                        ? "primary.main"
                        : target.value <= 25
                        ? "error.main"
                        : "warning.main",
                  }}
                  color="inherit"
                  variant="determinate"
                  value={target.value}
                />
              </ListItemText>
            </ListItem>
          ))}
        </List>
      </CardContent>
    </Card>
  );
};

export default PersonalTargetsWidget;


================================================
FILE: src/admin/widgets/ProgressWidget.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import LinearProgress from "@material-ui/core/LinearProgress";
import Typography from "@material-ui/core/Typography";

type ProgressWidgetProps = {
  avatar: React.ReactNode;
  mb?: number;
  title: string;
  value: number;
};

const ProgressWidget = ({
  avatar,
  mb = 0,
  title,
  value,
}: ProgressWidgetProps) => {
  return (
    <Card sx={{ mb }}>
      <CardContent sx={{ display: "flex", alignItems: "center" }}>
        <Avatar sx={{ mr: 2 }}>{avatar}</Avatar>
        <Box sx={{ flexGrow: 1 }}>
          <Box sx={{ display: "flex", mb: 1 }}>
            <Typography component="div" variant="h6">
              {title}
            </Typography>
            <Box sx={{ flexGrow: 1 }} />
            <Typography component="div" color="textSecondary">
              {`${value}%`}
            </Typography>
          </Box>
          <LinearProgress
            aria-label={`${title} progress`}
            sx={{ height: 8 }}
            variant="determinate"
            value={value}
          />
        </Box>
      </CardContent>
    </Card>
  );
};

export default ProgressWidget;


================================================
FILE: src/admin/widgets/SalesByAgeWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import { useTheme } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
import {
  Legend,
  PolarAngleAxis,
  RadialBar,
  RadialBarChart,
  ResponsiveContainer,
} from "recharts";

const SalesByAgeWidget = () => {
  const { t } = useTranslation();
  const theme = useTheme();

  const data = [
    {
      name: "18-39",
      uv: 30,
      fill: theme.palette.text.secondary,
    },
    {
      name: "40-59",
      uv: 45,
      fill: theme.palette.error.main,
    },
    {
      name: "60-79",
      uv: 60,
      fill: theme.palette.warning.main,
    },
    {
      name: "80+",
      uv: 75,
      fill: theme.palette.primary.main,
    },
  ];

  return (
    <Card>
      <CardHeader title={t("dashboard.salesByAge.title")} />
      <CardContent>
        <ResponsiveContainer width="99%" height={244}>
          <RadialBarChart
            barGap={1}
            innerRadius="15%"
            outerRadius="100%"
            barSize={16}
            data={data}
          >
            <PolarAngleAxis
              type="number"
              domain={[0, 100]}
              dataKey={"value"}
              angleAxisId={0}
              tick={false}
            />
            <RadialBar
              background={{ fill: theme.palette.background.default }}
              cornerRadius={16}
              label={{ position: "insideStart", fill: "#fff", fontWeight: 700 }}
              dataKey="uv"
            />
            <Legend
              align="right"
              wrapperStyle={{ fontWeight: 700 }}
              iconSize={16}
              layout="vertical"
              verticalAlign="middle"
            />
          </RadialBarChart>
        </ResponsiveContainer>
      </CardContent>
    </Card>
  );
};

export default SalesByAgeWidget;


================================================
FILE: src/admin/widgets/SalesByCategoryWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import { useTheme } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
import { Legend, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";

const SalesByCategoryWidget = () => {
  const { t } = useTranslation();
  const theme = useTheme();

  const data = [
    {
      name: t("dashboard.salesByCategory.legend.books"),
      fill: theme.palette.primary.main,
      value: 400,
    },
    {
      name: t("dashboard.salesByCategory.legend.movies"),
      fill: theme.palette.warning.main,
      value: 300,
    },
    {
      name: t("dashboard.salesByCategory.legend.software"),
      fill: theme.palette.error.main,
      value: 300,
    },
  ];

  return (
    <Card>
      <CardHeader title={t("dashboard.salesByCategory.title")} />
      <CardContent>
        <ResponsiveContainer width="99%" height={244}>
          <PieChart width={244} height={244}>
            <Pie
              dataKey="value"
              isAnimationActive={false}
              data={data}
              cx="50%"
              cy="50%"
              outerRadius={80}
              stroke={theme.palette.background.paper}
              strokeWidth={8}
            />

            <Tooltip
              contentStyle={{
                borderRadius: 16,
                boxShadow: theme.shadows[3],
                backgroundColor: theme.palette.background.paper,
                borderColor: theme.palette.background.paper,
              }}
              itemStyle={{
                color: theme.palette.text.primary,
              }}
            />
            <Legend wrapperStyle={{ fontSize: 14 }} />
          </PieChart>
        </ResponsiveContainer>
      </CardContent>
    </Card>
  );
};

export default SalesByCategoryWidget;


================================================
FILE: src/admin/widgets/SalesHistoryWidget.tsx
================================================
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import { useTheme } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import TrendingUpIcon from "@material-ui/icons/TrendingUp";
import { useTranslation } from "react-i18next";
import { Bar, BarChart, ResponsiveContainer } from "recharts";

type SalesWidgetProps = {
  value: number;
};

const SalesWidget = ({ value }: SalesWidgetProps) => {
  const { t } = useTranslation();
  const theme = useTheme();

  const data = [
    {
      name: "Mon",
      uv: 4000,
    },
    {
      name: "Tue",
      uv: 3000,
    },
    {
      name: "Wed",
      uv: 2000,
    },
    {
      name: "Thu",
      uv: 2780,
    },
    {
      name: "Fri",
      uv: 1890,
    },
    {
      name: "Sat",
      uv: 2390,
    },
  ];

  return (
    <Card>
      <CardHeader title={t("dashboard.salesHistory.title")} />
      <CardContent>
        <ResponsiveContainer width="99%" height={124}>
          <BarChart
            width={150}
            height={40}
            data={data}
            margin={{
              right: 0,
              left: 0,
            }}
          >
            <Bar
              dataKey="uv"
              fill={theme.palette.primary.main}
              radius={[50, 50, 50, 50]}
            />
          </BarChart>
        </ResponsiveContainer>
        <Box sx={{ display: "flex", alignItems: "center", mt: 3 }}>
          <Box sx={{ flexGrow: 1 }}>
            <Typography variant="h2" component="div" marginBottom={1}>
              {value}
            </Typography>
            <Typography variant="body2" color="textSecondary" component="div">
              {t("dashboard.salesHistory.unit")}
            </Typography>
          </Box>
          <TrendingUpIcon sx={{ color: "text.secondary" }} />
        </Box>
      </CardContent>
    </Card>
  );
};

export default SalesWidget;


================================================
FILE: src/admin/widgets/TeamProgressWidget.tsx
================================================
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import LinearProgress from "@material-ui/core/LinearProgress";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next";

const teams = [
  {
    id: "1",
    color: "primary.main",
    name: "Marketing Team",
    progress: 75,
    value: 122,
  },
  {
    id: "2",
    color: "warning.main",
    name: "Operations Team",
    progress: 50,
    value: 82,
  },
  {
    id: "3",
    color: "error.main",
    name: "Sales Team",
    progress: 25,
    value: 39,
  },
  {
    id: "4",
    color: "text.secondary",
    name: "Research Team",
    progress: 10,
    value: 9,
  },
];

const TeamProgressWidget = () => {
  const { t } = useTranslation();

  return (
    <Card>
      <CardHeader title={t("dashboard.teams.title")} />
      <CardContent sx={{ px: 2 }}>
        <TableContainer>
          <Table
            aria-label="team progress table"
            size="small"
            sx={{
              "& td, & th": {
                border: 0,
              },
            }}
          >
            <TableHead>
              <TableRow>
                <TableCell>{t("dashboard.teams.columns.team")}</TableCell>
                <TableCell>{t("dashboard.teams.columns.progress")}</TableCell>
                <TableCell align="center">
                  {t("dashboard.teams.columns.value")}
                </TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {teams.map((team) => (
                <TableRow key={team.id}>
                  <TableCell>
                    <Typography color="text.secondary" component="div">
                      {team.name}
                    </Typography>
                  </TableCell>
                  <TableCell>
                    <Box sx={{ display: "flex", alignItems: "center" }}>
                      <Box sx={{ width: "100%", mr: 3 }}>
                        <LinearProgress
                          aria-label={`${team.name} progress`}
                          color="inherit"
                          sx={{ color: team.color }}
                          value={team.progress}
                          variant="determinate"
                        />
                      </Box>
                      <Box sx={{ minWidth: 35 }}>
                        <Typography
                          component="span"
                          variant="h6"
                          color={team.color}
                        >{`${team.progress}%`}</Typography>
                      </Box>
                    </Box>
                  </TableCell>
                  <TableCell align="center">{team.value}</TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
      </CardContent>
    </Card>
  );
};

export default TeamProgressWidget;


================================================
FILE: src/admin/widgets/UsersWidget.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import IconButton from "@material-ui/core/IconButton";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import ListItemText from "@material-ui/core/ListItemText";
import { useTheme } from "@material-ui/core/styles";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import PersonIcon from "@material-ui/icons/Person";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";

const users = [
  {
    id: "1",
    firstName: "Rhys",
    gender: "M",
    lastName: "Arriaga",
    role: "Admin",
  },
  {
    id: "2",
    firstName: "Laura",
    gender: "F",
    lastName: "Core",
    role: "Member",
  },
  {
    id: "3",
    firstName: "Joshua",
    gender: "M",
    lastName: "Jagger",
    role: "Member",
  },
];

const UsersWidget = () => {
  const theme = useTheme();
  const { t } = useTranslation();

  return (
    <Card>
      <CardHeader title={t("dashboard.users.title")} />
      <CardContent>
        <List>
          {users.map((user) => (
            <ListItem disableGutters key={user.id}>
              <ListItemAvatar>
                <Avatar>
                  <PersonIcon />
                </Avatar>
              </ListItemAvatar>
              <ListItemText
                primary={`${user.lastName} ${user.firstName}`}
                primaryTypographyProps={{
                  fontWeight: theme.typography.fontWeightMedium,
                }}
                secondary={user.role}
              />
              <ListItemSecondaryAction>
                <IconButton
                  aria-label="Go to user details"
                  component={RouterLink}
                  edge="end"
                  to={`/${process.env.PUBLIC_URL}/admin/user-management`}
                >
                  <ChevronRightIcon />
                </IconButton>
              </ListItemSecondaryAction>
            </ListItem>
          ))}
        </List>
      </CardContent>
    </Card>
  );
};

export default UsersWidget;


================================================
FILE: src/admin/widgets/ViewsWidget.tsx
================================================
import Avatar from "@material-ui/core/Avatar";
import Box from "@material-ui/core/Box";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import IconButton from "@material-ui/core/IconButton";
import useTheme from "@material-ui/core/styles/useTheme";
import Typography from "@material-ui/core/Typography";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import DashboardIcon from "@material-ui/icons/Dashboard";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis } from "recharts";

const data = [
  {
    name: "Jan",
    fb: 2.5,
  },
  {
    name: "Feb",
    fb: 1.4,
  },
  {
    name: "Mar",
    fb: 6,
  },
  {
    name: "Avr",
    fb: 4,
  },
];

const views = "6.967.431";

const ViewsWidget = () => {
  const theme = useTheme();
  const { t } = useTranslation();

  return (
    <Card>
      <CardContent>
        <Typography
          align="center"
          component="div"
          marginBottom={2}
          variant="body2"
        >
          {t("admin.home.views.unit")}
        </Typography>
        <Typography align="center" component="div" variant="h2">
          {views}
        </Typography>
        <Box sx={{ height: 224 }}>
          <ResponsiveContainer width="100%" height="100%">
            <AreaChart
              width={500}
              height={400}
              data={data}
              margin={{
                top: 0,
                right: 0,
                left: 0,
                bottom: 0,
              }}
            >
              <XAxis
                axisLine={false}
                dataKey="name"
                interval="preserveStartEnd"
                tick={{ fill: theme.palette.text.secondary, fontSize: 12 }}
                tickLine={false}
              />
              <Tooltip
                contentStyle={{
                  borderRadius: 16,
                  boxShadow: theme.shadows[3],
                  backgroundColor: theme.palette.background.paper,
                  borderColor: theme.palette.background.paper,
                }}
              />
              <Area
                type="monotone"
                dataKey="fb"
                fill={theme.palette.primary.main}
                fillOpacity={0.3}
                stroke={theme.palette.primary.main}
                strokeWidth={6}
                activeDot={{ r: 8 }}
              />
            </AreaChart>
          </ResponsiveContainer>
        </Box>
        <Card sx={{ bgcolor: "background.default", mt: 5 }}>
          <CardContent sx={{ display: "flex", alignItems: "center" }}>
            <Avatar sx={{ bgcolor: "background.paper", mr: 2 }}>
              <DashboardIcon />
            </Avatar>
            <Box sx={{ flexGrow: 1 }}>
              <Typography component="div" variant="h6">
                {t("admin.home.views.action")}
              </Typography>
            </Box>
            <IconButton
              aria-label="Go to dashboard"
              component={RouterLink}
              to={`/${process.env.PUBLIC_URL}/admin/dashboard`}
            >
              <ChevronRightIcon />
            </IconButton>
          </CardContent>
        </Card>
      </CardContent>
    </Card>
  );
};

export default ViewsWidget;


================================================
FILE: src/admin/widgets/WelcomeWidget.tsx
================================================
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next";
import { useAuth } from "../../auth/contexts/AuthProvider";
import { ReactComponent as WelcomeSvg } from "../../core/assets/welcome.svg";
import SvgContainer from "../../core/components/SvgContainer";

const WelcomeWidget = () => {
  const { userInfo } = useAuth();
  const { t } = useTranslation();

  return (
    <Card elevation={0} sx={{ backgroundColor: "transparent", mb: 2 }}>
      <CardContent>
        <Typography component="div" gutterBottom variant="h1">
          {t("admin.home.welcome.title", { name: userInfo?.firstName })}
        </Typography>
        <Typography
          component="div"
          sx={{ fontWeight: 300, mb: 3 }}
          variant="h1"
        >
          {t("admin.home.welcome.subTitle")}
        </Typography>
        <Typography
          color="textSecondary"
          component="p"
          gutterBottom
          marginBottom={2}
          variant="subtitle1"
        >
          {t("admin.home.welcome.message")}
        </Typography>
        <SvgContainer>
          <WelcomeSvg />
        </SvgContainer>
      </CardContent>
    </Card>
  );
};

export default WelcomeWidget;


================================================
FILE: src/auth/contexts/AuthProvider.tsx
================================================
import React, { createContext, useContext } from "react";
import { useLocalStorage } from "../../core/hooks/useLocalStorage";
import { useLogin } from "../hooks/useLogin";
import { useLogout } from "../hooks/useLogout";
import { useUserInfo } from "../hooks/useUserInfo";
import { UserInfo } from "../types/userInfo";

interface AuthContextInterface {
  hasRole: (roles?: string[]) => {};
  isLoggingIn: boolean;
  isLoggingOut: boolean;
  login: (email: string, password: string) => Promise<any>;
  logout: () => Promise<any>;
  userInfo?: UserInfo;
}

export const AuthContext = createContext({} as AuthContextInterface);

type AuthProviderProps = {
  children?: React.ReactNode;
};

const AuthProvider = ({ children }: AuthProviderProps) => {
  const [authKey, setAuthKey] = useLocalStorage<string>("authkey", "");

  const { isLoggingIn, login } = useLogin();
  const { isLoggingOut, logout } = useLogout();
  const { data: userInfo } = useUserInfo(authKey);

  const hasRole = (roles?: string[]) => {
    if (!roles || roles.length === 0) {
      return true;
    }
    if (!userInfo) {
      return false;
    }
    return roles.includes(userInfo.role);
  };

  const handleLogin = async (email: string, password: string) => {
    return login({ email, password })
      .then((key: string) => {
        setAuthKey(key);
        return key;
      })
      .catch((err) => {
        throw err;
      });
  };

  const handleLogout = async () => {
    return logout()
      .then((data) => {
        setAuthKey("");
        return data;
      })
      .catch((err) => {
        throw err;
      });
  };

  return (
    <AuthContext.Provider
      value={{
        hasRole,
        isLoggingIn,
        isLoggingOut,
        login: handleLogin,
        logout: handleLogout,
        userInfo,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export function useAuth() {
  return useContext(AuthContext);
}

export default AuthProvider;


================================================
FILE: src/auth/hooks/useForgotPassword.ts
================================================
import axios from "axios";
import { useMutation } from "react-query";

const forgotPassword = async ({ email }: { email: string }) => {
  const { data } = await axios.post("/api/forgot-password", { email });
  return data;
};

export function useForgotPassword() {
  const { isLoading, mutateAsync } = useMutation(forgotPassword);
  return { isLoading, forgotPassword: mutateAsync };
}


================================================
FILE: src/auth/hooks/useForgotPasswordSubmit.ts
================================================
import axios from "axios";
import { useMutation } from "react-query";

const forgotPasswordSubmit = async ({
  code,
  newPassword,
}: {
  code: string;
  newPassword: string;
}) => {
  const { data } = await axios.post("/api/forgot-password-submit", {
    code,
    newPassword,
  });
  return data;
};

export function useForgotPasswordSubmit() {
  const { isLoading, mutateAsync } = useMutation(forgotPasswordSubmit);
  return { isLoading, forgotPasswordSubmit: mutateAsync };
}


================================================
FILE: src/auth/hooks/useLogin.ts
================================================
import axios from "axios";
import { useMutation } from "react-query";

const login = async ({
  email,
  password,
}: {
  email: string;
  password: string;
}): Promise<string> => {
  const { data } = await axios.post("/api/login", { email, password });
  return data;
};

export function useLogin() {
  const { isLoading, mutateAsync } = useMutation(login);

  return { isLoggingIn: isLoading, login: mutateAsync };
}


================================================
FILE: src/auth/hooks/useLogout.ts
================================================
import axios from "axios";
import { useMutation } from "react-query";

const logout = async (): Promise<string> => {
  const { data } = await axios.post("/api/logout");
  return data;
};

export function useLogout() {
  const { isLoading, mutateAsync } = useMutation(logout);

  return { isLoggingOut: isLoading, logout: mutateAsync };
}


================================================
FILE: src/auth/hooks/useRegister.ts
================================================
import axios from "axios";
import { useMutation } from "react-query";
import { UserInfo } from "../types/userInfo";

const register = async (userInfo: UserInfo): Promise<UserInfo> => {
  const { data } = await axios.post("/api/register", userInfo);
  return data;
};

export function useRegister() {
  const { isLoading, mutateAsync } = useMutation(register);
  return { isRegistering: isLoading, register: mutateAsync };
}


================================================
FILE: src/auth/hooks/useUpdatePassword.ts
================================================
import axios from "axios";
import { useMutation } from "react-query";

const updatePassword = async ({
  oldPassword,
  newPassword,
}: {
  oldPassword: string;
  newPassword: string;
}) => {
  const { data } = await axios.put("/api/password", {
    oldPassword,
    newPassword,
  });
  return data;
};

export function useUpdatePassword() {
  const { isLoading, mutateAsync } = useMutation(updatePassword);
  return { isUpdating: isLoading, updatePassword: mutateAsync };
}


================================================
FILE: src/auth/hooks/useUserInfo.ts
================================================
import axios from "axios";
import { useQuery } from "react-query";
import { UserInfo } from "../types/userInfo";

const fetchUserInfo = async (key?: string): Promise<UserInfo> => {
  const { data } = await axios.get("/api/user-info", { params: { key } });
  return data;
};

export function useUserInfo(key?: string) {
  return useQuery(["user-info", key], () => fetchUserInfo(key), {
    enabled: !!key,
  });
}


================================================
FILE: src/auth/pages/ForgotPassword.tsx
================================================
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import * as Yup from "yup";
import BoxedLayout from "../../core/components/BoxedLayout";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import { useForgotPassword } from "../hooks/useForgotPassword";

const ForgotPassword = () => {
  const navigate = useNavigate();
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const { forgotPassword, isLoading } = useForgotPassword();

  const formik = useFormik({
    initialValues: {
      email: "",
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .email(t("common.validations.email"))
        .required(t("common.validations.required")),
    }),
    onSubmit: ({ email }) => handleForgotPassword(email),
  });

  const handleForgotPassword = async (email: string) => {
    forgotPassword({ email })
      .then(() => {
        snackbar.success(t("auth.forgotPassword.notifications.success"));
        navigate(`/${process.env.PUBLIC_URL}/forgot-password-submit`);
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  return (
    <BoxedLayout>
      <Typography component="h1" variant="h5">
        {t("auth.forgotPassword.title")}
      </Typography>
      <Typography marginTop={3}>{t("auth.forgotPassword.subTitle")}</Typography>
      <Box
        component="form"
        marginTop={3}
        noValidate
        onSubmit={formik.handleSubmit}
      >
        <TextField
          margin="normal"
          required
          fullWidth
          id="email"
          label={t("auth.forgotPassword.form.email.label")}
          name="email"
          autoComplete="email"
          autoFocus
          disabled={isLoading}
          value={formik.values.email}
          onChange={formik.handleChange}
          error={formik.touched.email && Boolean(formik.errors.email)}
          helperText={formik.touched.email && formik.errors.email}
        />
        <LoadingButton
          type="submit"
          fullWidth
          variant="contained"
          color="primary"
          disabled={isLoading}
          loading={isLoading}
          sx={{ mt: 2 }}
        >
          {t("auth.forgotPassword.form.action")}
        </LoadingButton>
        <Button
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/login`}
          color="primary"
          fullWidth
          sx={{ mt: 2 }}
        >
          {t("auth.forgotPassword.form.back")}
        </Button>
      </Box>
    </BoxedLayout>
  );
};

export default ForgotPassword;


================================================
FILE: src/auth/pages/ForgotPasswordSubmit.tsx
================================================
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import * as Yup from "yup";
import BoxedLayout from "../../core/components/BoxedLayout";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import { useForgotPasswordSubmit } from "../hooks/useForgotPasswordSubmit";

const ForgotPasswordSubmit = () => {
  const navigate = useNavigate();
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const { forgotPasswordSubmit, isLoading } = useForgotPasswordSubmit();

  const formik = useFormik({
    initialValues: {
      code: "",
      newPassword: "",
      confirmPassword: "",
    },
    validationSchema: Yup.object({
      code: Yup.string().required(t("common.validations.required")),
      newPassword: Yup.string().required(t("common.validations.required")),
      confirmPassword: Yup.string().required(t("common.validations.required")),
    }),
    onSubmit: ({ code, newPassword }) =>
      handleSubmitPassword(code, newPassword),
  });

  const handleSubmitPassword = async (code: string, newPassword: string) => {
    forgotPasswordSubmit({ code, newPassword })
      .then(() => {
        snackbar.success(t("auth.forgotPasswordSubmit.notifications.success"));
        navigate(`/${process.env.PUBLIC_URL}/login`);
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  return (
    <BoxedLayout>
      <Typography component="h1" variant="h5">
        {t("auth.forgotPasswordSubmit.title")}
      </Typography>
      <Typography marginTop={3}>
        {t("auth.forgotPasswordSubmit.subTitle")}
      </Typography>
      <Box
        component="form"
        marginTop={3}
        noValidate
        onSubmit={formik.handleSubmit}
      >
        <TextField
          margin="normal"
          required
          fullWidth
          id="code"
          label={t("auth.forgotPasswordSubmit.form.code.label")}
          name="code"
          autoFocus
          disabled={isLoading}
          value={formik.values.code}
          onChange={formik.handleChange}
          error={formik.touched.code && Boolean(formik.errors.code)}
          helperText={formik.touched.code && formik.errors.code}
        />
        <TextField
          margin="normal"
          required
          fullWidth
          name="newPassword"
          label={t("auth.forgotPasswordSubmit.form.newPassword.label")}
          type="password"
          id="newPassword"
          disabled={isLoading}
          value={formik.values.newPassword}
          onChange={formik.handleChange}
          error={
            formik.touched.newPassword && Boolean(formik.errors.newPassword)
          }
          helperText={formik.touched.newPassword && formik.errors.newPassword}
        />
        <TextField
          margin="normal"
          required
          fullWidth
          name="confirmPassword"
          label={t("auth.forgotPasswordSubmit.form.confirmPassword.label")}
          type="password"
          id="confirmPassword"
          disabled={isLoading}
          value={formik.values.confirmPassword}
          onChange={formik.handleChange}
          error={
            formik.touched.confirmPassword &&
            Boolean(formik.errors.confirmPassword)
          }
          helperText={
            formik.touched.confirmPassword && formik.errors.confirmPassword
          }
        />
        <LoadingButton
          type="submit"
          fullWidth
          variant="contained"
          color="primary"
          disabled={isLoading}
          loading={isLoading}
          sx={{ mt: 2 }}
        >
          {t("auth.forgotPasswordSubmit.form.action")}
        </LoadingButton>
        <Button
          component={Link}
          to={`/${process.env.PUBLIC_URL}/login`}
          color="primary"
          fullWidth
          sx={{ mt: 2 }}
        >
          {t("auth.forgotPasswordSubmit.form.back")}
        </Button>
      </Box>
    </BoxedLayout>
  );
};

export default ForgotPasswordSubmit;


================================================
FILE: src/auth/pages/Login.tsx
================================================
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
import Link from "@material-ui/core/Link";
import Paper from "@material-ui/core/Paper";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import * as Yup from "yup";
import BoxedLayout from "../../core/components/BoxedLayout";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import { useAuth } from "../contexts/AuthProvider";

const Login = () => {
  const { isLoggingIn, login } = useAuth();
  const navigate = useNavigate();
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const handleLogin = (email: string, password: string) => {
    login(email, password)
      .then(() =>
        navigate(`/${process.env.PUBLIC_URL}/admin`, { replace: true })
      )
      .catch(() => snackbar.error(t("common.errors.unexpected.subTitle")));
  };

  const formik = useFormik({
    initialValues: {
      email: "demo@example.com",
      password: "guWEK<'r/-47-XG3",
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .email(t("common.validations.email"))
        .required(t("common.validations.required")),
      password: Yup.string()
        .min(8, t("common.validations.min", { size: 8 }))
        .required(t("common.validations.required")),
    }),
    onSubmit: (values) => handleLogin(values.email, values.password),
  });

  return (
    <Grid container component="main" sx={{ height: "100vh" }}>
      <Grid
        item
        xs={false}
        sm={4}
        md={7}
        sx={{
          backgroundImage: "url(./img/startup.svg)",
          backgroundRepeat: "no-repeat",
          bgcolor: "background.default",
          backgroundSize: "cover",
          backgroundPosition: "center",
        }}
      />
      <Grid item xs={12} sm={8} md={5} component={Paper} square>
        <BoxedLayout>
          <Typography component="h1" variant="h5">
            {t("auth.login.title")}
          </Typography>
          <Box
            component="form"
            marginTop={3}
            noValidate
            onSubmit={formik.handleSubmit}
          >
            <TextField
              margin="normal"
              variant="filled"
              required
              fullWidth
              id="email"
              label={t("auth.login.form.email.label")}
              name="email"
              autoComplete="email"
              autoFocus
              disabled={isLoggingIn}
              value={formik.values.email}
              onChange={formik.handleChange}
              error={formik.touched.email && Boolean(formik.errors.email)}
              helperText={formik.touched.email && formik.errors.email}
            />
            <TextField
              margin="normal"
              variant="filled"
              required
              fullWidth
              name="password"
              label={t("auth.login.form.password.label")}
              type="password"
              id="password"
              autoComplete="current-password"
              disabled={isLoggingIn}
              value={formik.values.password}
              onChange={formik.handleChange}
              error={formik.touched.password && Boolean(formik.errors.password)}
              helperText={formik.touched.password && formik.errors.password}
            />
            <Box sx={{ textAlign: "right" }}>
              <Link
                component={RouterLink}
                to={`/${process.env.PUBLIC_URL}/forgot-password`}
                variant="body2"
              >
                {t("auth.login.forgotPasswordLink")}
              </Link>
            </Box>
            <LoadingButton
              type="submit"
              fullWidth
              loading={isLoggingIn}
              variant="contained"
              sx={{ mt: 3 }}
            >
              {t("auth.login.submit")}
            </LoadingButton>
            <Button
              component={RouterLink}
              to={`/${process.env.PUBLIC_URL}/register`}
              color="primary"
              fullWidth
              sx={{ mt: 2 }}
            >
              {t("auth.login.newAccountLink")}
            </Button>
          </Box>
        </BoxedLayout>
      </Grid>
    </Grid>
  );
};

export default Login;


================================================
FILE: src/auth/pages/Register.tsx
================================================
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormLabel from "@material-ui/core/FormLabel";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import * as Yup from "yup";
import BoxedLayout from "../../core/components/BoxedLayout";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import { useRegister } from "../hooks/useRegister";
import { UserInfo } from "../types/userInfo";

const genders = [
  { label: "auth.register.form.gender.options.f", value: "F" },
  { label: "auth.register.form.gender.options.m", value: "M" },
  { label: "auth.register.form.gender.options.n", value: "NC" },
];

const Register = () => {
  const navigate = useNavigate();
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const { isRegistering, register } = useRegister();

  const formik = useFormik({
    initialValues: {
      email: "",
      firstName: "",
      gender: "F",
      lastName: "",
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .email("Invalid email address")
        .required(t("common.validations.required")),
      firstName: Yup.string()
        .max(20, t("common.validations.max", { size: 20 }))
        .required(t("common.validations.required")),
      lastName: Yup.string()
        .max(30, t("common.validations.max", { size: 30 }))
        .required(t("common.validations.required")),
    }),
    onSubmit: (values) => handleRegister(values),
  });

  const handleRegister = async (values: Partial<UserInfo>) => {
    register(values as UserInfo)
      .then(() => {
        snackbar.success(t("auth.register.notifications.success"));
        navigate(`/${process.env.PUBLIC_URL}/login`);
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  return (
    <BoxedLayout>
      <Typography component="h1" variant="h5">
        {t("auth.register.title")}
      </Typography>
      <Box
        component="form"
        marginTop={3}
        noValidate
        onSubmit={formik.handleSubmit}
      >
        <TextField
          margin="normal"
          required
          fullWidth
          id="lastName"
          label={t("auth.register.form.lastName.label")}
          name="lastName"
          autoComplete="family-name"
          autoFocus
          disabled={isRegistering}
          value={formik.values.lastName}
          onChange={formik.handleChange}
          error={formik.touched.lastName && Boolean(formik.errors.lastName)}
          helperText={formik.touched.lastName && formik.errors.lastName}
        />
        <TextField
          margin="normal"
          required
          fullWidth
          id="firstName"
          label={t("auth.register.form.firstName.label")}
          name="firstName"
          autoComplete="given-name"
          disabled={isRegistering}
          value={formik.values.firstName}
          onChange={formik.handleChange}
          error={formik.touched.firstName && Boolean(formik.errors.firstName)}
          helperText={formik.touched.firstName && formik.errors.firstName}
        />
        <FormControl component="fieldset" margin="normal">
          <FormLabel component="legend">
            {t("auth.register.form.gender.label")}
          </FormLabel>
          <RadioGroup
            row
            aria-label="gender"
            name="gender"
            value={formik.values.gender}
            onChange={formik.handleChange}
          >
            {genders.map((gender) => (
              <FormControlLabel
                control={<Radio />}
                key={gender.value}
                disabled={isRegistering}
                label={t(gender.label)}
                value={gender.value}
              />
            ))}
          </RadioGroup>
        </FormControl>
        <TextField
          margin="normal"
          required
          fullWidth
          id="email"
          label={t("auth.register.form.email.label")}
          name="email"
          autoComplete="email"
          disabled={isRegistering}
          value={formik.values.email}
          onChange={formik.handleChange}
          error={formik.touched.email && Boolean(formik.errors.email)}
          helperText={formik.touched.email && formik.errors.email}
        />
        <LoadingButton
          type="submit"
          fullWidth
          variant="contained"
          color="primary"
          disabled={isRegistering}
          loading={isRegistering}
          sx={{ mt: 2 }}
        >
          {t("auth.register.submit")}
        </LoadingButton>
        <Button
          component={Link}
          to={`/${process.env.PUBLIC_URL}/login`}
          color="primary"
          fullWidth
          sx={{ mt: 2 }}
        >
          {t("auth.register.back")}
        </Button>
      </Box>
    </BoxedLayout>
  );
};

export default Register;


================================================
FILE: src/auth/types/userInfo.ts
================================================
export interface UserInfo {
  id: string;
  avatar?: string;
  email: string;
  firstName: string;
  job: string;
  lastName: string;
  progress: number;
  role: string;
}


================================================
FILE: src/calendar/components/Calendar.tsx
================================================
import FullCalendar, {
  CalendarOptions,
  EventClickArg,
} from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import {
  alpha,
  experimentalStyled as styled,
  useTheme,
} from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import ArrowLeftIcon from "@material-ui/icons/ArrowLeft";
import ArrowRightIcon from "@material-ui/icons/ArrowRight";
import EventIcon from "@material-ui/icons/Event";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Event, eventColors } from "../types/event";

const StyledWrapper = styled("div")(
  ({ theme }) => `
  .fc-theme-standard .fc-scrollgrid {
    border-color: ${theme.palette.divider};
  }

  .fc th {
    border-right: none;
    border-left: none;
    padding: 10px 0;
  }

  .fc-theme-standard .fc-scrollgrid {
    border-right: none;
    border-left: none;
    border-bottom: none;
  }

  .fc-theme-standard td, .fc-theme-standard th {
    border-right: none;
  }

  .fc-theme-standard td, .fc-theme-standard th {
    border-color: ${theme.palette.divider};
  }

  .fc .fc-daygrid-day-number {
    color: ${theme.palette.text.secondary};
    font-size: 14px;
    font-weight: ${theme.typography.fontWeightBold};
    padding: 12px;
  }

  .fc .fc-daygrid-day.fc-day-today {
    background-color: ${alpha(theme.palette.primary.main, 0.1)};
  }
`
);

type CalendarProps = {
  events: Event[];
  onEventClick: (event?: Event) => void;
} & CalendarOptions;

const Calendar = ({
  events,
  onEventClick,
  ...calendarProps
}: CalendarProps) => {
  const theme = useTheme();
  const { i18n, t } = useTranslation();

  const [viewTitle, setViewTitle] = useState("");
  const [calendarRef, setCalendarRef] = useState<FullCalendar | null>(null);

  const onCalendarRefSet = useCallback((ref) => {
    if (ref !== null) {
      setCalendarRef(ref);
    }
  }, []);

  const handleEventClick = (arg: EventClickArg) => {
    if (onEventClick) {
      const event = events.find((e) => e.id === arg.event.id);
      onEventClick(event);
    }
  };

  const handleNext = () => {
    if (calendarRef) {
      calendarRef.getApi().next();
      setViewTitle(calendarRef.getApi().getCurrentData().viewTitle);
    }
  };

  const handlePrev = () => {
    if (calendarRef) {
      calendarRef.getApi().prev();
      setViewTitle(calendarRef.getApi().getCurrentData().viewTitle);
    }
  };

  const handleToday = () => {
    if (calendarRef) {
      calendarRef.getApi().today();
      setViewTitle(calendarRef.getApi().getCurrentData().viewTitle);
    }
  };

  useEffect(() => {
    if (calendarRef) {
      setViewTitle(calendarRef.getApi().getCurrentData().viewTitle);
    }
  }, [calendarRef, i18n.language]);

  const eventSource = useMemo(() => {
    return events.map((event: Event) => {
      if (event.color && eventColors.includes(event.color)) {
        return { ...event, color: theme.palette[event.color].main };
      }
      return event;
    });
  }, [events, theme]);

  return (
    <React.Fragment>
      {/* Start - Custom Header Bar */}
      <Box
        sx={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          px: 3,
          py: 2,
        }}
      >
        <Typography sx={{ display: "inline-flex" }} variant="h5">
          <EventIcon sx={{ mr: 2 }} />
          {viewTitle}
        </Typography>
        <Box>
          <IconButton
            aria-label="previous"
            component="span"
            onClick={handlePrev}
          >
            <ArrowLeftIcon />
          </IconButton>
          <Button onClick={handleToday}>{t("common.today")}</Button>
          <IconButton
            aria-label="next"
            component="span"
            edge="end"
            onClick={handleNext}
          >
            <ArrowRightIcon />
          </IconButton>
        </Box>
      </Box>
      {/* End - Custom Header Bar */}
      <StyledWrapper>
        <FullCalendar
          headerToolbar={false}
          plugins={[dayGridPlugin]}
          initialView="dayGridMonth"
          locale={i18n.language}
          ref={onCalendarRefSet}
          events={eventSource}
          eventClick={handleEventClick}
          eventTimeFormat={{
            hour: "numeric",
            minute: "2-digit",
            meridiem: false,
          }}
          {...calendarProps}
        />
      </StyledWrapper>
    </React.Fragment>
  );
};

export default Calendar;


================================================
FILE: src/calendar/components/EventDialog.tsx
================================================
import Box from "@material-ui/core/Box";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import IconButton from "@material-ui/core/IconButton";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import TextField from "@material-ui/core/TextField";
import DeleteIcon from "@material-ui/icons/Delete";
import LoadingButton from "@material-ui/lab/LoadingButton";
import MobileDateTimePicker from "@material-ui/lab/MobileDateTimePicker";
import { getTime } from "date-fns";
import { useFormik } from "formik";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
import { Event, EventColor, eventColors } from "../types/event";

type EventDialogProps = {
  onAdd: (event: Partial<Event>) => void;
  onClose: () => void;
  onDelete: (eventId: string) => void;
  onUpdate: (event: Event) => void;
  open: boolean;
  processing: boolean;
  event?: Event;
};

type EventFormValues = {
  title: string;
  description?: string;
  start: Date;
  end: Date;
  color?: EventColor;
};

const EventDialog = ({
  onAdd,
  onClose,
  onDelete,
  onUpdate,
  open,
  processing,
  event,
}: EventDialogProps) => {
  const { t } = useTranslation();

  const editMode = Boolean(event && event.id);

  const convertFormValues = (values: EventFormValues): Partial<Event> => {
    return {
      ...values,
      start: getTime(values.start),
      end: getTime(values.end),
    };
  };

  const handleSubmit = (values: EventFormValues) => {
    const newEvent = convertFormValues(values);
    if (event && event.id) {
      onUpdate({ ...newEvent, id: event.id } as Event);
    } else {
      onAdd(newEvent);
    }
  };

  const formik = useFormik({
    initialValues: {
      title: event ? event.title : "",
      description: event ? event.description : undefined,
      start: event ? new Date(event.start) : new Date(),
      end: event ? new Date(event.end) : new Date(),
      color: event ? event.color : "primary",
    },
    validationSchema: Yup.object({
      title: Yup.string()
        .max(30, t("common.validations.max", { size: 30 }))
        .required(t("common.validations.required")),
      description: Yup.string().max(
        100,
        t("common.validations.max", { size: 100 })
      ),
      start: Yup.date().required(t("common.validations.required")),
      end: Yup.date().required(t("common.validations.required")),
      color: Yup.string(),
    }),
    onSubmit: handleSubmit,
  });

  return (
    <Dialog open={open} onClose={onClose} aria-labelledby="event-dialog-title">
      <form onSubmit={formik.handleSubmit} noValidate>
        <DialogTitle id="event-dialog-title">
          {editMode
            ? t("calendar.modal.edit.title")
            : t("calendar.modal.add.title")}
        </DialogTitle>
        <DialogContent>
          <TextField
            margin="normal"
            required
            fullWidth
            id="title"
            label={t("calendar.form.title.label")}
            name="title"
            autoFocus
            disabled={processing}
            value={formik.values.title}
            onChange={formik.handleChange}
            error={formik.touched.title && Boolean(formik.errors.title)}
            helperText={formik.touched.title && formik.errors.title}
          />
          <TextField
            margin="normal"
            fullWidth
            id="description"
            label={t("calendar.form.description.label")}
            name="description"
            disabled={processing}
            value={formik.values.description}
            onChange={formik.handleChange}
            error={
              formik.touched.description && Boolean(formik.errors.description)
            }
            helperText={formik.touched.description && formik.errors.description}
          />
          <MobileDateTimePicker
            label={t("calendar.form.start.label")}
            inputFormat="dd/MM/yyyy H:mm"
            value={formik.values.start}
            onChange={(date: Date | null) =>
              formik.setFieldValue("start", date)
            }
            renderInput={(params) => (
              <TextField
                {...params}
                id="start"
                disabled={processing}
                fullWidth
                margin="normal"
                name="start"
              />
            )}
          />
          <MobileDateTimePicker
            label={t("calendar.form.end.label")}
            inputFormat="dd/MM/yyyy H:mm"
            value={formik.values.end}
            onChange={(date: Date | null) => formik.setFieldValue("end", date)}
            renderInput={(params) => (
              <TextField
                {...params}
                id="end"
                disabled={processing}
                fullWidth
                margin="normal"
                name="end"
              />
            )}
          />
          <FormControl component="fieldset" margin="normal">
            <FormLabel component="legend">
              {t("calendar.form.color.label")}
            </FormLabel>
            <RadioGroup
              row
              aria-label="color"
              name="color"
              value={formik.values.color}
              onChange={formik.handleChange}
            >
              {eventColors.map((color) => (
                <Radio
                  key={color}
                  disabled={processing}
                  sx={{
                    color: `${color}.main`,
                    "&.Mui-checked": {
                      color: `${color}.main`,
                    },
                  }}
                  value={color}
                />
              ))}
            </RadioGroup>
          </FormControl>
        </DialogContent>
        <DialogActions>
          {event && event.id && (
            <IconButton
              aria-label="delete event"
              onClick={() => onDelete(event.id)}
              disabled={processing}
            >
              <DeleteIcon />
            </IconButton>
          )}
          <Box sx={{ flexGrow: 1 }} />
          <Button onClick={onClose}>{t("common.cancel")}</Button>
          <LoadingButton loading={processing} type="submit" variant="contained">
            {editMode
              ? t("calendar.modal.edit.action")
              : t("calendar.modal.add.action")}
          </LoadingButton>
        </DialogActions>
      </form>
    </Dialog>
  );
};

export default EventDialog;


================================================
FILE: src/calendar/hooks/useAddEvent.ts
================================================
import axios from "axios";
import { useMutation, useQueryClient } from "react-query";
import { addOne } from "../../core/utils/crudUtils";
import { Event } from "../types/event";

const addEvent = async (event: Event): Promise<Event> => {
  const { data } = await axios.post("/api/events", event);
  return data;
};

export function useAddEvent() {
  const queryClient = useQueryClient();

  const { isLoading, mutateAsync } = useMutation(addEvent, {
    onSuccess: (event: Event) => {
      queryClient.setQueryData<Event[]>(["events"], (oldEvents) =>
        addOne(oldEvents, event)
      );
    },
  });

  return { isAdding: isLoading, addEvent: mutateAsync };
}


================================================
FILE: src/calendar/hooks/useDeleteEvent.ts
================================================
import axios from "axios";
import { useMutation, useQueryClient } from "react-query";
import { removeOne } from "../../core/utils/crudUtils";
import { Event } from "../types/event";

const deleteEvent = async (eventId: string): Promise<string> => {
  const { data } = await axios.delete("/api/events", { data: eventId });
  return data;
};

export function useDeleteEvent() {
  const queryClient = useQueryClient();

  const { isLoading, mutateAsync } = useMutation(deleteEvent, {
    onSuccess: (eventId: string) => {
      queryClient.setQueryData<Event[]>(["events"], (oldEvents) =>
        removeOne(oldEvents, eventId)
      );
    },
  });

  return { isDeleting: isLoading, deleteEvent: mutateAsync };
}


================================================
FILE: src/calendar/hooks/useEvents.ts
================================================
import axios from "axios";
import { useQuery } from "react-query";
import { Event } from "../types/event";

const fetchEvents = async (): Promise<Event[]> => {
  const { data } = await axios.get("/api/events");
  return data;
};

export function useEvents() {
  return useQuery("events", () => fetchEvents());
}


================================================
FILE: src/calendar/hooks/useUpdateEvent.ts
================================================
import axios from "axios";
import { useMutation, useQueryClient } from "react-query";
import { updateOne } from "../../core/utils/crudUtils";
import { Event } from "../types/event";

const updateEvent = async (event: Event): Promise<Event> => {
  const { data } = await axios.put("/api/events", event);
  return data;
};

export function useUpdateEvent() {
  const queryClient = useQueryClient();

  const { isLoading, mutateAsync } = useMutation(updateEvent, {
    onSuccess: (event: Event) => {
      queryClient.setQueryData<Event[]>(["events"], (oldEvents) =>
        updateOne(oldEvents, event)
      );
    },
  });

  return { isUpdating: isLoading, updateEvent: mutateAsync };
}


================================================
FILE: src/calendar/pages/CalendarApp.tsx
================================================
import Card from "@material-ui/core/Card";
import Fab from "@material-ui/core/Fab";
import AddIcon from "@material-ui/icons/Add";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import AdminAppBar from "../../admin/components/AdminAppBar";
import AdminToolbar from "../../admin/components/AdminToolbar";
import { useAddEvent } from "../../calendar/hooks/useAddEvent";
import ConfirmDialog from "../../core/components/ConfirmDialog";
import { useSnackbar } from "../../core/contexts/SnackbarProvider";
import Calendar from "../components/Calendar";
import EventDialog from "../components/EventDialog";
import { useDeleteEvent } from "../hooks/useDeleteEvent";
import { useEvents } from "../hooks/useEvents";
import { useUpdateEvent } from "../hooks/useUpdateEvent";
import { Event } from "../types/event";

const CalendarApp = () => {
  const snackbar = useSnackbar();
  const { t } = useTranslation();

  const [eventDeleted, setEventDeleted] =
    useState<string | undefined>(undefined);
  const [eventUpdated, setEventUpdated] =
    useState<Event | undefined>(undefined);
  const [openConfirmDeleteDialog, setOpenConfirmDeleteDialog] = useState(false);
  const [openEventDialog, setOpenEventDialog] = useState(false);

  const { addEvent, isAdding } = useAddEvent();
  const { deleteEvent, isDeleting } = useDeleteEvent();
  const { data } = useEvents();
  const { isUpdating, updateEvent } = useUpdateEvent();

  const processing = isAdding || isDeleting || isUpdating;

  const handleAddEvent = async (event: Partial<Event>) => {
    addEvent(event as Event)
      .then(() => {
        snackbar.success(
          t("calendar.notifications.addSuccess", { event: event.title })
        );
        setOpenEventDialog(false);
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  const handleDeleteEvent = async () => {
    if (eventDeleted) {
      deleteEvent(eventDeleted)
        .then(() => {
          snackbar.success(t("calendar.notifications.deleteSuccess"));
          setOpenConfirmDeleteDialog(false);
          setOpenEventDialog(false);
        })
        .catch(() => {
          snackbar.error(t("common.errors.unexpected.subTitle"));
        });
    }
  };

  const handleUpdateEvent = async (event: Event) => {
    updateEvent(event)
      .then(() => {
        snackbar.success(
          t("calendar.notifications.updateSuccess", { event: event.title })
        );
        setOpenEventDialog(false);
      })
      .catch(() => {
        snackbar.error(t("common.errors.unexpected.subTitle"));
      });
  };

  const handleCloseConfirmDeleteDialog = () => {
    setOpenConfirmDeleteDialog(false);
  };

  const handleCloseEventDialog = () => {
    setEventUpdated(undefined);
    setOpenEventDialog(false);
  };

  const handleOpenConfirmDeleteDialog = (eventId: string) => {
    setEventDeleted(eventId);
    setOpenConfirmDeleteDialog(true);
  };

  const handleOpenEventDialog = (event?: Event) => {
    setEventUpdated(event);
    setOpenEventDialog(true);
  };

  return (
    <React.Fragment>
      <AdminAppBar>
        <AdminToolbar title={t("calendar.title")}>
          <Fab
            aria-label="add event"
            color="primary"
            onClick={() => handleOpenEventDialog()}
            size="small"
          >
            <AddIcon />
          </Fab>
        </AdminToolbar>
      </AdminAppBar>
      <Card>
        <Calendar
          contentHeight={720}
          events={data || []}
          onEventClick={handleOpenEventDialog}
        />
      </Card>
      <ConfirmDialog
        description={t("calendar.confirmations.delete")}
        pending={processing}
        onClose={handleCloseConfirmDeleteDialog}
        onConfirm={handleDeleteEvent}
        open={openConfirmDeleteDialog}
        title={t("common.confirmation")}
      />
      {openEventDialog && (
        <EventDialog
          onAdd={handleAddEvent}
          onClose={handleCloseEventDialog}
          onDelete={handleOpenConfirmDeleteDialog}
          onUpdate={handleUpdateEvent}
          open={openEventDialog}
          processing={processing}
          event={eventUpdated}
        />
      )}
    </React.Fragment>
  );
};

export default CalendarApp;


================================================
FILE: src/calendar/types/event.ts
================================================
export interface Event {
  id: string;
  title: string;
  description?: string;
  start: number;
  end: number;
  color?: EventColor;
}

export const eventColors = ["primary", "warning", "error", "success"] as const;

export type EventColor = typeof eventColors[number];


================================================
FILE: src/core/components/BoxedLayout.tsx
================================================
import AppBar from "@material-ui/core/AppBar";
import Box from "@material-ui/core/Box";
import Container from "@material-ui/core/Container";
import GlobalStyles from "@material-ui/core/GlobalStyles";
import IconButton from "@material-ui/core/IconButton";
import useTheme from "@material-ui/core/styles/useTheme";
import Toolbar from "@material-ui/core/Toolbar";
import SettingsIcon from "@material-ui/icons/Settings";
import React, { useState } from "react";
import Logo from "./Logo";
import SettingsDrawer from "./SettingsDrawer";

type BoxedLayoutProps = {
  children: React.ReactNode;
};

const BoxedLayout = ({ children }: BoxedLayoutProps) => {
  const theme = useTheme();
  const [settingsOpen, setSettingsOpen] = useState(false);

  const handleSettingsToggle = () => {
    setSettingsOpen(!settingsOpen);
  };

  return (
    <React.Fragment>
      <GlobalStyles
        styles={{ body: { backgroundColor: theme.palette.background.paper } }}
      />
      <AppBar color="transparent" position="relative">
        <Toolbar>
          <Box sx={{ flexGrow: 1 }} />
          <IconButton
            aria-label="settings"
            component="span"
            onClick={handleSettingsToggle}
          >
            <SettingsIcon />
          </IconButton>
        </Toolbar>
      </AppBar>
      <Container component="main" maxWidth="xs" sx={{ mt: 6 }}>
        <Box
          sx={{
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
          }}
        >
          <Logo sx={{ mb: 2 }} />
          {children}
          <Box>
            <SettingsDrawer
              onDrawerToggle={handleSettingsToggle}
              open={settingsOpen}
            />
          </Box>
        </Box>
      </Container>
    </React.Fragment>
  );
};

export default BoxedLayout;


================================================
FILE: src/core/components/ConfirmDialog.tsx
================================================
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import LoadingButton from "@material-ui/lab/LoadingButton";
import { useTranslation } from "react-i18next";
import { ReactComponent as ConfirmSvg } from "../assets/confirm.svg";
import SvgContainer from "./SvgContainer";

type ConfirmDialogProps = {
  description?: string;
  onClose: () => void;
  onConfirm: () => void;
  open: boolean;
  pending: boolean;
  title: string;
};

const ConfirmDialog = ({
  description,
  onClose,
  onConfirm,
  open,
  pending,
  title,
}: ConfirmDialogProps) => {
  const { t } = useTranslation();

  return (
    <Dialog
      open={open}
      onClose={onClose}
      aria-labelledby="confirm-dialog-title"
      aria-describedby="confirm-dialog-description"
    >
      <DialogContent sx={{ textAlign: "center" }}>
        <SvgContainer>
          <ConfirmSvg style={{ maxWidth: 280, width: "100%" }} />
        </SvgContainer>
        <DialogTitle id="confirm-dialog-title" sx={{ pb: 1, pt: 0 }}>
          {title}
        </DialogTitle>
        {description && (
          <DialogContentText id="confirm-dialog-description">
            {description}
          </DialogContentText>
        )}
      </DialogContent>
      <DialogActions>
        <Button onClick={onClose}>{t("common.cancel")}</Button>
        <LoadingButton
          autoFocus
          onClick={onConfirm}
          loading={pending}
          variant="contained"
        >
          {t("common.confirm")}
        </LoadingButton>
      </DialogActions>
    </Dialog>
  );
};

export default ConfirmDialog;


================================================
FILE: src/core/components/Empty.tsx
================================================
import { ReactComponent as EmptySvg } from "../assets/empty.svg";
import Result from "./Result";

type EmptyProps = {
  message?: string;
  title: string;
};

const Empty = ({ message, title }: EmptyProps) => {
  return <Result image={<EmptySvg />} subTitle={message} title={title} />;
};

export default Empty;


================================================
FILE: src/core/components/Footer.tsx
================================================
import Box from "@material-ui/core/Box";
import Link from "@material-ui/core/Link";
import Typography from "@material-ui/core/Typography";
import { Link as RouterLink } from "react-router-dom";

const Footer = () => {
  return (
    <Box sx={{ p: 6 }} component="footer">
      <Typography variant="body2" color="text.secondary" align="center">
        {"© "}
        <Link
          color="inherit"
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/`}
        >
          {process.env.REACT_APP_NAME}
        </Link>{" "}
        {new Date().getFullYear()}
        {"."}
      </Typography>
    </Box>
  );
};

export default Footer;


================================================
FILE: src/core/components/Loader.tsx
================================================
import { useTheme } from "@material-ui/core/styles";
import Logo from "./Logo";

const Loader = () => {
  const theme = useTheme();
  return (
    <Logo
      size={100}
      sx={{
        "@keyframes pulse": {
          "0%": {
            opacity: 1,
          },
          "50%": {
            opacity: 0.4,
          },
          "100%": {
            opacity: 1,
          },
        },
        animation: "pulse 1.5s ease-in-out 0.5s infinite",
        color: theme.palette.mode === "light" ? "grey.300" : "grey.600",
        textAlign: "center",
        px: 3,
        py: 8,
      }}
    />
  );
};

export default Loader;


================================================
FILE: src/core/components/Logo.tsx
================================================
import Box, { BoxProps } from "@material-ui/core/Box";
import { ReactComponent as LogoSvg } from "../assets/logo.svg";

type LogoProps = {
  colored?: boolean;
  size?: number;
} & BoxProps;

const Logo = ({ colored = false, size = 40, ...boxProps }: LogoProps) => {
  return (
    <Box {...boxProps}>
      <LogoSvg height={size} width={size} />
    </Box>
  );
};

export default Logo;


================================================
FILE: src/core/components/PrivateRoute.tsx
================================================
import { Navigate, Route, RouteProps } from "react-router";
import { useAuth } from "../../auth/contexts/AuthProvider";

type PrivateRouteProps = {
  roles?: string[];
} & RouteProps;

const PrivateRoute = ({
  children,
  roles,
  ...routeProps
}: PrivateRouteProps) => {
  const { hasRole, userInfo } = useAuth();

  if (userInfo) {
    if (!hasRole(roles)) {
      return <Navigate to={`/${process.env.PUBLIC_URL}/403`} />;
    }
    return <Route {...routeProps} />;
  } else {
    return <Navigate to={`/${process.env.PUBLIC_URL}/login`} />;
  }
};

export default PrivateRoute;


================================================
FILE: src/core/components/QueryWrapper.tsx
================================================
import Button from "@material-ui/core/Button";
import React from "react";
import { ErrorBoundary } from "react-error-boundary";
import { useTranslation } from "react-i18next";
import { useQueryErrorResetBoundary } from "react-query";
import Loader from "./Loader";
import Result from "./Result";

type QueryWrapperProps = {
  children: React.ReactNode;
};

const QueryWrapper = ({ children }: QueryWrapperProps) => {
  const { reset } = useQueryErrorResetBoundary();
  const { t } = useTranslation();

  return (
    <ErrorBoundary
      onReset={reset}
      fallbackRender={({ resetErrorBoundary }) => (
        <Result
          extra={
            <Button onClick={() => resetErrorBoundary()} variant="contained">
              {t("common.retry")}
            </Button>
          }
          status="error"
          subTitle={t("common.errors.unexpected.subTitle")}
          title={t("common.errors.unexpected.title")}
        />
      )}
    >
      <React.Suspense fallback={<Loader />}>{children}</React.Suspense>
    </ErrorBoundary>
  );
};

export default QueryWrapper;


================================================
FILE: src/core/components/Result.tsx
================================================
import Box from "@material-ui/core/Box";
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import React from "react";
import { ReactComponent as ErrorSvg } from "../assets/error.svg";
import { ReactComponent as SuccessSvg } from "../assets/success.svg";
import SvgContainer from "./SvgContainer";

type ResultImageProps = {
  customImage?: React.ReactNode;
  status?: "error" | "success";
};

const ResultImage = ({ customImage, status }: ResultImageProps) => {
  let image = customImage;

  if (!image) {
    if (status === "error") {
      image = <ErrorSvg />;
    } else if (status === "success") {
      image = <SuccessSvg />;
    }
  }

  return image ? <Box marginBottom={3}>{image}</Box> : null;
};

type ResultProps = {
  extra?: React.ReactNode;
  image?: React.ReactNode;
  maxWidth?: "xs" | "sm";
  status?: "error" | "success";
  subTitle?: string;
  title: string;
};

const Result = ({
  extra,
  image,
  maxWidth = "xs",
  status,
  subTitle,
  title,
}: ResultProps) => {
  return (
    <Container maxWidth={maxWidth}>
      <Box sx={{ textAlign: "center", px: 3, py: 8 }}>
        <SvgContainer>
          <ResultImage customImage={image} status={status} />
        </SvgContainer>
        <Typography gutterBottom variant="h5">
          {title}
        </Typography>
        {subTitle && <Typography variant="body2">{subTitle}</Typography>}
        {extra && <Box sx={{ mt: 4, textAlign: "center" }}>{extra}</Box>}
      </Box>
    </Container>
  );
};

export default Result;


================================================
FILE: src/core/components/SelectToolbar.tsx
================================================
import Box from "@material-ui/core/Box";
import Fab from "@material-ui/core/Fab";
import Toolbar from "@material-ui/core/Toolbar";
import Tooltip from "@material-ui/core/Tooltip";
import CloseIcon from "@material-ui/icons/Close";
import DeleteIcon from "@material-ui/icons/Delete";
import { useTranslation } from "react-i18next";

interface SelectToolbarProps {
  onCancel: () => void;
  onDelete: (userIds: string[]) => void;
  processing: boolean;
  selected: string[];
}

const SelectToolbar = ({
  onCancel,
  onDelete,
  processing,
  selected,
}: SelectToolbarProps) => {
  const { t } = useTranslation();

  const numSelected = selected.length;

  return (
    <Toolbar sx={{ ml: 1, px: { xs: 3, sm: 6 } }}>
      <Fab color="secondary" onClick={onCancel} variant="extended">
        <CloseIcon sx={{ mr: 1 }} />
        {numSelected} {t("common.selected")}
      </Fab>
      <Box sx={{ flexGrow: 1 }} />

      {numSelected > 0 && (
        <Tooltip title={t("common.delete") as string}>
          <Fab
            color="secondary"
            disabled={processing}
            onClick={() => onDelete(selected)}
          >
            <DeleteIcon />
          </Fab>
        </Tooltip>
      )}
    </Toolbar>
  );
};

export default SelectToolbar;


================================================
FILE: src/core/components/SettingsDrawer.tsx
================================================
import Box from "@material-ui/core/Box";
import Drawer from "@material-ui/core/Drawer";
import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import IconButton from "@material-ui/core/IconButton";
import Radio from "@material-ui/core/Radio";
import RadioGroup from "@material-ui/core/RadioGroup";
import ToggleButton from "@material-ui/core/ToggleButton";
import ToggleButtonGroup from "@material-ui/core/ToggleButtonGroup";
import Typography from "@material-ui/core/Typography";
import CloseIcon from "@material-ui/icons/Close";
import { useTranslation } from "react-i18next";
import { drawerWidth } from "../config/layout";
import { useSettings } from "../contexts/SettingsProvider";

type SettingsDrawerProps = {
  onDrawerToggle: () => void;
  open: boolean;
};

const SettingsDrawer = ({ onDrawerToggle, open }: SettingsDrawerProps) => {
  const {
    changeCollapsed,
    changeDirection,
    changeMode,
    collapsed,
    direction,
    mode,
  } = useSettings();
  const { i18n, t } = useTranslation();

  const handleDirectionChange = (_: any, direction: "ltr" | "rtl") => {
    changeDirection(direction);
  };

  const handleLanguageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    i18n.changeLanguage((event.target as HTMLInputElement).value);
  };

  const handleModeChange = (_: any, mode: string) => {
    changeMode(mode);
  };

  const handleSidebarChange = (_: any, collapsed: boolean) => {
    changeCollapsed(collapsed);
  };

  return (
    <Drawer
      anchor="left"
      open={open}
      onClose={onDrawerToggle}
      sx={{
        "& .MuiDrawer-paper": {
          width: drawerWidth,
        },
      }}
      variant="temporary"
    >
      <Box
        sx={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          p: 2,
        }}
      >
        <Typography variant="h5">{t("settings.drawer.title")}</Typography>
        <IconButton color="inherit" onClick={onDrawerToggle} edge="end">
          <CloseIcon />
        </IconButton>
      </Box>
      <Box sx={{ pl: 2, pr: 2 }}>
        <Typography
          gutterBottom
          id="settings-language"
          marginTop={3}
          variant="h6"
        >
          {t("settings.drawer.language.label")}
        </Typography>
        <FormControl>
          <RadioGroup
            aria-label="language"
            name="language-radio-group"
            onChange={handleLanguageChange}
            value={i18n.language}
          >
            <FormControlLabel
              value="en"
              control={<Radio />}
              label={t("settings.drawer.language.options.en")}
            />
            <FormControlLabel
              value="fr"
              control={<Radio />}
              label={t("settings.drawer.language.options.fr")}
            />
          </RadioGroup>
        </FormControl>
        <Typography gutterBottom id="settings-mode" marginTop={3} variant="h6">
          {t("settings.drawer.mode.label")}
        </Typography>
        <ToggleButtonGroup
          color="primary"
          value={mode}
          exclusive
          fullWidth
          onChange={handleModeChange}
        >
          <ToggleButton value="light">
            {t("settings.drawer.mode.options.light")}
          </ToggleButton>
          <ToggleButton value="dark">
            {t("settings.drawer.mode.options.dark")}
          </ToggleButton>
        </ToggleButtonGroup>
        <Typography gutterBottom id="settings-mode" marginTop={3} variant="h6">
          {t("settings.drawer.direction.label")}
        </Typography>
        <ToggleButtonGroup
          color="primary"
          value={direction}
          exclusive
          fullWidth
          onChange={handleDirectionChange}
        >
          <ToggleButton value="ltr">
            {t("settings.drawer.direction.options.ltr")}
          </ToggleButton>
          <ToggleButton value="rtl">
            {t("settings.drawer.direction.options.rtl")}
          </ToggleButton>
        </ToggleButtonGroup>
        <Typography
          gutterBottom
          id="settings-sidebar"
          marginTop={3}
          variant="h6"
        >
          {t("settings.drawer.sidebar.label")}
        </Typography>
        <ToggleButtonGroup
          color="primary"
          value={collapsed}
          exclusive
          fullWidth
          onChange={handleSidebarChange}
        >
          <ToggleButton value={true}>
            {t("settings.drawer.sidebar.options.collapsed")}
          </ToggleButton>
          <ToggleButton value={false}>
            {t("settings.drawer.sidebar.options.full")}
          </ToggleButton>
        </ToggleButtonGroup>
      </Box>
    </Drawer>
  );
};

export default SettingsDrawer;


================================================
FILE: src/core/components/SvgContainer.tsx
================================================
import Box from "@material-ui/core/Box";
import { useTheme } from "@material-ui/core/styles";
import React from "react";

type SvgContainerProps = {
  children: React.ReactNode;
};

const SvgContainer = ({ children }: SvgContainerProps) => {
  const theme = useTheme();
  return (
    <Box
      sx={{
        svg: { height: "100%", width: "100%" },
        ".fill-primary": { fill: theme.palette.primary.light },
        ".fill-secondary": { fill: theme.palette.secondary.light },
        ".fill-error": { fill: theme.palette.error.light },
        ".fill-success": { fill: theme.palette.success.light },
        ".fill-warning": { fill: theme.palette.warning.light },
        ".fill-paper": { fill: theme.palette.background.paper },
      }}
    >
      {children}
    </Box>
  );
};

export default SvgContainer;


================================================
FILE: src/core/config/i18n.ts
================================================
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-xhr-backend";
import { initReactI18next } from "react-i18next";

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    backend: {
      loadPath: `${process.env.PUBLIC_URL}/locales/{{lng}}/translation.json`,
    },
    fallbackLng: "en",
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    supportedLngs: ["en", "fr"],
  });


================================================
FILE: src/core/config/layout.ts
================================================
export const drawerCollapsedWidth = 104;
export const drawerWidth = 280;


================================================
FILE: src/core/contexts/SettingsProvider.tsx
================================================
import { ThemeProvider as MuiThemeProvider } from "@material-ui/core";
import CssBaseline from "@material-ui/core/CssBaseline";
import AdapterDateFns from "@material-ui/lab/AdapterDateFns";
import LocalizationProvider from "@material-ui/lab/LocalizationProvider";
import React, {
  createContext,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { useLocalStorage } from "../hooks/useLocalStorage";
import { createTheme } from "../theme";

interface SettingsContextInterface {
  collapsed: boolean;
  direction: string;
  mode: string;
  open: boolean;
  changeCollapsed: (collapsed: boolean) => void;
  changeDirection: (direction: "ltr" | "rtl") => void;
  changeMode: (mode: string) => void;
  toggleDrawer: () => void;
}

export const SettingsContext = createContext({} as SettingsContextInterface);

type SettingsProviderProps = {
  children: React.ReactNode;
};

const SettingsProvider = ({ children }: SettingsProviderProps) => {
  const [collapsed, setCollapsed] = useLocalStorage("sidebarcollapsed", false);
  const [direction, setDirection] = useLocalStorage("direction", "ltr");
  const [mode, setMode] = useLocalStorage("mode", "light");
  const [open, setOpen] = useState(false);

  useEffect(() => {
    document.body.dir = direction;
  }, [direction]);

  const theme = useMemo(
    () => createTheme(direction as "ltr" | "rtl", mode as "dark" | "light"),
    [direction, mode]
  );

  const changeCollapsed = (collapsed: boolean) => {
    if (typeof collapsed === "boolean") {
      setCollapsed(collapsed);
    }
  };

  const changeDirection = (direction: "ltr" | "rtl") => {
    if (direction) {
      setDirection(direction);
    }
  };

  const changeMode = (mode: string) => {
    if (mode) {
      setMode(mode);
    }
  };

  const toggleDrawer = () => {
    setOpen(!open);
  };

  return (
    <SettingsContext.Provider
      value={{
        collapsed,
        direction,
        mode,
        open,
        changeCollapsed,
        changeDirection,
        changeMode,
        toggleDrawer,
      }}
    >
      <MuiThemeProvider theme={theme}>
        <LocalizationProvider dateAdapter={AdapterDateFns}>
          <CssBaseline />
          {children}
        </LocalizationProvider>
      </MuiThemeProvider>
    </SettingsContext.Provider>
  );
};

export function useSettings() {
  return useContext(SettingsContext);
}

export default SettingsProvider;


================================================
FILE: src/core/contexts/SnackbarProvider.tsx
================================================
import Alert, { Color } from "@material-ui/core/Alert";
import AlertTitle from "@material-ui/core/AlertTitle";
import Snackbar from "@material-ui/core/Snackbar";
import React, { createContext, useContext, useState } from "react";
import { useTranslation } from "react-i18next";

interface SnackbarContextInterface {
  error: (newMessage: string) => void;
  success: (newMessage: string) => void;
}

export const SnackbarContext = createContext({} as SnackbarContextInterface);

type SnackbarProviderProps = {
  children: React.ReactNode;
};

const SnackbarProvider = ({ children }: SnackbarProviderProps) => {
  const { t } = useTranslation();
  const [open, setOpen] = useState(false);
  const [message, setMessage] = useState("");
  const [title, setTitle] = useState("");
  const [severity, setSeverity] = useState<Color | undefined>(undefined);

  const handleClose = (
    event: React.SyntheticEvent | React.MouseEvent,
    reason?: string
  ) => {
    if (reason === "clickaway") {
      return;
    }

    setOpen(false);
  };

  const error = (newMessage: string) => {
    setTitle(t("common.snackbar.error"));
    setMessage(newMessage);
    setSeverity("error");
    setOpen(true);
  };

  const success = (newMessage: string) => {
    setTitle(t("common.snackbar.success"));
    setMessage(newMessage);
    setSeverity("success");
    setOpen(true);
  };

  return (
    <SnackbarContext.Provider value={{ error, success }}>
      {children}
      <Snackbar
        key={message}
        anchorOrigin={{
          vertical: "bottom",
          horizontal: "right",
        }}
        open={open}
        autoHideDuration={6000}
        onClose={handleClose}
      >
        <Alert onClose={handleClose} severity={severity}>
          <AlertTitle>{title}</AlertTitle>
          {message}
        </Alert>
      </Snackbar>
    </SnackbarContext.Provider>
  );
};

export function useSnackbar() {
  return useContext(SnackbarContext);
}

export default SnackbarProvider;


================================================
FILE: src/core/hooks/useDateLocale.ts
================================================
import { Locale } from "date-fns";
import en from "date-fns/locale/en-US";
import fr from "date-fns/locale/fr";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

const locales: { [key: string]: Locale } = { en, fr };

export function useDateLocale(): Locale | undefined {
  const [locale, setLocale] = useState<Locale | undefined>(undefined);
  const { i18n } = useTranslation();

  useEffect(() => {
    setLocale(locales[i18n.language]);
  }, [i18n.language]);

  return locale;
}


================================================
FILE: src/core/hooks/useLocalStorage.ts
================================================
// https://usehooks.com/useLocalStorage/

import { useState } from "react";

export function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] {
  // State to store our value
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      // Get from local storage by key
      const item = window.localStorage.getItem(key);
      // Parse stored json or if none return initialValue
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      // If error also return initialValue
      console.log(error);
      return initialValue;
    }
  });
  // Return a wrapped version of useState's setter function that ...
  // ... persists the new value to localStorage.
  const setValue = (value: T) => {
    try {
      // Allow value to be a function so we have same API as useState
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      // Save state
      setStoredValue(valueToStore);
      // Save to local storage
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      // A more advanced implementation would handle the error case
      console.log(error);
    }
  };
  return [storedValue, setValue];
}


================================================
FILE: src/core/hooks/usePageTracking.ts
================================================
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";

const usePageTracking = () => {
  const location = useLocation();
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    const trackingId = process.env.REACT_APP_GA_TRACKING_ID;
    if (trackingId) {
      setInitialized(true);
    }
  }, []);

  useEffect(() => {
    if (initialized) {
      (window as any).gtag("send", "page_view", {
        page_location: window.location.href,
        page_path: window.location.pathname,
      });
    }
  }, [initialized, location]);
};

export default usePageTracking;


================================================
FILE: src/core/pages/Forbidden.tsx
================================================
import Button from "@material-ui/core/Button";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import { ReactComponent as ForbiddenSvg } from "../assets/403.svg";
import Result from "../components/Result";

const Forbidden = () => {
  const { t } = useTranslation();

  return (
    <Result
      extra={
        <Button
          color="secondary"
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/admin`}
          variant="contained"
        >
          {t("common.backHome")}
        </Button>
      }
      image={<ForbiddenSvg />}
      maxWidth="sm"
      subTitle={t("common.errors.forbidden.subTitle")}
      title={t("common.errors.unexpected.title")}
    />
  );
};

export default Forbidden;


================================================
FILE: src/core/pages/NotFound.tsx
================================================
import Button from "@material-ui/core/Button";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import Result from "../../core/components/Result";
import { ReactComponent as NotFoundSvg } from "../assets/404.svg";

const NotFound = () => {
  const { t } = useTranslation();

  return (
    <Result
      extra={
        <Button
          color="secondary"
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/admin`}
          variant="contained"
        >
          {t("common.backHome")}
        </Button>
      }
      image={<NotFoundSvg />}
      maxWidth="sm"
      subTitle={t("common.errors.notFound.subTitle")}
      title={t("common.errors.notFound.title")}
    />
  );
};

export default NotFound;


================================================
FILE: src/core/pages/UnderConstructions.tsx
================================================
import Button from "@material-ui/core/Button";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import Result from "../../core/components/Result";
import { ReactComponent as ConstructionsSvg } from "../assets/constructions.svg";

const UnderConstructions = () => {
  const { t } = useTranslation();

  return (
    <Result
      extra={
        <Button
          color="secondary"
          component={RouterLink}
          to={`/${process.env.PUBLIC_URL}/admin`}
          variant="contained"
        >
          {t("common.backHome")}
        </Button>
      }
      image={<ConstructionsSvg />}
      maxWidth="sm"
      subTitle={t("common.errors.underConstructions.subTitle")}
      title={t("common.errors.underConstructions.title")}
    />
  );
};

export default UnderConstructions;


================================================
FILE: src/core/theme/components.tsx
================================================
import { Theme } from "@material-ui/core";
import CheckCircle from "@material-ui/icons/CheckCircle";
import RadioButtonUnchecked from "@material-ui/icons/RadioButtonUnchecked";
import RemoveCircle from "@material-ui/icons/RemoveCircle";

export const createThemeComponents = (theme: Theme) => ({
  MuiAccordion: {
    styleOverrides: {
      root: {
        borderRadius: theme.shape.borderRadius,
        marginBottom: theme.spacing(3),
        "&.Mui-expanded:last-of-type": {
          marginBottom: theme.spacing(3),
        },
        "&:before": {
          content: "none",
        },
      },
    },
  },
  MuiAccordionDetails: {
    styleOverrides: {
      root: {
        padding: theme.spacing(1, 3, 3),
      },
    },
  },
  MuiAccordionSummary: {
    styleOverrides: {
      root: {
        padding: theme.spacing(3),
      },
      content: {
        margin: 0,
      },
    },
  },
  MuiAppBar: {
    defaultProps: {
      elevation: 0,
    },
    styleOverrides: {
      root: {
        "&.MuiAppBar-colorDefault": {
          backgroundColor: theme.palette.background.default,
          color: theme.palette.text.primary,
        },
      },
    },
  },
  MuiAvatar: {
    styleOverrides: {
      root: {
        color: "inherit",
        backgroundColor: theme.palette.background.default,
      },
    },
  },
  MuiButton: {
    defaultProps: {
      disableElevation: true,
    },
    styleOverrides: {
      root: {
        padding: "16px 24px",
        textTransform: "none" as any,
      },
      label: {
        fontWeight: theme.typography.fontWeightMedium,
      },
      text: {
        padding: "16px 16px",
      },
    },
  },
  MuiButtonBase: {
    defaultProps: {
      disableRipple: true, // No more ripple, on the whole application
    },
  },
  MuiCardActions: {
    styleOverrides: {
      root: {
        justifyContent: "flex-end",
        padding: "0 24px 24px 24px",
      },
    },
  },
  MuiCardContent: {
    styleOverrides: {
      root: {
        padding: theme.spacing(3),
      },
    },
  },
  MuiCardHeader: {
    styleOverrides: {
      root: {
        padding: "24px 24px 0 24px",
      },
    },
  },
  MuiCheckbox: {
    defaultProps: {
      checkedIcon: <CheckCircle />,
      indeterminateIcon: <RemoveCircle />,
      icon: <RadioButtonUnchecked />,
    },
  },
  MuiChip: {
    styleOverrides: {
      label: {
        fontWeight: theme.typography.fontWeightMedium,
      },
    },
  },
  MuiDialogActions: {
    styleOverrides: {
      root: {
        padding: 24,
      },
    },
  },
  MuiDialogTitle: {
    styleOverrides: {
      root: {
        padding: 24,
        "& .MuiTypography-root": {
          fontSize: "1.25rem",
        },
      },
    },
  },
  MuiDrawer: {
    styleOverrides: {
      paper: {
        border: "none; !important",
      },
    },
  },
  MuiFab: {
    styleOverrides: {
      root: {
        boxShadow: "none",
        lineHeight: "inherit",
        textTransform: "none" as any,
        "&.MuiFab-secondary": {
          color: theme.palette.text.primary,
        },
      },
    },
  },
  MuiFilledInput: {
    defaultProps: {
      disableUnderline: true,
    },
    styleOverrides: {
      root: {
        borderRadius: theme.shape.borderRadius,
      },
    },
  },
  MuiInternalClock: {
    styleOverrides: {
      clock: {
        backgroundColor: theme.palette.background.default,
      },
    },
  },
  MuiInternalDateTimePickerTabs: {
    styleOverrides: {
      tabs: {
        backgroundColor: theme.palette.background.default,
        "
Download .txt
gitextract_ux3vinbg/

├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── public/
│   ├── 404.html
│   ├── index.html
│   ├── locales/
│   │   ├── en/
│   │   │   └── translation.json
│   │   └── fr/
│   │       └── translation.json
│   ├── manifest.json
│   └── robots.txt
├── src/
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── AppRoutes.tsx
│   ├── admin/
│   │   ├── components/
│   │   │   ├── AdminAppBar.tsx
│   │   │   ├── AdminDrawer.tsx
│   │   │   ├── AdminToolbar.tsx
│   │   │   └── RecentNotifications.tsx
│   │   ├── config/
│   │   │   ├── activity.ts
│   │   │   └── notification.ts
│   │   ├── hooks/
│   │   │   ├── useActivityLogs.ts
│   │   │   ├── useNotifications.ts
│   │   │   ├── useProfileInfo.ts
│   │   │   └── useUpdateProfileInfo.ts
│   │   ├── pages/
│   │   │   ├── Admin.tsx
│   │   │   ├── Dashboard.tsx
│   │   │   ├── Faq.tsx
│   │   │   ├── HelpCenter.tsx
│   │   │   ├── Home.tsx
│   │   │   ├── Profile.tsx
│   │   │   ├── ProfileActivity.tsx
│   │   │   ├── ProfileInformation.tsx
│   │   │   └── ProfilePassword.tsx
│   │   ├── types/
│   │   │   ├── activityLog.ts
│   │   │   ├── notification.ts
│   │   │   └── profileInfo.ts
│   │   └── widgets/
│   │       ├── AchievementWidget.tsx
│   │       ├── ActivityWidget.tsx
│   │       ├── BudgetWidget.tsx
│   │       ├── CircleProgressWidget.tsx
│   │       ├── FollowersWidget.tsx
│   │       ├── MeetingWidgets.tsx
│   │       ├── OverviewWidget.tsx
│   │       ├── PersonalTargetsWidget.tsx
│   │       ├── ProgressWidget.tsx
│   │       ├── SalesByAgeWidget.tsx
│   │       ├── SalesByCategoryWidget.tsx
│   │       ├── SalesHistoryWidget.tsx
│   │       ├── TeamProgressWidget.tsx
│   │       ├── UsersWidget.tsx
│   │       ├── ViewsWidget.tsx
│   │       └── WelcomeWidget.tsx
│   ├── auth/
│   │   ├── contexts/
│   │   │   └── AuthProvider.tsx
│   │   ├── hooks/
│   │   │   ├── useForgotPassword.ts
│   │   │   ├── useForgotPasswordSubmit.ts
│   │   │   ├── useLogin.ts
│   │   │   ├── useLogout.ts
│   │   │   ├── useRegister.ts
│   │   │   ├── useUpdatePassword.ts
│   │   │   └── useUserInfo.ts
│   │   ├── pages/
│   │   │   ├── ForgotPassword.tsx
│   │   │   ├── ForgotPasswordSubmit.tsx
│   │   │   ├── Login.tsx
│   │   │   └── Register.tsx
│   │   └── types/
│   │       └── userInfo.ts
│   ├── calendar/
│   │   ├── components/
│   │   │   ├── Calendar.tsx
│   │   │   └── EventDialog.tsx
│   │   ├── hooks/
│   │   │   ├── useAddEvent.ts
│   │   │   ├── useDeleteEvent.ts
│   │   │   ├── useEvents.ts
│   │   │   └── useUpdateEvent.ts
│   │   ├── pages/
│   │   │   └── CalendarApp.tsx
│   │   └── types/
│   │       └── event.ts
│   ├── core/
│   │   ├── components/
│   │   │   ├── BoxedLayout.tsx
│   │   │   ├── ConfirmDialog.tsx
│   │   │   ├── Empty.tsx
│   │   │   ├── Footer.tsx
│   │   │   ├── Loader.tsx
│   │   │   ├── Logo.tsx
│   │   │   ├── PrivateRoute.tsx
│   │   │   ├── QueryWrapper.tsx
│   │   │   ├── Result.tsx
│   │   │   ├── SelectToolbar.tsx
│   │   │   ├── SettingsDrawer.tsx
│   │   │   └── SvgContainer.tsx
│   │   ├── config/
│   │   │   ├── i18n.ts
│   │   │   └── layout.ts
│   │   ├── contexts/
│   │   │   ├── SettingsProvider.tsx
│   │   │   └── SnackbarProvider.tsx
│   │   ├── hooks/
│   │   │   ├── useDateLocale.ts
│   │   │   ├── useLocalStorage.ts
│   │   │   └── usePageTracking.ts
│   │   ├── pages/
│   │   │   ├── Forbidden.tsx
│   │   │   ├── NotFound.tsx
│   │   │   └── UnderConstructions.tsx
│   │   ├── theme/
│   │   │   ├── components.tsx
│   │   │   ├── index.ts
│   │   │   ├── mixins.ts
│   │   │   ├── palette.ts
│   │   │   ├── shape.ts
│   │   │   ├── transitions.ts
│   │   │   └── typography.ts
│   │   └── utils/
│   │       ├── crudUtils.ts
│   │       └── selectUtils.ts
│   ├── index.tsx
│   ├── landing/
│   │   ├── components/
│   │   │   └── LandingLayout.tsx
│   │   └── pages/
│   │       └── Landing.tsx
│   ├── mocks/
│   │   ├── activityLogs.json
│   │   ├── events.json
│   │   ├── notifications.json
│   │   ├── profileInfo.json
│   │   ├── server.ts
│   │   ├── userInfo.json
│   │   └── users.json
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   ├── setupTests.ts
│   └── users/
│       ├── components/
│       │   ├── UserDialog.tsx
│       │   └── UserTable.tsx
│       ├── hooks/
│       │   ├── useAddUser.ts
│       │   ├── useDeleteUsers.ts
│       │   ├── useUpdateUser.ts
│       │   └── useUsers.ts
│       ├── pages/
│       │   └── UserManagement.tsx
│       └── types/
│           └── user.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (71 symbols across 55 files)

FILE: src/App.tsx
  function App (line 30) | function App() {

FILE: src/admin/components/AdminAppBar.tsx
  type AdminAppBarProps (line 5) | type AdminAppBarProps = {

FILE: src/admin/components/AdminDrawer.tsx
  type AdminDrawerProps (line 22) | type AdminDrawerProps = {

FILE: src/admin/components/AdminToolbar.tsx
  type AdminToolbarProps (line 7) | type AdminToolbarProps = {

FILE: src/admin/hooks/useActivityLogs.ts
  function useActivityLogs (line 10) | function useActivityLogs() {

FILE: src/admin/hooks/useNotifications.ts
  function useNotifications (line 10) | function useNotifications() {

FILE: src/admin/hooks/useProfileInfo.ts
  function useProfileInfo (line 10) | function useProfileInfo() {

FILE: src/admin/hooks/useUpdateProfileInfo.ts
  function useUpdateProfileInfo (line 12) | function useUpdateProfileInfo() {

FILE: src/admin/types/activityLog.ts
  type ActivityLog (line 1) | interface ActivityLog {

FILE: src/admin/types/notification.ts
  type Notification (line 1) | interface Notification {

FILE: src/admin/types/profileInfo.ts
  type ProfileInfo (line 1) | interface ProfileInfo {

FILE: src/admin/widgets/CircleProgressWidget.tsx
  type CircleProgressWidgetProps (line 12) | type CircleProgressWidgetProps = {

FILE: src/admin/widgets/OverviewWidget.tsx
  type OverviewWidgetProps (line 5) | type OverviewWidgetProps = {

FILE: src/admin/widgets/ProgressWidget.tsx
  type ProgressWidgetProps (line 8) | type ProgressWidgetProps = {

FILE: src/admin/widgets/SalesHistoryWidget.tsx
  type SalesWidgetProps (line 11) | type SalesWidgetProps = {

FILE: src/auth/contexts/AuthProvider.tsx
  type AuthContextInterface (line 8) | interface AuthContextInterface {
  type AuthProviderProps (line 19) | type AuthProviderProps = {
  function useAuth (line 78) | function useAuth() {

FILE: src/auth/hooks/useForgotPassword.ts
  function useForgotPassword (line 9) | function useForgotPassword() {

FILE: src/auth/hooks/useForgotPasswordSubmit.ts
  function useForgotPasswordSubmit (line 18) | function useForgotPasswordSubmit() {

FILE: src/auth/hooks/useLogin.ts
  function useLogin (line 15) | function useLogin() {

FILE: src/auth/hooks/useLogout.ts
  function useLogout (line 9) | function useLogout() {

FILE: src/auth/hooks/useRegister.ts
  function useRegister (line 10) | function useRegister() {

FILE: src/auth/hooks/useUpdatePassword.ts
  function useUpdatePassword (line 18) | function useUpdatePassword() {

FILE: src/auth/hooks/useUserInfo.ts
  function useUserInfo (line 10) | function useUserInfo(key?: string) {

FILE: src/auth/types/userInfo.ts
  type UserInfo (line 1) | interface UserInfo {

FILE: src/calendar/components/Calendar.tsx
  type CalendarProps (line 61) | type CalendarProps = {

FILE: src/calendar/components/EventDialog.tsx
  type EventDialogProps (line 22) | type EventDialogProps = {
  type EventFormValues (line 32) | type EventFormValues = {

FILE: src/calendar/hooks/useAddEvent.ts
  function useAddEvent (line 11) | function useAddEvent() {

FILE: src/calendar/hooks/useDeleteEvent.ts
  function useDeleteEvent (line 11) | function useDeleteEvent() {

FILE: src/calendar/hooks/useEvents.ts
  function useEvents (line 10) | function useEvents() {

FILE: src/calendar/hooks/useUpdateEvent.ts
  function useUpdateEvent (line 11) | function useUpdateEvent() {

FILE: src/calendar/types/event.ts
  type Event (line 1) | interface Event {
  type EventColor (line 12) | type EventColor = typeof eventColors[number];

FILE: src/core/components/BoxedLayout.tsx
  type BoxedLayoutProps (line 13) | type BoxedLayoutProps = {

FILE: src/core/components/ConfirmDialog.tsx
  type ConfirmDialogProps (line 12) | type ConfirmDialogProps = {

FILE: src/core/components/Empty.tsx
  type EmptyProps (line 4) | type EmptyProps = {

FILE: src/core/components/Logo.tsx
  type LogoProps (line 4) | type LogoProps = {

FILE: src/core/components/PrivateRoute.tsx
  type PrivateRouteProps (line 4) | type PrivateRouteProps = {

FILE: src/core/components/QueryWrapper.tsx
  type QueryWrapperProps (line 9) | type QueryWrapperProps = {

FILE: src/core/components/Result.tsx
  type ResultImageProps (line 9) | type ResultImageProps = {
  type ResultProps (line 28) | type ResultProps = {

FILE: src/core/components/SelectToolbar.tsx
  type SelectToolbarProps (line 9) | interface SelectToolbarProps {

FILE: src/core/components/SettingsDrawer.tsx
  type SettingsDrawerProps (line 16) | type SettingsDrawerProps = {

FILE: src/core/components/SvgContainer.tsx
  type SvgContainerProps (line 5) | type SvgContainerProps = {

FILE: src/core/contexts/SettingsProvider.tsx
  type SettingsContextInterface (line 15) | interface SettingsContextInterface {
  type SettingsProviderProps (line 28) | type SettingsProviderProps = {
  function useSettings (line 92) | function useSettings() {

FILE: src/core/contexts/SnackbarProvider.tsx
  type SnackbarContextInterface (line 7) | interface SnackbarContextInterface {
  type SnackbarProviderProps (line 14) | type SnackbarProviderProps = {
  function useSnackbar (line 72) | function useSnackbar() {

FILE: src/core/hooks/useDateLocale.ts
  function useDateLocale (line 9) | function useDateLocale(): Locale | undefined {

FILE: src/core/hooks/useLocalStorage.ts
  function useLocalStorage (line 5) | function useLocalStorage<T>(

FILE: src/core/utils/crudUtils.ts
  function addOne (line 1) | function addOne<T>(items: T[] = [], newItem: T) {
  function removeOne (line 5) | function removeOne<T extends { id: string }>(
  function removeMany (line 12) | function removeMany<T extends { id: string }>(
  function updateOne (line 19) | function updateOne<T extends { id: string }>(

FILE: src/landing/components/LandingLayout.tsx
  type LandingLayoutProps (line 12) | type LandingLayoutProps = {

FILE: src/mocks/server.ts
  function generateId (line 12) | function generateId() {

FILE: src/users/components/UserDialog.tsx
  type UserDialogProps (line 27) | type UserDialogProps = {

FILE: src/users/components/UserTable.tsx
  type HeadCell (line 27) | interface HeadCell {
  type EnhancedTableProps (line 56) | interface EnhancedTableProps {
  function EnhancedTableHead (line 62) | function EnhancedTableHead({
  type UserRowProps (line 96) | type UserRowProps = {
  type UserTableProps (line 232) | type UserTableProps = {

FILE: src/users/hooks/useAddUser.ts
  function useAddUser (line 11) | function useAddUser() {

FILE: src/users/hooks/useDeleteUsers.ts
  function useDeleteUsers (line 11) | function useDeleteUsers() {

FILE: src/users/hooks/useUpdateUser.ts
  function useUpdateUser (line 11) | function useUpdateUser() {

FILE: src/users/hooks/useUsers.ts
  function useUsers (line 10) | function useUsers() {

FILE: src/users/types/user.ts
  type User (line 1) | interface User {
Condensed preview — 125 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (254K chars).
[
  {
    "path": ".gitignore",
    "chars": 310,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "LICENSE",
    "chars": 1045,
    "preview": "Copyright 2021 Vaniya\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and"
  },
  {
    "path": "README.md",
    "chars": 4748,
    "preview": "<p align=\"center\">\n  <a href=\"https://m6v3l9.github.io/react-material-admin/\" rel=\"noopener\" target=\"_blank\"><img width="
  },
  {
    "path": "package.json",
    "chars": 2268,
    "preview": "{\n  \"name\": \"react-material-admin\",\n  \"version\": \"0.1.0-alpha\",\n  \"description\": \"Free and open-source admin application"
  },
  {
    "path": "public/404.html",
    "chars": 1891,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Single Page Apps for GitHub Pages</title>\n    <s"
  },
  {
    "path": "public/index.html",
    "chars": 3530,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.i"
  },
  {
    "path": "public/locales/en/translation.json",
    "chars": 11674,
    "preview": "{\n  \"admin\": {\n    \"drawer\": {\n      \"menu\": {\n        \"calendar\": \"Calendar\",\n        \"dashboard\": \"Dashboard\",\n       "
  },
  {
    "path": "public/locales/fr/translation.json",
    "chars": 12274,
    "preview": "{\n  \"admin\": {\n    \"drawer\": {\n      \"menu\": {\n        \"calendar\": \"Calendrier\",\n        \"dashboard\": \"Dashboard\",\n     "
  },
  {
    "path": "public/manifest.json",
    "chars": 491,
    "preview": "{\n  \"short_name\": \"React Admin\",\n  \"name\": \"React Material Admin\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      "
  },
  {
    "path": "public/robots.txt",
    "chars": 67,
    "preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "src/App.test.tsx",
    "chars": 273,
    "preview": "import { render, screen } from \"@testing-library/react\";\nimport React from \"react\";\nimport App from \"./App\";\n\ntest(\"rend"
  },
  {
    "path": "src/App.tsx",
    "chars": 1519,
    "preview": "import * as Sentry from \"@sentry/react\";\nimport React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"re"
  },
  {
    "path": "src/AppRoutes.tsx",
    "chars": 3215,
    "preview": "import { lazy } from \"react\";\nimport { Navigate, Route, Routes } from \"react-router-dom\";\nimport PrivateRoute from \"./co"
  },
  {
    "path": "src/admin/components/AdminAppBar.tsx",
    "chars": 663,
    "preview": "import AppBar from \"@material-ui/core/AppBar\";\nimport { drawerCollapsedWidth, drawerWidth } from \"../../core/config/layo"
  },
  {
    "path": "src/admin/components/AdminDrawer.tsx",
    "chars": 4951,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Drawer from \"@material-ui"
  },
  {
    "path": "src/admin/components/AdminToolbar.tsx",
    "chars": 956,
    "preview": "import IconButton from \"@material-ui/core/IconButton\";\nimport Toolbar from \"@material-ui/core/Toolbar\";\nimport Typograph"
  },
  {
    "path": "src/admin/components/RecentNotifications.tsx",
    "chars": 4641,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Badge from \"@material-ui/core/Badge\";\nimport Box from \"@material-u"
  },
  {
    "path": "src/admin/config/activity.ts",
    "chars": 312,
    "preview": "export const logKeys: { [key: string]: string } = {\n  eventAdded: \"profile.activity.logs.eventAdded\",\n  eventUpdated: \"p"
  },
  {
    "path": "src/admin/config/notification.ts",
    "chars": 156,
    "preview": "export const notificationKeys: { [key: string]: string } = {\n  newComment: \"notifications.newComment\",\n  unreadMessages:"
  },
  {
    "path": "src/admin/hooks/useActivityLogs.ts",
    "chars": 362,
    "preview": "import axios from \"axios\";\nimport { useQuery } from \"react-query\";\nimport { ActivityLog } from \"../types/activityLog\";\n\n"
  },
  {
    "path": "src/admin/hooks/useNotifications.ts",
    "chars": 396,
    "preview": "import axios from \"axios\";\nimport { useQuery } from \"react-query\";\nimport { Notification } from \"../types/notification\";"
  },
  {
    "path": "src/admin/hooks/useProfileInfo.ts",
    "chars": 355,
    "preview": "import axios from \"axios\";\nimport { useQuery } from \"react-query\";\nimport { ProfileInfo } from \"../types/profileInfo\";\n\n"
  },
  {
    "path": "src/admin/hooks/useUpdateProfileInfo.ts",
    "chars": 664,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { ProfileInfo } from \"../ty"
  },
  {
    "path": "src/admin/pages/Admin.tsx",
    "chars": 1197,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Toolbar from \"@material-ui/core/Toolbar\";\nimport { useState } from \"reac"
  },
  {
    "path": "src/admin/pages/Dashboard.tsx",
    "chars": 3169,
    "preview": "import Grid from \"@material-ui/core/Grid\";\nimport AttachMoneyIcon from \"@material-ui/icons/AttachMoney\";\nimport Shopping"
  },
  {
    "path": "src/admin/pages/Faq.tsx",
    "chars": 2211,
    "preview": "import Accordion from \"@material-ui/core/Accordion\";\nimport AccordionDetails from \"@material-ui/core/AccordionDetails\";\n"
  },
  {
    "path": "src/admin/pages/HelpCenter.tsx",
    "chars": 4451,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Badge from \"@material-ui/core/Badge\";\nimport Card from \"@material-"
  },
  {
    "path": "src/admin/pages/Home.tsx",
    "chars": 1229,
    "preview": "import Grid from \"@material-ui/core/Grid\";\nimport React from \"react\";\nimport AdminAppBar from \"../components/AdminAppBar"
  },
  {
    "path": "src/admin/pages/Profile.tsx",
    "chars": 3387,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Fab from \"@material-ui/co"
  },
  {
    "path": "src/admin/pages/ProfileActivity.tsx",
    "chars": 2287,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-u"
  },
  {
    "path": "src/admin/pages/ProfileInformation.tsx",
    "chars": 5378,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport Card from \"@material-ui/core/Card\";\nimport CardActions from \"@mate"
  },
  {
    "path": "src/admin/pages/ProfilePassword.tsx",
    "chars": 4102,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardActions from \"@material-ui/core/CardActions\";\nimport CardContent f"
  },
  {
    "path": "src/admin/types/activityLog.ts",
    "chars": 139,
    "preview": "export interface ActivityLog {\n  id: string;\n  actor: string;\n  code: string;\n  createdAt: number;\n  params?: { [key: st"
  },
  {
    "path": "src/admin/types/notification.ts",
    "chars": 164,
    "preview": "export interface Notification {\n  id: string;\n  code: string;\n  createdAt: number;\n  params?: {\n    quantity?: string;\n "
  },
  {
    "path": "src/admin/types/profileInfo.ts",
    "chars": 168,
    "preview": "export interface ProfileInfo {\n  id: string;\n  avatar?: string;\n  email: string;\n  firstName: string;\n  gender?: \"F\" | \""
  },
  {
    "path": "src/admin/widgets/AchievementWidget.tsx",
    "chars": 1606,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Button from \"@material-ui/core/Button\";\nimport Card from \"@materia"
  },
  {
    "path": "src/admin/widgets/ActivityWidget.tsx",
    "chars": 1972,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport CardHeader fr"
  },
  {
    "path": "src/admin/widgets/BudgetWidget.tsx",
    "chars": 1755,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport CardHeader fr"
  },
  {
    "path": "src/admin/widgets/CircleProgressWidget.tsx",
    "chars": 1657,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport CardHeader fr"
  },
  {
    "path": "src/admin/widgets/FollowersWidget.tsx",
    "chars": 2333,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/c"
  },
  {
    "path": "src/admin/widgets/MeetingWidgets.tsx",
    "chars": 2386,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui"
  },
  {
    "path": "src/admin/widgets/OverviewWidget.tsx",
    "chars": 712,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport Typography fr"
  },
  {
    "path": "src/admin/widgets/PersonalTargetsWidget.tsx",
    "chars": 2141,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-u"
  },
  {
    "path": "src/admin/widgets/ProgressWidget.tsx",
    "chars": 1282,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/c"
  },
  {
    "path": "src/admin/widgets/SalesByAgeWidget.tsx",
    "chars": 1948,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport CardHeader fr"
  },
  {
    "path": "src/admin/widgets/SalesByCategoryWidget.tsx",
    "chars": 1917,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport CardHeader fr"
  },
  {
    "path": "src/admin/widgets/SalesHistoryWidget.tsx",
    "chars": 2042,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-u"
  },
  {
    "path": "src/admin/widgets/TeamProgressWidget.tsx",
    "chars": 3368,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-u"
  },
  {
    "path": "src/admin/widgets/UsersWidget.tsx",
    "chars": 2388,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Card from \"@material-ui/core/Card\";\nimport CardContent from \"@mate"
  },
  {
    "path": "src/admin/widgets/ViewsWidget.tsx",
    "chars": 3377,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Card from \"@material-ui/c"
  },
  {
    "path": "src/admin/widgets/WelcomeWidget.tsx",
    "chars": 1329,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport Typography fr"
  },
  {
    "path": "src/auth/contexts/AuthProvider.tsx",
    "chars": 1960,
    "preview": "import React, { createContext, useContext } from \"react\";\nimport { useLocalStorage } from \"../../core/hooks/useLocalStor"
  },
  {
    "path": "src/auth/hooks/useForgotPassword.ts",
    "chars": 386,
    "preview": "import axios from \"axios\";\nimport { useMutation } from \"react-query\";\n\nconst forgotPassword = async ({ email }: { email:"
  },
  {
    "path": "src/auth/hooks/useForgotPasswordSubmit.ts",
    "chars": 482,
    "preview": "import axios from \"axios\";\nimport { useMutation } from \"react-query\";\n\nconst forgotPasswordSubmit = async ({\n  code,\n  n"
  },
  {
    "path": "src/auth/hooks/useLogin.ts",
    "chars": 419,
    "preview": "import axios from \"axios\";\nimport { useMutation } from \"react-query\";\n\nconst login = async ({\n  email,\n  password,\n}: {\n"
  },
  {
    "path": "src/auth/hooks/useLogout.ts",
    "chars": 338,
    "preview": "import axios from \"axios\";\nimport { useMutation } from \"react-query\";\n\nconst logout = async (): Promise<string> => {\n  c"
  },
  {
    "path": "src/auth/hooks/useRegister.ts",
    "chars": 424,
    "preview": "import axios from \"axios\";\nimport { useMutation } from \"react-query\";\nimport { UserInfo } from \"../types/userInfo\";\n\ncon"
  },
  {
    "path": "src/auth/hooks/useUpdatePassword.ts",
    "chars": 476,
    "preview": "import axios from \"axios\";\nimport { useMutation } from \"react-query\";\n\nconst updatePassword = async ({\n  oldPassword,\n  "
  },
  {
    "path": "src/auth/hooks/useUserInfo.ts",
    "chars": 413,
    "preview": "import axios from \"axios\";\nimport { useQuery } from \"react-query\";\nimport { UserInfo } from \"../types/userInfo\";\n\nconst "
  },
  {
    "path": "src/auth/pages/ForgotPassword.tsx",
    "chars": 2946,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui/core/Button\";\nimport TextField from \"@material"
  },
  {
    "path": "src/auth/pages/ForgotPasswordSubmit.tsx",
    "chars": 4345,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui/core/Button\";\nimport TextField from \"@material"
  },
  {
    "path": "src/auth/pages/Login.tsx",
    "chars": 4590,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui/core/Button\";\nimport Grid from \"@material-ui/c"
  },
  {
    "path": "src/auth/pages/Register.tsx",
    "chars": 5371,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui/core/Button\";\nimport FormControl from \"@materi"
  },
  {
    "path": "src/auth/types/userInfo.ts",
    "chars": 172,
    "preview": "export interface UserInfo {\n  id: string;\n  avatar?: string;\n  email: string;\n  firstName: string;\n  job: string;\n  last"
  },
  {
    "path": "src/calendar/components/Calendar.tsx",
    "chars": 4675,
    "preview": "import FullCalendar, {\n  CalendarOptions,\n  EventClickArg,\n} from \"@fullcalendar/react\";\nimport dayGridPlugin from \"@ful"
  },
  {
    "path": "src/calendar/components/EventDialog.tsx",
    "chars": 6814,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui/core/Button\";\nimport Dialog from \"@material-ui"
  },
  {
    "path": "src/calendar/hooks/useAddEvent.ts",
    "chars": 668,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { addOne } from \"../../core"
  },
  {
    "path": "src/calendar/hooks/useDeleteEvent.ts",
    "chars": 711,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { removeOne } from \"../../c"
  },
  {
    "path": "src/calendar/hooks/useEvents.ts",
    "chars": 312,
    "preview": "import axios from \"axios\";\nimport { useQuery } from \"react-query\";\nimport { Event } from \"../types/event\";\n\nconst fetchE"
  },
  {
    "path": "src/calendar/hooks/useUpdateEvent.ts",
    "chars": 687,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { updateOne } from \"../../c"
  },
  {
    "path": "src/calendar/pages/CalendarApp.tsx",
    "chars": 4285,
    "preview": "import Card from \"@material-ui/core/Card\";\nimport Fab from \"@material-ui/core/Fab\";\nimport AddIcon from \"@material-ui/ic"
  },
  {
    "path": "src/calendar/types/event.ts",
    "chars": 271,
    "preview": "export interface Event {\n  id: string;\n  title: string;\n  description?: string;\n  start: number;\n  end: number;\n  color?"
  },
  {
    "path": "src/core/components/BoxedLayout.tsx",
    "chars": 1826,
    "preview": "import AppBar from \"@material-ui/core/AppBar\";\nimport Box from \"@material-ui/core/Box\";\nimport Container from \"@material"
  },
  {
    "path": "src/core/components/ConfirmDialog.tsx",
    "chars": 1859,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport Dialog from \"@material-ui/core/Dialog\";\nimport DialogActions from "
  },
  {
    "path": "src/core/components/Empty.tsx",
    "chars": 312,
    "preview": "import { ReactComponent as EmptySvg } from \"../assets/empty.svg\";\nimport Result from \"./Result\";\n\ntype EmptyProps = {\n  "
  },
  {
    "path": "src/core/components/Footer.tsx",
    "chars": 660,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Link from \"@material-ui/core/Link\";\nimport Typography from \"@material-ui"
  },
  {
    "path": "src/core/components/Loader.tsx",
    "chars": 632,
    "preview": "import { useTheme } from \"@material-ui/core/styles\";\nimport Logo from \"./Logo\";\n\nconst Loader = () => {\n  const theme = "
  },
  {
    "path": "src/core/components/Logo.tsx",
    "chars": 388,
    "preview": "import Box, { BoxProps } from \"@material-ui/core/Box\";\nimport { ReactComponent as LogoSvg } from \"../assets/logo.svg\";\n\n"
  },
  {
    "path": "src/core/components/PrivateRoute.tsx",
    "chars": 584,
    "preview": "import { Navigate, Route, RouteProps } from \"react-router\";\nimport { useAuth } from \"../../auth/contexts/AuthProvider\";\n"
  },
  {
    "path": "src/core/components/QueryWrapper.tsx",
    "chars": 1082,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport React from \"react\";\nimport { ErrorBoundary } from \"react-error-bou"
  },
  {
    "path": "src/core/components/Result.tsx",
    "chars": 1557,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Container from \"@material-ui/core/Container\";\nimport Typography from \"@m"
  },
  {
    "path": "src/core/components/SelectToolbar.tsx",
    "chars": 1261,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Fab from \"@material-ui/core/Fab\";\nimport Toolbar from \"@material-ui/core"
  },
  {
    "path": "src/core/components/SettingsDrawer.tsx",
    "chars": 4829,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport Drawer from \"@material-ui/core/Drawer\";\nimport FormControl from \"@materi"
  },
  {
    "path": "src/core/components/SvgContainer.tsx",
    "chars": 816,
    "preview": "import Box from \"@material-ui/core/Box\";\nimport { useTheme } from \"@material-ui/core/styles\";\nimport React from \"react\";"
  },
  {
    "path": "src/core/config/i18n.ts",
    "chars": 570,
    "preview": "import i18n from \"i18next\";\nimport LanguageDetector from \"i18next-browser-languagedetector\";\nimport Backend from \"i18nex"
  },
  {
    "path": "src/core/config/layout.ts",
    "chars": 73,
    "preview": "export const drawerCollapsedWidth = 104;\nexport const drawerWidth = 280;\n"
  },
  {
    "path": "src/core/contexts/SettingsProvider.tsx",
    "chars": 2409,
    "preview": "import { ThemeProvider as MuiThemeProvider } from \"@material-ui/core\";\nimport CssBaseline from \"@material-ui/core/CssBas"
  },
  {
    "path": "src/core/contexts/SnackbarProvider.tsx",
    "chars": 1981,
    "preview": "import Alert, { Color } from \"@material-ui/core/Alert\";\nimport AlertTitle from \"@material-ui/core/AlertTitle\";\nimport Sn"
  },
  {
    "path": "src/core/hooks/useDateLocale.ts",
    "chars": 527,
    "preview": "import { Locale } from \"date-fns\";\nimport en from \"date-fns/locale/en-US\";\nimport fr from \"date-fns/locale/fr\";\nimport {"
  },
  {
    "path": "src/core/hooks/useLocalStorage.ts",
    "chars": 1328,
    "preview": "// https://usehooks.com/useLocalStorage/\n\nimport { useState } from \"react\";\n\nexport function useLocalStorage<T>(\n  key: "
  },
  {
    "path": "src/core/hooks/usePageTracking.ts",
    "chars": 632,
    "preview": "import { useEffect, useState } from \"react\";\nimport { useLocation } from \"react-router-dom\";\n\nconst usePageTracking = ()"
  },
  {
    "path": "src/core/pages/Forbidden.tsx",
    "chars": 784,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link as RouterLi"
  },
  {
    "path": "src/core/pages/NotFound.tsx",
    "chars": 785,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link as RouterLi"
  },
  {
    "path": "src/core/pages/UnderConstructions.tsx",
    "chars": 845,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport { useTranslation } from \"react-i18next\";\nimport { Link as RouterLi"
  },
  {
    "path": "src/core/theme/components.tsx",
    "chars": 6304,
    "preview": "import { Theme } from \"@material-ui/core\";\nimport CheckCircle from \"@material-ui/icons/CheckCircle\";\nimport RadioButtonU"
  },
  {
    "path": "src/core/theme/index.ts",
    "chars": 782,
    "preview": "import { createTheme as createMuiTheme } from \"@material-ui/core\";\nimport { createThemeComponents } from \"./components\";"
  },
  {
    "path": "src/core/theme/mixins.ts",
    "chars": 233,
    "preview": "const mixins = {\n  toolbar: {\n    minHeight: 80,\n    \"@media (min-width:600px)\": {\n      minHeight: 104,\n    },\n    \"@me"
  },
  {
    "path": "src/core/theme/palette.ts",
    "chars": 1632,
    "preview": "import { PaletteMode } from \"@material-ui/core\";\n\nconst palette = {\n  grey: {\n    \"50\": \"#ECEFF1\",\n    \"100\": \"#CFD8DC\","
  },
  {
    "path": "src/core/theme/shape.ts",
    "chars": 62,
    "preview": "const shape = {\n  borderRadius: 16,\n};\n\nexport default shape;\n"
  },
  {
    "path": "src/core/theme/transitions.ts",
    "chars": 210,
    "preview": "const transitions = {\n  duration: {\n    shortest: 75,\n    shorter: 100,\n    short: 125,\n    standard: 150,\n    complex: "
  },
  {
    "path": "src/core/theme/typography.ts",
    "chars": 897,
    "preview": "const typography = {\n  fontFamily: \"Nunito, sans-serif\",\n  fontWeightMedium: 700,\n  fontWeightBold: 800,\n  h1: {\n    fon"
  },
  {
    "path": "src/core/utils/crudUtils.ts",
    "chars": 576,
    "preview": "export function addOne<T>(items: T[] = [], newItem: T) {\n  return [...items, newItem];\n}\n\nexport function removeOne<T ex"
  },
  {
    "path": "src/core/utils/selectUtils.ts",
    "chars": 709,
    "preview": "export const selectAll = (list: any, key = \"id\") =>\n  list.map((item: any) => item[key]);\n\nexport const selectOne = (sel"
  },
  {
    "path": "src/index.tsx",
    "chars": 625,
    "preview": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { BrowserRouter } from \"react-router-dom\";\nimport Ap"
  },
  {
    "path": "src/landing/components/LandingLayout.tsx",
    "chars": 1546,
    "preview": "import AppBar from \"@material-ui/core/AppBar\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport Paper from "
  },
  {
    "path": "src/landing/pages/Landing.tsx",
    "chars": 4303,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Button from \"@material-ui"
  },
  {
    "path": "src/mocks/activityLogs.json",
    "chars": 813,
    "preview": "[\n  {\n    \"id\": \"1\",\n    \"actor\": \"You\",\n    \"code\": \"eventAdded\",\n    \"createdAt\": 1617868226000,\n    \"params\": {\n     "
  },
  {
    "path": "src/mocks/events.json",
    "chars": 182,
    "preview": "[\n  {\n    \"id\": \"1\",\n    \"title\": \"React Summit\",\n    \"description\": \"Lorem ipsum dolor sid amet\",\n    \"start\": 15192118"
  },
  {
    "path": "src/mocks/notifications.json",
    "chars": 305,
    "preview": "[\n  {\n    \"id\": \"1\",\n    \"code\": \"unreadMessages\",\n    \"createdAt\": 1617868226000,\n    \"params\": {\n      \"quantity\": \"5\""
  },
  {
    "path": "src/mocks/profileInfo.json",
    "chars": 127,
    "preview": "{\n  \"id\": \"1\",\n  \"avatar\": \"\",\n  \"email\": \"john@smith.com\",\n  \"firstName\": \"John\",\n  \"job\": \"Founder\",\n  \"lastName\": \"Sm"
  },
  {
    "path": "src/mocks/server.ts",
    "chars": 1851,
    "preview": "import axios from \"axios\";\nimport MockAdapter from \"axios-mock-adapter\";\nimport activityLogs from \"./activityLogs.json\";"
  },
  {
    "path": "src/mocks/userInfo.json",
    "chars": 164,
    "preview": "{\n  \"id\": \"1\",\n  \"avatar\": \"\",\n  \"email\": \"john@smith.com\",\n  \"firstName\": \"John\",\n  \"job\": \"Founder\",\n  \"lastName\": \"Sm"
  },
  {
    "path": "src/mocks/users.json",
    "chars": 2100,
    "preview": "[\n  {\n    \"id\": \"1\",\n    \"avatar\": \"\",\n    \"disabled\": false,\n    \"email\": \"rhys@arriaga.com\",\n    \"firstName\": \"Rhys\",\n"
  },
  {
    "path": "src/react-app-env.d.ts",
    "chars": 40,
    "preview": "/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "src/reportWebVitals.ts",
    "chars": 425,
    "preview": "import { ReportHandler } from 'web-vitals';\n\nconst reportWebVitals = (onPerfEntry?: ReportHandler) => {\n  if (onPerfEntr"
  },
  {
    "path": "src/setupTests.ts",
    "chars": 241,
    "preview": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).to"
  },
  {
    "path": "src/users/components/UserDialog.tsx",
    "chars": 6760,
    "preview": "import Button from \"@material-ui/core/Button\";\nimport Checkbox from \"@material-ui/core/Checkbox\";\nimport Dialog from \"@m"
  },
  {
    "path": "src/users/components/UserTable.tsx",
    "chars": 8972,
    "preview": "import Avatar from \"@material-ui/core/Avatar\";\nimport Box from \"@material-ui/core/Box\";\nimport Checkbox from \"@material-"
  },
  {
    "path": "src/users/hooks/useAddUser.ts",
    "chars": 650,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { addOne } from \"../../core"
  },
  {
    "path": "src/users/hooks/useDeleteUsers.ts",
    "chars": 712,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { removeMany } from \"../../"
  },
  {
    "path": "src/users/hooks/useUpdateUser.ts",
    "chars": 669,
    "preview": "import axios from \"axios\";\nimport { useMutation, useQueryClient } from \"react-query\";\nimport { updateOne } from \"../../c"
  },
  {
    "path": "src/users/hooks/useUsers.ts",
    "chars": 304,
    "preview": "import axios from \"axios\";\nimport { useQuery } from \"react-query\";\nimport { User } from \"../types/user\";\n\nconst fetchUse"
  },
  {
    "path": "src/users/pages/UserManagement.tsx",
    "chars": 4886,
    "preview": "import Fab from \"@material-ui/core/Fab\";\nimport AddIcon from \"@material-ui/icons/Add\";\nimport React, { useState } from \""
  },
  {
    "path": "src/users/types/user.ts",
    "chars": 183,
    "preview": "export interface User {\n  id: string;\n  avatar?: string;\n  disabled: boolean;\n  email: string;\n  firstName: string;\n  ge"
  },
  {
    "path": "tsconfig.json",
    "chars": 535,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    "
  }
]

About this extraction

This page contains the full source code of the m6v3l9/react-material-admin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 125 files (225.7 KB), approximately 57.5k tokens, and a symbol index with 71 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!