diff --git a/.env.example b/.env.example index 9962426..e6b65f4 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,11 @@ +# FRONTEND REACT_APP_GAMESHQ_API_URL="http://127.0.0.1:3000" -# Firebase +# FIREBASE REACT_APP_FIREBASE_API_KEY= REACT_APP_FIREBASE_AUTH_DOMAIN= REACT_APP_FIREBASE_DATTABASE_URL= REACT_APP_PROJECT_ID= REACT_APP_STORAGE_BUCKET= REACT_APP_MESSAGING_SENDER_ID= -REACT_APP_APP_ID= \ No newline at end of file +REACT_APP_APP_ID= diff --git a/README.md b/README.md index b58e0af..67b2c65 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,138 @@ -# Getting Started with Create React App +

+
+ Markdownify +
+ Games Admin panel +
+

-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +## What is it? +Headquarters of the games interactions. Games Admin Panel is basically a publishing tool to connect new browser games with the XTU ecosystem. -## Available Scripts +The admin panel let game developers integrate their games with the XTU world. It lets them create game key pairs, leaderboards, achievements, monitor statistics, between others. -In the project directory, you can run: +The app also works great for XTeam Admins letting them create and manage The arena, and the Tower game rounds, new items, monitor games, between others. -### `yarn start` -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +## 📑Contents -The page will reload if you make edits.\ -You will also see any lint errors in the console. +* [Tech Stack](#tech-stack) +* [Requirements](#requirements) +* [Project](#project) + * [Environmental Variables](#environmental-variables) + * [Run APP](#run-app) +* [How to contribute](#how-to-contribute) + * [JIRA](#jira) + * [Commits](#commits) + * [Pull Requests](#pull-requests) +* [Continous Integration](#continous-integration) -### `yarn test` +## 📦Tech Stack +* [Node.js](https://nodejs.org/) +* [React](https://es.reactjs.org/) +* [CRACO](https://www.npmjs.com/package/@craco/craco) -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +## 🔎Requirements +* [NVM - Node Version Manager](https://github.com/nvm-sh/nvm) -### `yarn build` +## 🚀Project +Let's setup the project!🥹 -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. +### **🔒Environmental Variables** +The APP needs some env vars to work properly. You can ask @ccmoralesj for this. -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! +Just grab the `.env.example` file and copy everything into a new file called `.env`. It will look something like this at first. -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +``` +# FRONTEND +REACT_APP_GAMESHQ_API_URL="http://127.0.0.1:3000" -### `yarn eject` +# FIREBASE +REACT_APP_FIREBASE_API_KEY= +REACT_APP_FIREBASE_AUTH_DOMAIN= +REACT_APP_FIREBASE_DATTABASE_URL= +REACT_APP_PROJECT_ID= +REACT_APP_STORAGE_BUCKET= +REACT_APP_MESSAGING_SENDER_ID= +REACT_APP_APP_ID= -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +``` +### **🖼️Run App** +We are almost there with the setup.🥲 Now it's time to run the API to connect the APP with. You can go to [Games API repo](https://github.com/x-team/GamesHQ-API) and follow the [README](https://github.com/x-team/GamesHQ-API#readme) to setup you own API, or you can connect with the staging API: -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +```bash +https://gameshq-api-staging.x-team.com +``` -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. +Now let's run the FE APP. 🎉 -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. -## Learn More +First, make sure you're using our recommended version of Node and yarn by running these commands: -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +```bash +nvm use +``` -To learn React, check out the [React documentation](https://reactjs.org/). +Following up, we have to install all required dependencies to run the project: + +```bash +npm i --legacy-peer-deps +# We use legacy peer deps due to a compatibility issue in React at the moment +``` + +Finally, **run the API first** and then the application with the following command: + +```bash +npm start +``` + +The APP should be up and running 🎉at port 3001!🎉 You can verify by browsing to [http://localhost:3001](http://localhost:3001) + +Let's start coding!🤓 + +(GIF: Wait for it...) +![App Running](https://i.imgur.com/puC3sX2.png) + +## 🫂How to contribute +Collaborate in this repo is quite easy. +### 📊JIRA +You only need to pick up a ticket from the [JIRA board](https://x-team-internal.atlassian.net/jira/software/c/projects/XTG/boards/48) (If you don't have access you can ask @ccmoralesj) + +Each JIRA ticket has an identifier based on a code and a number like XTG-123 which you will use later. +### 💾Commits +Each commit you do needs to have the JIRA ticket identifier so it can be related to the board. + +You can use this commit format suggestion. + +``` +:optional-emoji: XTG-123: New endpoint to handle login +``` + +| **Emoji** | **Description** | +|-----------|-----------------------------------------| +| **🚀** | New features, or enhancements to code | +| **🐞** | Bug fixes | +| **⚙️** | Refactors | +| **📦** | Build files, dependencies, config files | +| **🔎** | Minor fixes, typos, imports, etc | +| **🪄** | Others | + +![Commit Example](https://i.imgur.com/gClC6CV.gif) + +### 🕵🏻Pull Requests +Once you're ready. You can create a new Pull Request (PR) by adding the JIRA ticket identifier in the title. The repo will give you a template to fill in the details of your amazing work! + +You can use this PR title format suggestion. + +``` +XTG-123: Login +``` + +You'll need at least 1 review from your teammates to merge the pull request. + +## 🪄Continous Integration +This project is connected to an EC2 instance from AWS. + +All code from `main` branch will be deployed to staging. + +To **deploy to production** you must create a `new release` and follow the [semantic versioning](https://semver.org/lang/es/) fundamentals. That will trigger an automated deployment to **production**. diff --git a/buildspec.yml b/buildspec.yml index 270277f..96b6c8e 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -14,20 +14,17 @@ phases: commands: - n 16 - npm update -g npm - - npm install -g yarn - aws --version - node -v - - yarn -v - - yarn install --frozen-lockfile --production=false - wget https://github.com/Droplr/aws-env/raw/master/bin/aws-env-linux-amd64 -O aws-env - chmod +x aws-env + - ls -la build: commands: - - eval $(./aws-env --recursive) - - yarn install --frozen-lockfile --production=false - - NODE_ENV=production yarn build + - npm i --legacy-peer-deps + - eval $(./aws-env --recursive) && npm run build post_build: commands: - - aws s3 sync build/. s3://$FRONTEND_BUCKET \ No newline at end of file + - aws s3 sync build/. s3://$FRONTEND_BUCKET diff --git a/package-lock.json b/package-lock.json index f63493c..5415f0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,11 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-firebaseui": "6.0.0", + "react-icons": "^4.4.0", "react-router-dom": "6.3.0", "react-scripts": "5.0.1", "react-spinners": "0.12.0", + "react-toastify": "^9.0.5", "recharts": "2.1.10", "sweetalert2": "11.4.17", "typescript": "4.7.3", @@ -2390,6 +2392,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2420,6 +2437,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -4880,13 +4902,13 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -4910,34 +4932,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5339,6 +5333,34 @@ "webpack": ">=2" } }, + "node_modules/babel-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/babel-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/babel-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/babel-loader/node_modules/schema-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", @@ -5921,6 +5943,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6416,21 +6446,6 @@ } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -6442,11 +6457,6 @@ "ajv": "^8.8.2" } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -7858,6 +7868,21 @@ "webpack": "^5.0.0" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7903,6 +7928,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -8459,6 +8489,29 @@ } } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8511,6 +8564,11 @@ "node": ">=10" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -10927,9 +10985,9 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -11362,21 +11420,6 @@ "webpack": "^5.0.0" } }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -11388,11 +11431,6 @@ "ajv": "^8.8.2" } }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -13683,6 +13721,14 @@ "react": ">=15 <=17" } }, + "node_modules/react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -13859,6 +13905,18 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.5.tgz", + "integrity": "sha512-dszPCeQINY+Nm6HmsiAXT/7wsazPqv0S/RuhIYLAW+fTKcd3T1iRjZG0XqrN9nvAzqaE5J6uxMaiBrOevxjY8g==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-transition-group": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", @@ -14430,6 +14488,34 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -15955,21 +16041,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -15986,11 +16057,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -16059,21 +16125,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-server/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -16090,11 +16141,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -16366,21 +16412,6 @@ "node": ">=10.0.0" } }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/workbox-build/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -16395,11 +16426,6 @@ "node": ">=10" } }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/workbox-build/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -18172,12 +18198,14 @@ "@csstools/postcss-unset-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.1.tgz", - "integrity": "sha512-f1G1WGDXEU/RN1TWAxBPQgQudtLnLQPyiWdtypkPC+mVYNKFKH/HYXSxH4MVNqwF8M0eDsoiU7HumJHCg/L/jg==" + "integrity": "sha512-f1G1WGDXEU/RN1TWAxBPQgQudtLnLQPyiWdtypkPC+mVYNKFKH/HYXSxH4MVNqwF8M0eDsoiU7HumJHCg/L/jg==", + "requires": {} }, "@csstools/selector-specificity": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.1.tgz", - "integrity": "sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA==" + "integrity": "sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA==", + "requires": {} }, "@emotion/babel-plugin": { "version": "11.9.2", @@ -18311,6 +18339,17 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -18332,6 +18371,11 @@ "argparse": "^2.0.1" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -18461,12 +18505,14 @@ "@firebase/auth-interop-types": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==" + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "requires": {} }, "@firebase/auth-types": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.0.tgz", - "integrity": "sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==" + "integrity": "sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==", + "requires": {} }, "@firebase/component": { "version": "0.5.15", @@ -18542,7 +18588,8 @@ "@firebase/firestore-types": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.0.tgz", - "integrity": "sha512-I6c2m1zUhZ5SH0cWPmINabDyH5w0PPFHk2UHsjBpKdZllzJZ2TwTkXbDtpHUZNmnc/zAa0WNMNMvcvbb/xJLKA==" + "integrity": "sha512-I6c2m1zUhZ5SH0cWPmINabDyH5w0PPFHk2UHsjBpKdZllzJZ2TwTkXbDtpHUZNmnc/zAa0WNMNMvcvbb/xJLKA==", + "requires": {} }, "@firebase/functions": { "version": "0.8.2", @@ -18719,7 +18766,8 @@ "@firebase/storage-types": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.0.tgz", - "integrity": "sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA==" + "integrity": "sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA==", + "requires": {} }, "@firebase/util": { "version": "1.6.1", @@ -19517,7 +19565,8 @@ "@testing-library/user-event": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.2.0.tgz", - "integrity": "sha512-+hIlG4nJS6ivZrKnOP7OGsDu9Fxmryj9vCl8x0ZINtTJcCHs2zLsYif5GzuRiBF2ck5GZG2aQr7Msg+EHlnYVQ==" + "integrity": "sha512-+hIlG4nJS6ivZrKnOP7OGsDu9Fxmryj9vCl8x0ZINtTJcCHs2zLsYif5GzuRiBF2ck5GZG2aQr7Msg+EHlnYVQ==", + "requires": {} }, "@tootallnate/once": { "version": "1.1.2", @@ -20204,12 +20253,14 @@ "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==" + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} }, "acorn-node": { "version": "1.8.2", @@ -20256,13 +20307,13 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, @@ -20272,31 +20323,8 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "requires": { "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -20587,6 +20615,28 @@ "schema-utils": "^2.6.5" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "schema-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", @@ -20643,7 +20693,8 @@ "babel-plugin-named-asset-import": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==" + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "requires": {} }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", @@ -21032,6 +21083,11 @@ "wrap-ansi": "^7.0.0" } }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -21338,7 +21394,8 @@ "css-declaration-sorter": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", - "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==" + "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", + "requires": {} }, "css-has-pseudo": { "version": "3.0.4", @@ -21376,17 +21433,6 @@ "source-map": "^0.6.1" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -21395,11 +21441,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -21416,7 +21457,8 @@ "css-prefers-color-scheme": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==" + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "requires": {} }, "css-select": { "version": "4.3.0", @@ -21518,7 +21560,8 @@ "cssnano-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==" + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "requires": {} }, "csso": { "version": "4.2.0", @@ -22174,6 +22217,17 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -22204,6 +22258,11 @@ "argparse": "^2.0.1" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -22451,7 +22510,8 @@ "eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==" + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "requires": {} }, "eslint-plugin-testing-library": { "version": "5.5.1", @@ -22914,6 +22974,23 @@ "tapable": "^1.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -22951,6 +23028,11 @@ "universalify": "^2.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -23415,7 +23497,8 @@ "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "requires": {} }, "idb": { "version": "7.0.1", @@ -24127,7 +24210,8 @@ "jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "requires": {} }, "jest-regex-util": { "version": "27.5.1", @@ -24703,9 +24787,9 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -25032,17 +25116,6 @@ "schema-utils": "^4.0.0" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -25051,11 +25124,6 @@ "fast-deep-equal": "^3.1.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -25595,7 +25663,8 @@ "postcss-browser-comments": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==" + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "requires": {} }, "postcss-calc": { "version": "8.2.4", @@ -25693,22 +25762,26 @@ "postcss-discard-comments": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==" + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==" + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==" + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==" + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "requires": {} }, "postcss-double-position-gradients": { "version": "3.1.1", @@ -25730,7 +25803,8 @@ "postcss-flexbugs-fixes": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==" + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "requires": {} }, "postcss-focus-visible": { "version": "6.0.4", @@ -25751,12 +25825,14 @@ "postcss-font-variant": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==" + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "requires": {} }, "postcss-gap-properties": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz", - "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==" + "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==", + "requires": {} }, "postcss-image-set-function": { "version": "4.0.6", @@ -25779,7 +25855,8 @@ "postcss-initial": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==" + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "requires": {} }, "postcss-js": { "version": "4.0.0", @@ -25821,12 +25898,14 @@ "postcss-logical": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==" + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==" + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "requires": {} }, "postcss-merge-longhand": { "version": "5.1.5", @@ -25887,7 +25966,8 @@ "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -25945,7 +26025,8 @@ "postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==" + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -26030,12 +26111,14 @@ "postcss-overflow-shorthand": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz", - "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==" + "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==", + "requires": {} }, "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==" + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "requires": {} }, "postcss-place": { "version": "7.0.4", @@ -26142,7 +26225,8 @@ "postcss-replace-overflow-wrap": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==" + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "requires": {} }, "postcss-selector-not": { "version": "6.0.0", @@ -26526,6 +26610,12 @@ "firebaseui": "^6.0.0" } }, + "react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -26653,6 +26743,14 @@ "@emotion/react": "^11.4.1" } }, + "react-toastify": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.5.tgz", + "integrity": "sha512-dszPCeQINY+Nm6HmsiAXT/7wsazPqv0S/RuhIYLAW+fTKcd3T1iRjZG0XqrN9nvAzqaE5J6uxMaiBrOevxjY8g==", + "requires": { + "clsx": "^1.1.1" + } + }, "react-transition-group": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", @@ -27061,6 +27159,30 @@ "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } } }, "select-hose": { @@ -27521,7 +27643,8 @@ "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==" + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "requires": {} }, "stylehacks": { "version": "5.1.0", @@ -28243,17 +28366,6 @@ "schema-utils": "^4.0.0" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -28267,11 +28379,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -28321,17 +28428,6 @@ "ws": "^8.4.2" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -28345,11 +28441,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -28364,7 +28455,8 @@ "ws": { "version": "8.8.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", - "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==" + "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", + "requires": {} } } }, @@ -28553,17 +28645,6 @@ "workbox-window": "6.5.3" }, "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -28575,11 +28656,6 @@ "universalify": "^2.0.0" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -28783,7 +28859,8 @@ "ws": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz", - "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==" + "integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==", + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index c195213..43d3718 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-firebaseui": "6.0.0", + "react-icons": "^4.4.0", "react-router-dom": "6.3.0", "react-scripts": "5.0.1", "react-spinners": "0.12.0", + "react-toastify": "^9.0.5", "recharts": "2.1.10", "sweetalert2": "11.4.17", "typescript": "4.7.3", diff --git a/src/App.tsx b/src/App.tsx index 844a665..97d54fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,8 @@ import TowerGamePage from "./pages/TowerGamePage"; import FloorsEditorPage from "./pages/FloorsEditorPage"; import SignInPage from "./pages/SignInPage"; import HomePage from "./pages/HomePage"; +import AchievementResultsPage from "./pages/AchievementResultsPage"; +import LeaderboardRanksPage from "./pages/LeaderboardRanksPage"; // import useCurrentUser from "./hooks/useCurrentUser"; // import { SyncLoader } from "react-spinners"; @@ -22,6 +24,8 @@ import HomePage from "./pages/HomePage"; // import { UnauthorizedPage } from "./pages/UnauthorizedPage"; import { ProtectedRoute } from "./ProtectedRoute"; import { AppMenu } from "./AppMenu"; +import { ToastContainer } from 'react-toastify'; + import 'react-toastify/dist/ReactToastify.css'; export const App = () => { // if (isDoingInitialLoading) { @@ -34,6 +38,7 @@ export const App = () => { return ( + { } /> + + + + + + } + /> + + + + + + } + /> { return achievements; }; +export const getAchievementsProgress = async (gameTypeId: number , achievementId: number) => { + const axios = await getAxiosInstance(); + + const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${gameTypeId}/achievements/${achievementId}/progress`; + const response = await axios.get(endpoint); + const achievements = response.data as IAchievementUnlocked[]; + + return achievements; +}; + export const getAchievement = async (gameTypeId: number, achievementId: number) => { const axios = await getAxiosInstance(); const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${gameTypeId}/achievements/${achievementId}`; const response = await axios.get(endpoint); - const acheivement = response.data.game as IGameType; + const acheivement = response.data as IAchievementUnlocked[]; return acheivement; }; @@ -23,7 +33,7 @@ export const getAchievement = async (gameTypeId: number, achievementId: number) export const upsertAchievement = async (data: IAchievement) => { const axios = await getAxiosInstance(); - const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${data._gameTypeId}/achievements/${data.id}`; + const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${data._gameTypeId}/achievements`; await axios.post(endpoint, data); }; diff --git a/src/api/leaderboards.ts b/src/api/leaderboards.ts index 449ac58..6c157e1 100644 --- a/src/api/leaderboards.ts +++ b/src/api/leaderboards.ts @@ -5,7 +5,7 @@ export const getGameTypeLeaderboards = async (gameTypeId: number) => { const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${gameTypeId}/leaderboards`; const response = await axios.get(endpoint); - const leaderboards = response.data.games as IGameType[]; + const leaderboards = response.data.games as ILeaderboard[]; return leaderboards; }; @@ -15,15 +15,25 @@ export const getLeaderboard = async (gameTypeId: number, leaderboardId: number) const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${gameTypeId}/leaderboards/${leaderboardId}`; const response = await axios.get(endpoint); - const game = response.data.game as IGameType; + const game = response.data.game as ILeaderboard; return game; }; -export const upsertLeaderboard = async (gameTypeId: number, data: ILeaderboard) => { +export const getLeaderboardResults = async (gameTypeId: number, leaderboardId: number) => { const axios = await getAxiosInstance(); - const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${gameTypeId}/leaderboards/${data.id}`; + const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${gameTypeId}/leaderboards/${leaderboardId}/results`; + const response = await axios.get(endpoint); + const game = response.data as ILeaderboardResult[]; + + return game; +}; + +export const upsertLeaderboard = async (data: ILeaderboard) => { + const axios = await getAxiosInstance(); + + const endpoint = gamesHqUrl + `/dashboard/game-dev/games/${data._gameTypeId}/leaderboards`; await axios.post(endpoint, data); }; diff --git a/src/helpers/emojiHelper.tsx b/src/helpers/emojiHelper.tsx index c7a8baa..95452a7 100644 --- a/src/helpers/emojiHelper.tsx +++ b/src/helpers/emojiHelper.tsx @@ -64,13 +64,17 @@ export const emojiToImageSrc = (emoji: string, allEmoji: IAllEmoji) => { export const emojiToImageTag = ( emoji: string, allEmoji: IAllEmoji, - className?: string + className?: string, + qty?: number, ) => { return ( - {emoji +
+ {emoji + {qty &&
{qty}
} +
); }; diff --git a/src/pages/AchievementResultsPage.tsx b/src/pages/AchievementResultsPage.tsx new file mode 100644 index 0000000..e8ce980 --- /dev/null +++ b/src/pages/AchievementResultsPage.tsx @@ -0,0 +1,65 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { getAchievementsProgress } from "../api/achievements"; +import Button from "../ui/Button"; + + +const AchievementResultsPage = () => { + const [achievementResults, setAchievementResults] = useState(); + const navigate = useNavigate(); + const { gameTypeId, achievementId } = useParams<{ + gameTypeId: string; + achievementId: string; + }>(); + + useEffect(() => { + async function fetchAchievementProgress() { + if (!gameTypeId || !achievementId) { + return; + } + + const achievementsRes = await getAchievementsProgress( + Number(gameTypeId), + Number(achievementId) + ); + + setAchievementResults(achievementsRes); + return achievementsRes; + } + + fetchAchievementProgress(); + }, [gameTypeId, achievementId]); + + return ( +
+

ACHIEVEMENT RANK

+ + + + + + + + + + {achievementResults && + achievementResults?.map((achievementRank: IAchievementUnlocked, index) => ( + // Despite it's not a good practice using index as a key, it's the only way + // to certify key is unique for every row, since AchievementUnlocked table won't have an id. + + + + + ))} + +
User + Rank +
{achievementRank._user?.email} + {achievementRank?.progress} +
+ +
+ ); +}; + +export default AchievementResultsPage; diff --git a/src/pages/FloorsEditorPage.tsx b/src/pages/FloorsEditorPage.tsx index 241f0d0..0104883 100644 --- a/src/pages/FloorsEditorPage.tsx +++ b/src/pages/FloorsEditorPage.tsx @@ -14,18 +14,22 @@ import PanelBox from "../ui/PanelBox"; const FloorsEditorPage = () => { const [allEmoji, setAllEmoji] = useState({}); const [, setIsLoading] = useState(false); + const [shouldReload, setShouldReload] = useState(true); const [towerGame, setTowerGame] = useState(null); const [enemies, setEnemies] = useState([]); useEffect(() => { const fetchTowerAndEnemiesData = async () => { setIsLoading(true); + if(!shouldReload) { + return; + } const [enemies, game, allEmoji] = await Promise.all([ getEnemies(), getCurrentTowerGameStatus(), getAllEmoji(), ]); - + setShouldReload(false); setEnemies(enemies); setTowerGame(game); setAllEmoji(allEmoji); @@ -33,7 +37,7 @@ const FloorsEditorPage = () => { }; fetchTowerAndEnemiesData(); - }, [setTowerGame, setEnemies]); + }, [setTowerGame, setEnemies, shouldReload]); if (!towerGame) { return ( @@ -69,6 +73,7 @@ const FloorsEditorPage = () => { allEmoji={allEmoji} enemies={enemies} towerGame={towerGame} + setShouldReload={setShouldReload} /> ); @@ -78,9 +83,10 @@ interface IFloorEditorProps { enemies: IEnemy[]; towerGame: IGameWithTower; allEmoji: IAllEmoji; + setShouldReload: (reload: boolean) => void; } -const FloorsEditor = ({ enemies, towerGame, allEmoji }: IFloorEditorProps) => { +const FloorsEditor = ({ enemies, towerGame, allEmoji, setShouldReload }: IFloorEditorProps) => { const [showAddEnemyModal, setShowAddEnemyModal] = useState(false); const [editingFloor, setEditingFloor] = useState(null); @@ -105,18 +111,19 @@ const FloorsEditor = ({ enemies, towerGame, allEmoji }: IFloorEditorProps) => {
-
+
{Object.keys(groupedByEnemies).map((enemyKey) => ( -
- {groupedByEnemies[enemyKey].length}x{" "} +
{emojiToImageTag( groupedByEnemies[enemyKey][0].emoji, - allEmoji + allEmoji, + "h-12 w-12", + groupedByEnemies[enemyKey].length, )}
))} @@ -125,14 +132,21 @@ const FloorsEditor = ({ enemies, towerGame, allEmoji }: IFloorEditorProps) => { ); }; - return ( + + const handleCloseEditFloorModalAction = (reload: boolean) => { + setShowAddEnemyModal(false); + setShouldReload(reload); + setEditingFloor(null); + } + + return (
setShowAddEnemyModal(false)} + onClose={handleCloseEditFloorModalAction} />
{towerGame._tower._floors diff --git a/src/pages/GameEditorPage.tsx b/src/pages/GameEditorPage.tsx index 0ca5c13..9a5cd22 100644 --- a/src/pages/GameEditorPage.tsx +++ b/src/pages/GameEditorPage.tsx @@ -3,8 +3,13 @@ import { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { SyncLoader } from "react-spinners"; import * as Yup from "yup"; +import { AiOutlineCheck, AiOutlineClose, AiOutlineCopy } from "react-icons/ai"; +import { toast } from "react-toastify"; + import { getAchievements } from "../api/achievements"; import { getGameType, upsertGameType } from "../api/gamedev"; +import AddOrEditAchievementModal from "../ui/AddOrEditAchievementModal"; +import AddOrEditLeaderboardModal from "../ui/AddOrEditLeaderboardModal"; import Button from "../ui/Button"; import TextInput from "../ui/TextInput"; @@ -28,10 +33,20 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) { ); const [achievements, setAchievements] = useState([]); const [isUpdatingGameName, setIsUpdatingGameName] = useState(false); + const [shouldLoadAchievements, setShouldLoadAchievements] = useState(true); + const [shouldLoadGameType, setShouldGameType] = useState(true); + const [selectedAchievement, setSelectedAchievement] = useState(); + const [selectedLeaderboard, setSelectedLeaderboard] = useState(); + const [showModal, setShowModal] = useState(false); + const [showLeaderboardModal, setShowLeaderboardModal] = useState(false); + const [hasCopiedSigninSecret, setHasCopiedSigninSecret] = useState(false); + const [hasCopiedClientSecret, setHasCopiedClientSecret] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); const navigate = useNavigate(); const { gameTypeId } = useParams<{ gameTypeId: string }>(); + const onSubmit = async (values: IForm, _actions: FormikHelpers) => { setIsLoading(true); @@ -52,7 +67,13 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) { try { await upsertGameType(upserGameTypeParams); navigate("/games"); + toast('Game successfully saved.',{ + type: 'success', + }); } catch (error: any) { + toast(`Error saving game : ${error?.message}`, { + type: "error", + }); console.log({ error }); setIsLoading(false); setErrorMessage(error.message); @@ -85,7 +106,7 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) { useEffect(() => { async function fetchGameTypeData() { try { - if (!gameTypeId) { + if (!gameTypeId || !shouldLoadGameType) { return; } const gameType = await getGameType(Number(gameTypeId)); @@ -100,25 +121,30 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) { setGameType(gameType); setIsLoading(false); + setShouldGameType(false); return gameType; } catch (error: any) { console.log({ error }); + toast(`Error : ${error?.message}`, { + type: "error", + }); setIsLoading(false); setErrorMessage(error.message); } } fetchGameTypeData(); - }, [gameTypeId, setValues]); + }, [gameTypeId, setValues, shouldLoadGameType]); useEffect(() => { async function fetchAchievementsData() { try { - if (!gameTypeId) { + if (!gameTypeId || !shouldLoadAchievements) { return; } const achievements = await getAchievements(Number(gameTypeId)); - setAchievements(achievements) + setAchievements(achievements); + setShouldLoadAchievements(false); return achievements; } catch (error: any) { console.log({ error }); @@ -128,7 +154,7 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) { } fetchAchievementsData(); - }, [gameTypeId]); + }, [gameTypeId, shouldLoadAchievements]); if (errorMessage) { return
{errorMessage}
; @@ -140,14 +166,46 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) { const handleEditButtonClick = () => { if(isUpdatingGameName && isValid) { - handleSubmit() + handleSubmit(); } else { - setIsUpdatingGameName(true) + setIsUpdatingGameName(true); } } + const openAchievementModal = (achievement?: IAchievement) => { + setSelectedAchievement(achievement); + setShowModal(true); + } + + const openLeaderboardModal = (leaderboard?: ILeaderboard) => { + setSelectedLeaderboard(leaderboard); + setShowLeaderboardModal(true); + } + + const handlePostSubmitAchievement = () => { + setSelectedAchievement(undefined); + setShowModal(false); + setShouldLoadAchievements(true) + } + + const handlePostSubmitLeaderboard = () => { + setSelectedLeaderboard(undefined); + setShowLeaderboardModal(false); + setShouldGameType(true) + } + + const handleCopyBtnClickSigningSecret = (value?: string) => { + navigator.clipboard.writeText(value ?? ""); + setHasCopiedSigninSecret(true); + } + + const handleCopyBtnClickClientSecret = (value?: string) => { + navigator.clipboard.writeText(value ?? ""); + setHasCopiedClientSecret(true); + } + return ( -
+ <>

{editMode ? "UPDATE GAME" : "NEW GAME"}

@@ -155,7 +213,7 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) {
- {isUpdatingGameName ? + {isUpdatingGameName || !editMode ? (
-
+ +
Client Secret - {currentGameType?.clientSecret} +
+ {currentGameType?.clientSecret} + handleCopyBtnClickClientSecret(currentGameType?.clientSecret)}>{hasCopiedClientSecret ? : } +
-
+
Signing Secret - {currentGameType?.signingSecret} +
+ {currentGameType?.signingSecret} + handleCopyBtnClickSigningSecret(currentGameType?.signingSecret)}>{hasCopiedSigninSecret ? : } +
@@ -191,114 +256,131 @@ const GameEditorPage = function GameEditorPage({ editMode }: IProps) {
-
+

Leaderboards

-
- - - - - - - - {currentGameType?._leaderboards?.map( - (leaderboard: ILeaderboard) => ( - - - - - - - - ) - )} + + + + + + + + + + + {currentGameType && currentGameType?._leaderboards?.map( + (leaderboard: ILeaderboard) => ( + + + + + + + + ) + )} +
idname - scoreStrategy - - resetStrategy - Edit
{leaderboard.id}{leaderboard.name} - {leaderboard.scoreStrategy} - - {leaderboard.resetStrategy} - - -
idname + scoreStrategy + + resetStrategy + Edit
navigate(`/games/${gameTypeId}/leaderboards/${leaderboard.id}`)}> + {leaderboard.id} + navigate(`/games/${gameTypeId}/leaderboards/${leaderboard.id}`)}> + {leaderboard.name} + navigate(`/games/${gameTypeId}/leaderboards/${leaderboard.id}`)}> + {leaderboard.scoreStrategy} + navigate(`/games/${gameTypeId}/leaderboards/${leaderboard.id}`)}> + {leaderboard.resetStrategy} + + +
-
+

Achievements

- +
- - - - - - - - - - {achievements?.map((achievement => - - - - - - - + + + + + + + + + + + + + {achievements && achievements?.map((achievement => + + + + + + + - ))} +
id - Description - - isEnabled - - targetValue - - createdAt - - updatedAt - Edit
{achievement.id}{achievement.description || "-"}{achievement.isEnabled ? "✅" : "❌"}{achievement.targetValue || "-"}{achievement.createdAt || "-"}{achievement.updatedAt || "-"}
id + Description + + isEnabled + + targetValue + + createdAt + + updatedAt + Edit
navigate(`/games/${gameTypeId}/achievements/${achievement.id}`)}> + {achievement.id} + navigate(`/games/${gameTypeId}/achievements/${achievement.id}`)}> + {achievement.description || "-"} + navigate(`/games/${gameTypeId}/achievements/${achievement.id}`)}> + {achievement.isEnabled ? : } + navigate(`/games/${gameTypeId}/achievements/${achievement.id}`)}> + {achievement.targetValue || "-"} + navigate(`/games/${gameTypeId}/achievements/${achievement.id}`)}> + {new Date(achievement?.createdAt || "").toLocaleString() } + navigate(`/games/${gameTypeId}/achievements/${achievement.id}`)}> + {new Date(achievement?.updatedAt || "").toLocaleString() } +
-
+ {showModal && } + {showLeaderboardModal && } + ); }; diff --git a/src/pages/LeaderboardRanksPage.tsx b/src/pages/LeaderboardRanksPage.tsx new file mode 100644 index 0000000..3f7295e --- /dev/null +++ b/src/pages/LeaderboardRanksPage.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { + getLeaderboardResults, +} from "../api/leaderboards"; +import Button from "../ui/Button"; + +const LeaderboardsPage = () => { + const [leaderboardResults, setLeaderboardResults] = useState(); + const navigate = useNavigate(); + const { gameTypeId, leaderboardId } = useParams<{ + gameTypeId: string; + leaderboardId: string; + }>(); + + useEffect(() => { + async function fetchLeaderboardResults() { + if (!gameTypeId || !leaderboardId) { + return; + } + + const leaderboardsRes = await getLeaderboardResults( + Number(gameTypeId), + Number(leaderboardId) + ); + + setLeaderboardResults(leaderboardsRes); + return leaderboardsRes; + } + + fetchLeaderboardResults(); + }, [gameTypeId, leaderboardId]); + + return ( +
+

LEADERBOARD RESULTS

+ + + + + + + + + + + + {leaderboardResults && + leaderboardResults?.map((ldr: ILeaderboardResult) => ( + + + + + + + ))} + +
idscore + User + Meta(JSON)
{ldr.id}{ldr.score} + {ldr._user?.email} + + {ldr._leaderboardResultsMeta?.map((meta: ILeaderboardResultMeta) => JSON.stringify(meta)+', ')} +
+ +
+ ); +}; + +export default LeaderboardsPage; diff --git a/src/pages/ListGamesPage.tsx b/src/pages/ListGamesPage.tsx index edb1766..6ff98f6 100644 --- a/src/pages/ListGamesPage.tsx +++ b/src/pages/ListGamesPage.tsx @@ -65,44 +65,35 @@ const ListGamesPage = function ListGamesPage(props: any) { {isLoading ? ( ) : ( -
+
{games.map((game: IGameType, index: number) => ( - <> -
- +
+
-
+
{game.name}
- - - - - - - + - - - - - - -
+
+
CLIENT SECRET -
+ +
{game.clientSecret.substring(0, maxKeyDisplayLength)} ... -
+
SIGNING SECRET -
+ +
{game.signingSecret.substring(0, maxKeyDisplayLength)} ... -
+
+
-
- ))}
)} diff --git a/src/types.d.ts b/src/types.d.ts index cbacded..3535b63 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -22,23 +22,51 @@ interface IGameType { } interface ILeaderboard { - id: number; + id?: number; + _gameTypeId: number; name: string; scoreStrategy: string; resetStrategy: string; + createdAt?: string; + updatedAt?: string; } -interface IAchievement extends SignInOut { - id: number; +interface ILeaderboardResult { + id?: number; + _leaderboardId: number; + _userId?: number; + _user?: IUser; + score: number; + _leaderboardResultsMeta?: ILeaderboardResultMeta[] + createdAt?: string; + updatedAt?: string; +} + +interface ILeaderboardResultMeta { + attribute?: string; + value?: string; +} + +interface IAchievement { + id?: number; _gameTypeId: number; description: string; - gameType: IGameType; + gameType?: IGameType; isEnabled: boolean; targetValue: number; - createdAt: string; + createdAt?: string; updatedAt: string; } +interface IAchievementUnlocked { + _achievementId: number; + _userId: number; + _user?: IUser; + progress: number; + createdAt?: string; + updatedAt?: string; +} + interface ITeam { id?: number; name: string; diff --git a/src/ui/AddEnemyToFloorModal/AddEnemyToFloorModal.tsx b/src/ui/AddEnemyToFloorModal/AddEnemyToFloorModal.tsx index aa2fae9..ec31041 100644 --- a/src/ui/AddEnemyToFloorModal/AddEnemyToFloorModal.tsx +++ b/src/ui/AddEnemyToFloorModal/AddEnemyToFloorModal.tsx @@ -1,4 +1,7 @@ -import { useState } from "react"; +import { groupBy } from "lodash"; +import { useEffect, useState } from "react"; +import { AiOutlineDelete } from "react-icons/ai"; +import { toast } from "react-toastify"; import { updateFloor } from "../../api/admin"; import { emojiToImageTag } from "../../helpers/emojiHelper"; import Button from "../Button"; @@ -6,7 +9,7 @@ import Modal from "../Modal"; interface IProps { show: boolean; - onClose: () => void; + onClose: (reload:boolean) => void; floor: ITowerFloor | null; allEnemies: IEnemy[]; allEmoji: IAllEmoji; @@ -21,20 +24,27 @@ const AddEnemyToFloorModal = ({ }: IProps) => { const [floorEnemies, setFloorEnemies] = useState([]); - if (!floor) { - return null; - } - - const handleOnSaveButtonClick = () => { - const floorId = floor.id; - const enemyIds = floorEnemies.map((enemy) => enemy.id) as number[]; - - if (!floorId || !enemyIds) { - return; + const handleOnSaveButtonClick = async () => { + try{ + const floorId = floor?.id; + const enemyIds = floorEnemies.map((enemy) => enemy.id) as number[]; + + if (!floorId || !enemyIds) { + return; + } + await updateFloor(floorId, { + enemyIds, + }); + onClose(true); + setFloorEnemies([]); + toast('Floor edited successfuly.', { + type: 'success', + }); + } catch(err: any) { + toast(`Error adding enemies to floor. ${err?.message} `, { + type: 'error', + }) } - updateFloor(floorId, { - enemyIds, - }); }; const handleOnAddEnemyClick = @@ -51,24 +61,41 @@ const AddEnemyToFloorModal = ({ setFloorEnemies(newFloorEnemiesArray); }; + const handleCloseModal = () => { + onClose(false); + setFloorEnemies([]); + } + + useEffect(() => { + if(floor?._floorEnemies) { + const floorEnemies = floor._floorEnemies?.map( + (floorEnemy) => floorEnemy._enemy + ); + setFloorEnemies(floorEnemies); + } + }, [floor?._floorEnemies]); + + const groupedByEnemies = groupBy(floorEnemies, "name"); + return (
- +

- Add Enemy to Floor {floor.number} + Edit Enemies on Floor {floor?.number}

-
-
+
+

All enemies

-
+
{allEnemies.map((enemy) => { return ( {emojiToImageTag( enemy.emoji, @@ -80,28 +107,27 @@ const AddEnemyToFloorModal = ({ })}
-
+

Floor Enemies

-
- {floorEnemies.map((enemy) => { - return ( - - {emojiToImageTag( - enemy.emoji, - allEmoji, - "h-12 w-12" - )} - - ); - })} +
+ {Object.keys(groupedByEnemies).map((enemyKey) => ( + + {emojiToImageTag( + groupedByEnemies[enemyKey][0].emoji, + allEmoji, + "h-12 w-12", + groupedByEnemies[enemyKey].length, + )} + + ))}
+ {floorEnemies && floorEnemies.length > 0 && setFloorEnemies([])}>}
diff --git a/src/ui/AddOrEditAchievementModal/AddOrEditAchievementModal.tsx b/src/ui/AddOrEditAchievementModal/AddOrEditAchievementModal.tsx new file mode 100644 index 0000000..c5427b3 --- /dev/null +++ b/src/ui/AddOrEditAchievementModal/AddOrEditAchievementModal.tsx @@ -0,0 +1,134 @@ +import { useFormik } from "formik"; +import { useEffect } from "react"; +import * as Yup from "yup"; +import { useParams } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { upsertAchievement } from "../../api/achievements"; +import Button from "../Button"; +import Checkbox from "../Checkbox"; +import Modal from "../Modal"; +import TextInput from "../TextInput"; + +interface IProps { + show: boolean; + onClose: () => void; + selectedAchievement?: IAchievement; +} + +interface IAchievementForm { + id?: number; + description: string; + targetValue: number; + isEnabled: boolean; +} + +const AddOrEditAchievementModal = ({ + show, + onClose, + selectedAchievement, +}: IProps) => { + const { gameTypeId } = useParams<{ gameTypeId: string }>(); + + const onSubmit = async (values: IAchievementForm) => { + try{ + await upsertAchievement({ + ...(selectedAchievement?.id && { id: selectedAchievement?.id }), + _gameTypeId: selectedAchievement?._gameTypeId || parseInt(gameTypeId || ""), + ...(selectedAchievement?.createdAt && { createdAt: selectedAchievement?.createdAt }), + description: values.description, + targetValue: values.targetValue, + isEnabled: values.isEnabled, + updatedAt: new Date().toString(), + }); + onClose(); + toast('Achievement successfully saved.',{ + type: 'success', + }); + } catch (err: any) { + toast(`Error : ${err.message}`, { + type: "error", + }); + } + } + + const validationSchema = Yup.object({ + description: Yup.string().required().label("Desctiption"), + targetValue: Yup.number().min(1).required().label("Target Value"), + }); + + const initialForm: IAchievementForm = { + description: "", + targetValue: 0, + isEnabled: true, + }; + + const { + getFieldProps, + getFieldMeta, + handleSubmit, + isValid, + setValues, + } = useFormik({ + initialValues: initialForm, + onSubmit, + validationSchema, + }); + + useEffect(() => { + setValues({ + description: selectedAchievement?.description || "", + targetValue: selectedAchievement?.targetValue || 0, + isEnabled: selectedAchievement?.isEnabled || true, + }); + }, [selectedAchievement, setValues]) + + return ( +
+ +

+ {selectedAchievement?.id ? 'Edit' : 'New'} Achievement +

+
+
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+
+
+ ); +}; + +export default AddOrEditAchievementModal; diff --git a/src/ui/AddOrEditAchievementModal/index.tsx b/src/ui/AddOrEditAchievementModal/index.tsx new file mode 100644 index 0000000..10356e0 --- /dev/null +++ b/src/ui/AddOrEditAchievementModal/index.tsx @@ -0,0 +1,3 @@ +import AddOrEditAchievementModal from "./AddOrEditAchievementModal"; + +export default AddOrEditAchievementModal; diff --git a/src/ui/AddOrEditLeaderboardModal/AddOrEditLeaderboardModal.tsx b/src/ui/AddOrEditLeaderboardModal/AddOrEditLeaderboardModal.tsx new file mode 100644 index 0000000..0268adb --- /dev/null +++ b/src/ui/AddOrEditLeaderboardModal/AddOrEditLeaderboardModal.tsx @@ -0,0 +1,144 @@ +import { useFormik } from "formik"; +import { useEffect } from "react"; +import * as Yup from "yup"; +import { useParams } from "react-router-dom"; + +import { upsertLeaderboard } from "../../api/leaderboards"; +import Button from "../Button"; +import Modal from "../Modal"; +import TextInput from "../TextInput"; +import Dropdown from "../Dropdown"; +import { + resetStrategies, + scoreStrategies, +} from "../../utils/leaderboardStrategies"; +import { toast } from "react-toastify"; + +interface IProps { + show: boolean; + onClose: () => void; + selectedLeaderboard?: ILeaderboard; +} + +interface ILeaderboardForm { + id?: number; + name: string; + scoreStrategy: string; + resetStrategy: string; +} + +const AddOrEditLeaderboardModal = ({ + show, + onClose, + selectedLeaderboard, +}: IProps) => { + const { gameTypeId } = useParams<{ gameTypeId: string }>(); + + const onSubmit = async (values: ILeaderboardForm) => { + try{ + await upsertLeaderboard({ + ...(selectedLeaderboard?.id && { id: selectedLeaderboard?.id }), + _gameTypeId: + selectedLeaderboard?._gameTypeId || parseInt(gameTypeId || ""), + name: values.name, + scoreStrategy: values.scoreStrategy.toLowerCase(), + resetStrategy: values.resetStrategy.toLowerCase(), + }); + onClose(); + toast('Leaderboard successfully saved.',{ + type: 'success', + }); + + } catch (err: any) { + toast(`Error : ${err?.message}`, { + type: "error", + }); + } + }; + + const validationSchema = Yup.object({ + name: Yup.string().required().label("Desctiption"), + scoreStrategy: Yup.string().required().label("Score Strategy"), + resetStrategy: Yup.string().required().label("Reset Strategy"), + }); + + const initialForm: ILeaderboardForm = { + name: "", + scoreStrategy: "Highest", + resetStrategy: "Daily", + }; + + const { getFieldProps, getFieldMeta, handleSubmit, isValid, setValues } = + useFormik({ + initialValues: initialForm, + onSubmit, + validationSchema, + }); + + useEffect(() => { + setValues({ + name: selectedLeaderboard?.name || "", + scoreStrategy: selectedLeaderboard?.scoreStrategy || "Highest", + resetStrategy: selectedLeaderboard?.resetStrategy || "Daily", + }); + }, [selectedLeaderboard, setValues]); + + return ( +
+ +

+ { selectedLeaderboard?.id ? 'Edit' : 'New'} Leaderboard +

+
+
+ +
+ +
+ + {scoreStrategies.map((scoreStrategy) => ( + +
+ +
+ + {resetStrategies.map((resetStrategy) => ( + +
+
+ +
+
+
+
+ ); +}; + +export default AddOrEditLeaderboardModal; diff --git a/src/ui/AddOrEditLeaderboardModal/index.tsx b/src/ui/AddOrEditLeaderboardModal/index.tsx new file mode 100644 index 0000000..933943c --- /dev/null +++ b/src/ui/AddOrEditLeaderboardModal/index.tsx @@ -0,0 +1,3 @@ +import AddOrEditLeaderboardModal from "./AddOrEditLeaderboardModal"; + +export default AddOrEditLeaderboardModal; diff --git a/src/ui/Button/Button.tsx b/src/ui/Button/Button.tsx index c9bd463..e9ff503 100644 --- a/src/ui/Button/Button.tsx +++ b/src/ui/Button/Button.tsx @@ -3,14 +3,15 @@ interface IProps { disabled?: boolean; onClick?: () => void; type?: "button" | "submit" | "reset" | undefined; + fullWidth?: boolean; } const calculateClasses = (props: IProps) => { if (props.disabled) { - return "bg-gray-100 text-sm text-gray-400 py-1.5 px-5 rounded-sm cursor-not-allowed"; + return `bg-gray-100 text-sm text-gray-400 py-1.5 px-5 rounded-sm cursor-not-allowed ${props.fullWidth ? "w-full" : ""}`; } - return "bg-xteamaccent text-sm text-white py-1.5 px-5 rounded-sm"; + return `bg-xteamaccent text-sm text-white py-1.5 px-5 rounded-sm ${props.fullWidth ? "w-full" : ""}`; }; export default function Button(props: IProps) { diff --git a/src/ui/Dropdown/Dropdown.tsx b/src/ui/Dropdown/Dropdown.tsx index a1dc15e..15e0210 100644 --- a/src/ui/Dropdown/Dropdown.tsx +++ b/src/ui/Dropdown/Dropdown.tsx @@ -5,6 +5,7 @@ interface IProps { label: string; hasErrors?: boolean; touched?: boolean; + fullWidth?: boolean; children?: React.ReactNode; } @@ -13,6 +14,7 @@ function TextInput({ fieldProps, label, children, + fullWidth, hasErrors, touched, }: IProps) { @@ -26,7 +28,8 @@ function TextInput({ diff --git a/src/ui/TextInput/TextInput.tsx b/src/ui/TextInput/TextInput.tsx index 86038ac..ee598d0 100644 --- a/src/ui/TextInput/TextInput.tsx +++ b/src/ui/TextInput/TextInput.tsx @@ -6,6 +6,7 @@ interface IProps { type?: "text" | "number" | "password"; extraClass?: string; helperText?: string; + fullWidth?: boolean; } const renderInputError = (error?: string, touched?: boolean) => { @@ -23,7 +24,7 @@ function TextInput({ type, label, extraClass, - + fullWidth, ...props }: IProps) { const calculateColors = () => { @@ -54,7 +55,7 @@ function TextInput({ diff --git a/src/utils/leaderboardStrategies.ts b/src/utils/leaderboardStrategies.ts new file mode 100644 index 0000000..12a99e4 --- /dev/null +++ b/src/utils/leaderboardStrategies.ts @@ -0,0 +1,4 @@ + +export const scoreStrategies = ["Highest", "Lowest", "Sum", "Latest"]; + +export const resetStrategies = ["Daily", "Weekly", "Monthly", "Never" ]; \ No newline at end of file