diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..3e212e1 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,21 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md index 34ff007..587adfc 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,3 @@ -# frontend-adaptive-learning +# React + Vite - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.com/profile-image/kedaireka/polinema-adapative-learning/frontend-adaptive-learning.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.com/profile-image/kedaireka/polinema-adapative-learning/frontend-adaptive-learning/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README - -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. diff --git a/index.html b/index.html new file mode 100644 index 0000000..bc45ea0 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + Adaptive English Learning + + + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fbcc8a4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6149 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@ckeditor/ckeditor5-build-classic": "^43.2.0", + "@ckeditor/ckeditor5-react": "^9.3.1", + "axios": "^1.7.7", + "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", + "file-saver": "^2.0.5", + "jwt-decode": "^4.0.0", + "mammoth": "^1.8.0", + "react": "^18.3.1", + "react-bootstrap": "^2.10.4", + "react-dom": "^18.3.1", + "react-loading-skeleton": "^3.5.0", + "react-router-dom": "^6.26.0", + "react-select": "^5.8.1", + "react-sortablejs": "^6.1.4" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "vite": "^5.3.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "dependencies": { + "@babel/types": "^7.25.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ckeditor/ckeditor5-adapter-ckfinder": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-43.2.0.tgz", + "integrity": "sha512-RiZXc6l05yfkkSyJRxyDAnqqUxMXHboZxooptBUweUm1ofXltyaGkFa5D4kBMS5prrAEXrdpJ9uMZPvjY2BF7g==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-upload": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-alignment": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-43.2.0.tgz", + "integrity": "sha512-Yhh+1FmmcycBtFRX3nqULe/EiF5Y28eLEkmdus16jhfImtMFLzt344+SrcTkd2uQfC4U+yVlxmOo6/lbR1R7Qw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-autoformat": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-43.2.0.tgz", + "integrity": "sha512-8b/POJT08kR9eDeZXIEiU0CKIBanusbnSku+a//63hrW2urzS3F9umKsD8Y1I/xoKGm/ew4yVtncQRaCUEE7lA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-autosave": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-43.2.0.tgz", + "integrity": "sha512-ZDpWibRaXmwsFNPE0Q3Mc93yarR+zcgiCpEW5oZh68vUdR1aL63D3GqGI3ok4EgFKsCTQWHpB1WejyUJecVcMw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-basic-styles": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-43.2.0.tgz", + "integrity": "sha512-v1BS3JtD+6fAPx23Sbr+IZ39RAaiIoISursInuGTWU+kJ3RDh7fByKEt4A8kia4ZfSd1b469i/4LOiikN+jfxQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-block-quote": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-43.2.0.tgz", + "integrity": "sha512-0T5zy3vitecSWaRd7uhxvyHLLVJUutpeJHMqupKPZaOJlgJOqa9hFOFsK2t4y8EVTtLTx5UfFzTudK449kFnBg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-build-classic": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-build-classic/-/ckeditor5-build-classic-43.2.0.tgz", + "integrity": "sha512-IZX6Rf2EHdKB+9yH31mItaqDIP+KASO/Za3pWaG44lRvDQsjPB+3/ee6W+6fGv85asfixxrdA3DCq9bxPjfmQQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-adapter-ckfinder": "43.2.0", + "@ckeditor/ckeditor5-autoformat": "43.2.0", + "@ckeditor/ckeditor5-basic-styles": "43.2.0", + "@ckeditor/ckeditor5-block-quote": "43.2.0", + "@ckeditor/ckeditor5-ckbox": "43.2.0", + "@ckeditor/ckeditor5-ckfinder": "43.2.0", + "@ckeditor/ckeditor5-cloud-services": "43.2.0", + "@ckeditor/ckeditor5-easy-image": "43.2.0", + "@ckeditor/ckeditor5-editor-classic": "43.2.0", + "@ckeditor/ckeditor5-essentials": "43.2.0", + "@ckeditor/ckeditor5-heading": "43.2.0", + "@ckeditor/ckeditor5-image": "43.2.0", + "@ckeditor/ckeditor5-indent": "43.2.0", + "@ckeditor/ckeditor5-link": "43.2.0", + "@ckeditor/ckeditor5-list": "43.2.0", + "@ckeditor/ckeditor5-media-embed": "43.2.0", + "@ckeditor/ckeditor5-paragraph": "43.2.0", + "@ckeditor/ckeditor5-paste-from-office": "43.2.0", + "@ckeditor/ckeditor5-table": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-ckbox": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-43.2.0.tgz", + "integrity": "sha512-HSjYz2fYA2iJhua3wExApBYKz6k6AuSZmm2CG/X7cYFvq44OCIuOOkjqSABcByvVAtzOUerqWhurwBXTp/QrUg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-upload": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "blurhash": "2.0.5", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-ckfinder": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-43.2.0.tgz", + "integrity": "sha512-GOMI/FTTGglQBxVIGMxQwVIDD+gCQenegjMlol2eCDsys3td5mtzp0sEYPXCHeJY8u/R28K3ySWvKlrp8YBQ0Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-clipboard": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-43.2.0.tgz", + "integrity": "sha512-109dffphyvUEhdGDP7GIj6zEHb493QNVEY6Rbl8o9Q0Ia+AAmDGX3VWFewiFC325tWaeVW72wwXXeaCSLRpt2w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-cloud-services": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-43.2.0.tgz", + "integrity": "sha512-bcnklICj88ZNXTnjHdXt74zsxk9RBK1KXIZXKMT2K8NLZQZkppnzPICjDfFJ31BvcvYmFE3mKuVNeLnsofyezQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-code-block": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-43.2.0.tgz", + "integrity": "sha512-tNgqpcgigruSdbaJnl08BpOYkKs9mFA284hsTyptmxOloq/igEBvSgBfJDchUGVT0Lm78hAlcirdVKrwku5Eew==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-core": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-core/-/ckeditor5-core-43.2.0.tgz", + "integrity": "sha512-LLyDuNQdTC+P1E91SMAPGNTpDOLwsQK4OayO/qUS1sDxqWfv/YFxEpN/vBXEHbg5Q3LS2wW1HR6a4o/w7Wwt8w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-watchdog": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-easy-image": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-43.2.0.tgz", + "integrity": "sha512-mRIU5GRVTwv2uMHdoP4p/Jv6lgusmOBUp7p2PkmEEyo1MBG7t+b76rMWePFfBZKmXFmdO8lATJg/SGUNK7JIyg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-upload": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-balloon": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-43.2.0.tgz", + "integrity": "sha512-KtSX8mZTHphUMn7Uf9S3lRDBJI+m5POVvvCRFYDo+AsPO6FN72hZslpseOlAVTcJ0FIYM4aiqAFg1jojeoDyqQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-classic": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-43.2.0.tgz", + "integrity": "sha512-D07TxNnJb0qSKe3QicNsaZh28tIXEjmrib4Gnbc2McWi7nDYoxoF1dyUygxFR4OHQ6DXfYU4VUW9EKlJ8ZfSAg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-decoupled": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-43.2.0.tgz", + "integrity": "sha512-2vgfOd7sqptBRu6de0qq+hwQd7BuUu1rgfbuWB/msa2zMxwKLNoWwYNFz0N9Mm7b49jaMkQYjaOYFh1zE7BEMQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-inline": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-43.2.0.tgz", + "integrity": "sha512-MCb5ljlr4Jp9pYtKAUhLbtOcxHuiMOOPWCrwYmpDogzBss//G2+LILqWqal3b6YUm2WSm37NorGQ+KdPmOh5ow==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-multi-root": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-43.2.0.tgz", + "integrity": "sha512-nQcYWsetSG2h5NZOWuHf90VTxE82KBqetO3S0emMmlUstGmVk69KMlQDxU6UZhbB5NptVsgMSG2Y9Cd8XnrO0A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-engine": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-43.2.0.tgz", + "integrity": "sha512-fTrhFe+qUFZ+mvRd6KrvDzXAdMoyE44P4640iU7aOoqnlf2Z7D9wQN7ak+ysRKuccIt9t6Tidl8pmT3BRoBGfA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-utils": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-enter": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-43.2.0.tgz", + "integrity": "sha512-uFnexEaYyOYgekrpPgbA6tCiEPEXOtr1AoATVlIzy2Kb0SfJkfUyciV8559+tK9w16zI4tgMYJrwt0ktg55EYA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-essentials": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-43.2.0.tgz", + "integrity": "sha512-Nb0utwH+j4Kqn8OBzcHYBexJAnlJUMC3jrLnVW2mqbd71HTRFozJ6/MDdX1gIUoQswhl6pVZRLmly26HEubUPQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-select-all": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-undo": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-find-and-replace": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-43.2.0.tgz", + "integrity": "sha512-MKN4rEeq/RWi+++dLBIA/b9tacEd7pnFM8mewLDsehT1RPvWPeGy2e5dN6ugi5zn3It7UcIdkCQE7GLmsLGGBA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-font": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-font/-/ckeditor5-font-43.2.0.tgz", + "integrity": "sha512-3BUhy3AtB+SGiLA3ZsX3+JAhD7KmexEwfGKgrX4kDs9iMOg7xVXXHbdeJGEGV8oJc4hNgp34lt3lpFtCApBNgw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-heading": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-43.2.0.tgz", + "integrity": "sha512-nGvzMYN+2SRHxe99YhFwRpqw44W2lHbFNuWp0YiV3iYSgpQPwATFFpLXvyOMXOKY4dcob2KiDcogfWVoFxmMdQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-paragraph": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-highlight": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-43.2.0.tgz", + "integrity": "sha512-9zhf80TyheMxUXXScjSstVUAZMjeDs/SCbhWYwFR/ZZN3Vyhp7kD+WhyyezueHQLyPNiLZNzer7LQ/MB3b+8Lg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-horizontal-line": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-43.2.0.tgz", + "integrity": "sha512-d92LsTiOSsHEHeQbWJz5xqj+yYRbo1xiz2bix0cN1BoEsm6iEFJKUPewvh48cISdkw5RPKfDrfzbiBCDZsMlEw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-html-embed": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-43.2.0.tgz", + "integrity": "sha512-F55r0UQy53cKlWWGRcYTjrYpQd86jkHEqk901uC0FJdBwpLqV6ZDxCb+w5dMQ6cUh5oYKuILeu0ZZ0KF1C+HQA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-html-support": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-43.2.0.tgz", + "integrity": "sha512-VOjT73VbtiBLy/Qsn9aWib9LhkfXZSbfAHSttIsW3Y8v2am827uf4dL2Y2pop3pcXJdoB+LVGiTFdk1mBDvAFg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-image": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-image/-/ckeditor5-image-43.2.0.tgz", + "integrity": "sha512-UZVd9sZ9nuG3kZUUUgXzqTkT7YSZs4wvu98NuSgBC3T6l0UlJjdf//GQa1estxNDDc+yCjRk02u+sbHW+eUY0A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-undo": "43.2.0", + "@ckeditor/ckeditor5-upload": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-indent": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-43.2.0.tgz", + "integrity": "sha512-hzqAXOlxblaWNQ9eAGP/30kMLk+mMPES2/02B6QmI/CSYgwhXK1FVSTfZN0u6Cw94lWQ+EJr7riP2LCc85Rvfw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-integrations-common": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-integrations-common/-/ckeditor5-integrations-common-2.1.0.tgz", + "integrity": "sha512-vn6qMb36sl6eSCc27dvThk6xISif59MxnxZmRBC440TyP7S9ZcS0ai4yHd5QyaH70ZIe0lhS7DWdLaiKtBggVQ==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "ckeditor5": ">=42.0.0 || ^0.0.0-nightly" + } + }, + "node_modules/@ckeditor/ckeditor5-language": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-language/-/ckeditor5-language-43.2.0.tgz", + "integrity": "sha512-NSL9E0ROyffTHGKyIpqD27NclOXDhAFO8L9Z9kghqESNsCdOZJKKme+EK376r5gWHsiBNnKZ/5yQOUrGwATtuw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-link": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-link/-/ckeditor5-link-43.2.0.tgz", + "integrity": "sha512-PyU3bPyCzvNEp/7Hwx4oxuPSRN7ptaDuBe+Jhlz70PWegtANOUPvMIYlcZBB2E20Ruo0ukvrRRR0teqqFKHLug==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-list": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-list/-/ckeditor5-list-43.2.0.tgz", + "integrity": "sha512-HljK5Ew3fgPX/FYiK0ieuGIrjCqiNeVG825UaAeuRHkNm1QgCBF0xQ1fsaiJw7/lTXfPA5KhzD3ezEwI1qWytQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-markdown-gfm": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-43.2.0.tgz", + "integrity": "sha512-TJklEGxL7tTm8OfLFAEWDsKRD4TxgSbai45CvuXNuoSnwcUWsXYhsBT8kUD2zAv8zTlh7gy3tFnuhKnLu157uQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "ckeditor5": "43.2.0", + "marked": "4.0.12", + "turndown": "7.2.0", + "turndown-plugin-gfm": "1.0.2" + } + }, + "node_modules/@ckeditor/ckeditor5-media-embed": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-43.2.0.tgz", + "integrity": "sha512-N+MJUAC7+KPi3CaplVGLmA26W2GEFEukKxKDpjDbpBBgDgwyrJlpfFIOWIvrk+6J1QOwS4yMU+H+1aGypIgd+Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-undo": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-mention": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-43.2.0.tgz", + "integrity": "sha512-psEgMErFg6cKdEh2cM02tB/s7QJz+g5LlXCQ0k6OaNa/V7zO/qcT/pChrZ/13Mu2dgRKZqBUeeAG4aUXHN7QFQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-minimap": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-43.2.0.tgz", + "integrity": "sha512-rKECbba7QZtb8Kg5znYSez3y63RfX+19TvMvRwmiNw1aiNluyG04ykC0BBvejavO8Kz/EuNrliNMM3h44Og6jg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-page-break": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-43.2.0.tgz", + "integrity": "sha512-ukHpucDP0hskHEV1ql9G55dFiNCamI87vuHdLvEy0x/Th89C2ITswxfQAxMbZb1W0Vg93eMKyYf8pbg+YvcjwQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-paragraph": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-43.2.0.tgz", + "integrity": "sha512-Gi7Plu75rlwiV55K1V06iVdPAGOn1FKX2JPgK+eTfYe2uZeJlJWkjvkl8x32IxJxT9Z8V1yECcg/I8A0J4IdlA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-paste-from-office": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-43.2.0.tgz", + "integrity": "sha512-+jPVd79p1oyuIcW5Pq4LsxAN5ZxjtJaasx2flGi9mLMFaLxnO/JteFs0rH7BAavURrxdhvXuIosYMGfPWleGLw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-react": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-react/-/ckeditor5-react-9.3.1.tgz", + "integrity": "sha512-2lc1ICGCOZ0loC6DeMFwhkhrodLYUsOnC2wdgMiaXnEWRI/fU0SWBAoLbsMH7i6zpq29s+ZWMEImRVbly8SmEA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-integrations-common": "^2.1.0", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "ckeditor5": ">=42.0.0 || ^0.0.0-nightly", + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@ckeditor/ckeditor5-remove-format": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-43.2.0.tgz", + "integrity": "sha512-BrtlTWrvg4hcbPvTck9bqIVtDfbanPYGvh+qf3NCX4USxqzAs+zz3tT105JUAY0FF0Qngcvjz6gTfTTwXom//g==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-restricted-editing": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-43.2.0.tgz", + "integrity": "sha512-RKYLNbbKks0O6axiE0acYOq5IAdtRGhCC557szUSzFZsGh31LHRPxa7ZpwjKuuefUp4uI11HzFyXXBQfykdnDQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-select-all": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-43.2.0.tgz", + "integrity": "sha512-wHFKg/7UsxmR6YBKZnF+4kS8m0cDe+r4IotVcnJcxDRpIuHDNlROKohdMrKdBl6I++6nwvkD3da0Dsxd9gJ8Sw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-show-blocks": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-43.2.0.tgz", + "integrity": "sha512-VFXRvDRZvTX+t+lUxdK6sGPy/Lqu5h2OIYnTHykknwwySunOH1gCUjo1iVs0mrCKLwhmcp8fOeQjjdL811xcXQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-source-editing": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-43.2.0.tgz", + "integrity": "sha512-b8/PGBybNp//2thS2Objsl7Q+YZ7JHhVZhqH2T27cEIkFlMOag5lfA0Rpz8ClrNxY4MDu/0ArHyjFG8TkjPkvA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-theme-lark": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-special-characters": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-43.2.0.tgz", + "integrity": "sha512-VHe2MHFUwxFMSo/RKDqLF18dobaIMQoGnMWqZtWi5maQ1xs/l1Mo0Ohh+tdFYCQq97lNuZ4Z3z/FravZAlqV7A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-style": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-style/-/ckeditor5-style-43.2.0.tgz", + "integrity": "sha512-24Gk9iWOtiN1YaJh24QNSjafFMeidmW6NbtaldVibLKwJRFD2oNXvqccSGztj/+cv0FLK8rK1KHFYcm4WSCT3w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-table": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-table/-/ckeditor5-table-43.2.0.tgz", + "integrity": "sha512-sIA1Ik8shdrsy2sgDXLX7uZgPuNTEgZ0/H9/aKDX7cg8Z+vQozhElHb1H17oC5a1qoxwQF+qt8FoLgtguZ5gDQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-theme-lark": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-43.2.0.tgz", + "integrity": "sha512-K6KH0Wakzuf43XFmLN49qu1svwpb2FDP/wM+Vo+UolI3krRRxr6uTXQ9D3O8S4ckMJhwcrVRerBHtlZst0dXew==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-ui": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-typing": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-43.2.0.tgz", + "integrity": "sha512-IfuEYE9PonfxNe6RENtMIRqeN/ytX64781EVVhVl5FOebNFGKHscw+j0cCrqDGPGq7yVVvehEdCrl2xAXy+gyw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-ui": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-43.2.0.tgz", + "integrity": "sha512-sGWW4tqGvs7VvZJHZG2qLYBV3+fU4yLiZcLLG6zDU+RrK6rS2cndspjyBIDj94gdFA2tEXqMkteZeEekPrtLIw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "color-convert": "2.0.1", + "color-parse": "1.4.2", + "lodash-es": "4.17.21", + "vanilla-colorful": "0.7.2" + } + }, + "node_modules/@ckeditor/ckeditor5-ui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@ckeditor/ckeditor5-ui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@ckeditor/ckeditor5-undo": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-43.2.0.tgz", + "integrity": "sha512-BXApTSSicRIeKReYt3mla9IQfEpgSOFJjtC0jvHbfsVcC9xvo6B0Fxu9DhTzkXFasZtZvCdOqPCSF3oulqJGxQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-upload": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-43.2.0.tgz", + "integrity": "sha512-KPyXPCFTKQxjuwmyk3vgUoXTuBJctH4U67LdarsplwszOOS0Ho89bExY3VOQ5aGB7y7mk4oOS9tSKWyt64ASIg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0" + } + }, + "node_modules/@ckeditor/ckeditor5-utils": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-43.2.0.tgz", + "integrity": "sha512-0Q2Yj22+a2lcj+YHqe7JOmJANVjmDqAGOwjfYRUoZGXefb6yuEzEpzDin4rU/Msrnll1KrH+mD73HSxUgmgi1Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-watchdog": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-43.2.0.tgz", + "integrity": "sha512-hQ6+8uGJekGkPiAW6+DzhvJNXzo7SKaS+rRcI7ERm9O6CSP/vFyc77uw5Y1SDr1PlzF/bdWksEv5zqxiebQW0Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-widget": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-43.2.0.tgz", + "integrity": "sha512-qWyh4ZRvEmz+prHSx+oIaEIqS4jO1UcCFgmC/cuVp3jSXovBgbkSK1G8FqNdf+JHjc2hrZnfKm3Tb4N8OmEbLg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@ckeditor/ckeditor5-word-count": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-43.2.0.tgz", + "integrity": "sha512-gp9hHmOGStvA6wpSY4h0LwqTGYSdMRZ8/8XnUFHQ4QiKirejzmHA9K2lWL4grqCTZh5sfO7ZzaX1mx/QctpFxw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0", + "lodash-es": "4.17.21" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", + "license": "BSD-2-Clause" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.5.tgz", + "integrity": "sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.8.0.tgz", + "integrity": "sha512-xJEOXUOTmT4FngTmhdjKFRrVVF0hwCLNPdatLCHkyS4dkiSK12cEu1Y0fjxktjJrdst9jJIc5J6ihMJCoWEN/g==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/helpers": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.12.tgz", + "integrity": "sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/sortablejs": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz", + "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", + "license": "MIT" + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ] + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001646", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz", + "integrity": "sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ckeditor5": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/ckeditor5/-/ckeditor5-43.2.0.tgz", + "integrity": "sha512-wtW2TICJiXoOPK2K4L1oiO+HWBS6ifVQW2k80DoQI7cDranUC0FKMNirYZZXNdK0SYH87RSLZR8hHAAdbbOPBA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ckeditor/ckeditor5-adapter-ckfinder": "43.2.0", + "@ckeditor/ckeditor5-alignment": "43.2.0", + "@ckeditor/ckeditor5-autoformat": "43.2.0", + "@ckeditor/ckeditor5-autosave": "43.2.0", + "@ckeditor/ckeditor5-basic-styles": "43.2.0", + "@ckeditor/ckeditor5-block-quote": "43.2.0", + "@ckeditor/ckeditor5-ckbox": "43.2.0", + "@ckeditor/ckeditor5-ckfinder": "43.2.0", + "@ckeditor/ckeditor5-clipboard": "43.2.0", + "@ckeditor/ckeditor5-cloud-services": "43.2.0", + "@ckeditor/ckeditor5-code-block": "43.2.0", + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-easy-image": "43.2.0", + "@ckeditor/ckeditor5-editor-balloon": "43.2.0", + "@ckeditor/ckeditor5-editor-classic": "43.2.0", + "@ckeditor/ckeditor5-editor-decoupled": "43.2.0", + "@ckeditor/ckeditor5-editor-inline": "43.2.0", + "@ckeditor/ckeditor5-editor-multi-root": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-essentials": "43.2.0", + "@ckeditor/ckeditor5-find-and-replace": "43.2.0", + "@ckeditor/ckeditor5-font": "43.2.0", + "@ckeditor/ckeditor5-heading": "43.2.0", + "@ckeditor/ckeditor5-highlight": "43.2.0", + "@ckeditor/ckeditor5-horizontal-line": "43.2.0", + "@ckeditor/ckeditor5-html-embed": "43.2.0", + "@ckeditor/ckeditor5-html-support": "43.2.0", + "@ckeditor/ckeditor5-image": "43.2.0", + "@ckeditor/ckeditor5-indent": "43.2.0", + "@ckeditor/ckeditor5-language": "43.2.0", + "@ckeditor/ckeditor5-link": "43.2.0", + "@ckeditor/ckeditor5-list": "43.2.0", + "@ckeditor/ckeditor5-markdown-gfm": "43.2.0", + "@ckeditor/ckeditor5-media-embed": "43.2.0", + "@ckeditor/ckeditor5-mention": "43.2.0", + "@ckeditor/ckeditor5-minimap": "43.2.0", + "@ckeditor/ckeditor5-page-break": "43.2.0", + "@ckeditor/ckeditor5-paragraph": "43.2.0", + "@ckeditor/ckeditor5-paste-from-office": "43.2.0", + "@ckeditor/ckeditor5-remove-format": "43.2.0", + "@ckeditor/ckeditor5-restricted-editing": "43.2.0", + "@ckeditor/ckeditor5-select-all": "43.2.0", + "@ckeditor/ckeditor5-show-blocks": "43.2.0", + "@ckeditor/ckeditor5-source-editing": "43.2.0", + "@ckeditor/ckeditor5-special-characters": "43.2.0", + "@ckeditor/ckeditor5-style": "43.2.0", + "@ckeditor/ckeditor5-table": "43.2.0", + "@ckeditor/ckeditor5-theme-lark": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-undo": "43.2.0", + "@ckeditor/ckeditor5-upload": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "@ckeditor/ckeditor5-watchdog": "43.2.0", + "@ckeditor/ckeditor5-widget": "43.2.0", + "@ckeditor/ckeditor5-word-count": "43.2.0" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-parse": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.2.tgz", + "integrity": "sha512-RI7s49/8yqDj3fECFZjUI1Yi0z/Gq1py43oNJivAIIDSyJiOZLfYCRQEgn8HEVAj++PcRe8AnL2XF0fRJ3BTnA==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/dingbat-to-unicode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", + "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/duck": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", + "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", + "dependencies": { + "underscore": "^1.13.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", + "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz", + "integrity": "sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "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==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lop": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", + "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", + "dependencies": { + "duck": "^0.1.12", + "option": "~0.2.1", + "underscore": "^1.13.1" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mammoth": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.8.0.tgz", + "integrity": "sha512-pJNfxSk9IEGVpau+tsZFz22ofjUsl2mnA5eT8PjPs2n0BP+rhVte4Nez6FdgEuxv3IGI3afiV46ImKqTGDVlbA==", + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", + "dingbat-to-unicode": "^1.0.1", + "jszip": "^3.7.1", + "lop": "^0.4.1", + "path-is-absolute": "^1.0.0", + "underscore": "^1.13.1", + "xmlbuilder": "^10.0.0" + }, + "bin": { + "mammoth": "bin/mammoth" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/mammoth/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/marked": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz", + "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/option": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", + "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.4.tgz", + "integrity": "sha512-W3398nBM2CBfmGP2evneEO3ZZwEMPtHs72q++eNw60uDGDAdiGn0f9yNys91eo7/y8CTF5Ke1C0QO8JFVPU40Q==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.9", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-loading-skeleton": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz", + "integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", + "dependencies": { + "@remix-run/router": "1.19.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", + "dependencies": { + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-select": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.1.tgz", + "integrity": "sha512-RT1CJmuc+ejqm5MPgzyZujqDskdvB9a9ZqrdnVLsvAHjJ3Tj0hELnLeVPQlmYdVKCdCpxanepl6z7R5KhXhWzg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-sortablejs": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/react-sortablejs/-/react-sortablejs-6.1.4.tgz", + "integrity": "sha512-fc7cBosfhnbh53Mbm6a45W+F735jwZ1UFIYSrIqcO/gRIFoDyZeMtgKlpV4DdyQfbCzdh5LoALLTDRxhMpTyXQ==", + "license": "MIT", + "dependencies": { + "classnames": "2.3.1", + "tiny-invariant": "1.2.0" + }, + "peerDependencies": { + "@types/sortablejs": "1", + "react": ">=16.9.0", + "react-dom": ">=16.9.0", + "sortablejs": "1" + } + }, + "node_modules/react-sortablejs/node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==", + "license": "MIT" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sortablejs": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.3.tgz", + "integrity": "sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg==", + "license": "MIT", + "peer": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "license": "MIT", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, + "node_modules/turndown-plugin-gfm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz", + "integrity": "sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==", + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/vanilla-colorful": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", + "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e5c553c --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@ckeditor/ckeditor5-build-classic": "^43.2.0", + "@ckeditor/ckeditor5-react": "^9.3.1", + "axios": "^1.7.7", + "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", + "file-saver": "^2.0.5", + "jwt-decode": "^4.0.0", + "mammoth": "^1.8.0", + "react": "^18.3.1", + "react-bootstrap": "^2.10.4", + "react-dom": "^18.3.1", + "react-loading-skeleton": "^3.5.0", + "react-router-dom": "^6.26.0", + "react-select": "^5.8.1", + "react-sortablejs": "^6.1.4" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "vite": "^5.3.4" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..587a8a9 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; + +import PublicRoutes from './roles/guest/PublicRoutes'; +import UserRoutes from './roles/user/UserRoutes'; +import AdminRoutes from './roles/admin/AdminRoutes'; +import TeacherRoutes from './roles/teacher/TeacherRoutes'; + +const App = () => { + return ( + + + } /> + + } /> + } /> + } /> + + + ); +}; + +export default App; diff --git a/src/assets/audio/sample.mp3 b/src/assets/audio/sample.mp3 new file mode 100644 index 0000000..49d2959 Binary files /dev/null and b/src/assets/audio/sample.mp3 differ diff --git a/src/assets/images/Decoration.png b/src/assets/images/Decoration.png new file mode 100644 index 0000000..c7e5be8 Binary files /dev/null and b/src/assets/images/Decoration.png differ diff --git a/src/assets/images/Decoration2.png b/src/assets/images/Decoration2.png new file mode 100644 index 0000000..c375c84 Binary files /dev/null and b/src/assets/images/Decoration2.png differ diff --git a/src/assets/images/avatar.png b/src/assets/images/avatar.png new file mode 100644 index 0000000..36cb3a0 Binary files /dev/null and b/src/assets/images/avatar.png differ diff --git a/src/assets/images/decorFooter.png b/src/assets/images/decorFooter.png new file mode 100644 index 0000000..e1c39f3 Binary files /dev/null and b/src/assets/images/decorFooter.png differ diff --git a/src/assets/images/default-avatar.jpg b/src/assets/images/default-avatar.jpg new file mode 100644 index 0000000..e599422 Binary files /dev/null and b/src/assets/images/default-avatar.jpg differ diff --git a/src/assets/images/feature1.png b/src/assets/images/feature1.png new file mode 100644 index 0000000..b055703 Binary files /dev/null and b/src/assets/images/feature1.png differ diff --git a/src/assets/images/feature2.png b/src/assets/images/feature2.png new file mode 100644 index 0000000..9c3a3d7 Binary files /dev/null and b/src/assets/images/feature2.png differ diff --git a/src/assets/images/feature3.png b/src/assets/images/feature3.png new file mode 100644 index 0000000..ff078b4 Binary files /dev/null and b/src/assets/images/feature3.png differ diff --git a/src/assets/images/feature4.png b/src/assets/images/feature4.png new file mode 100644 index 0000000..95c0ee8 Binary files /dev/null and b/src/assets/images/feature4.png differ diff --git a/src/assets/images/footerDecor.png b/src/assets/images/footerDecor.png new file mode 100644 index 0000000..214565f Binary files /dev/null and b/src/assets/images/footerDecor.png differ diff --git a/src/assets/images/illustration/Illustration.png b/src/assets/images/illustration/Illustration.png new file mode 100644 index 0000000..3f4c364 Binary files /dev/null and b/src/assets/images/illustration/Illustration.png differ diff --git a/src/assets/images/illustration/Illustration2.png b/src/assets/images/illustration/Illustration2.png new file mode 100644 index 0000000..db5e7d0 Binary files /dev/null and b/src/assets/images/illustration/Illustration2.png differ diff --git a/src/assets/images/illustration/IllustrationForgot.png b/src/assets/images/illustration/IllustrationForgot.png new file mode 100644 index 0000000..cc77c26 Binary files /dev/null and b/src/assets/images/illustration/IllustrationForgot.png differ diff --git a/src/assets/images/illustration/IllustrationLogin.png b/src/assets/images/illustration/IllustrationLogin.png new file mode 100644 index 0000000..e90a4c4 Binary files /dev/null and b/src/assets/images/illustration/IllustrationLogin.png differ diff --git a/src/assets/images/illustration/IllustrationRegister.png b/src/assets/images/illustration/IllustrationRegister.png new file mode 100644 index 0000000..ee3d659 Binary files /dev/null and b/src/assets/images/illustration/IllustrationRegister.png differ diff --git a/src/assets/images/illustration/changePass.png b/src/assets/images/illustration/changePass.png new file mode 100644 index 0000000..d700802 Binary files /dev/null and b/src/assets/images/illustration/changePass.png differ diff --git a/src/assets/images/illustration/checkIcon.png b/src/assets/images/illustration/checkIcon.png new file mode 100644 index 0000000..ca7782c Binary files /dev/null and b/src/assets/images/illustration/checkIcon.png differ diff --git a/src/assets/images/illustration/emptyJourney.png b/src/assets/images/illustration/emptyJourney.png new file mode 100644 index 0000000..ff2ca95 Binary files /dev/null and b/src/assets/images/illustration/emptyJourney.png differ diff --git a/src/assets/images/illustration/emptyStudent.png b/src/assets/images/illustration/emptyStudent.png new file mode 100644 index 0000000..42aed61 Binary files /dev/null and b/src/assets/images/illustration/emptyStudent.png differ diff --git a/src/assets/images/illustration/grammar.png b/src/assets/images/illustration/grammar.png new file mode 100644 index 0000000..59d64c0 Binary files /dev/null and b/src/assets/images/illustration/grammar.png differ diff --git a/src/assets/images/illustration/listening.png b/src/assets/images/illustration/listening.png new file mode 100644 index 0000000..48ca7f0 Binary files /dev/null and b/src/assets/images/illustration/listening.png differ diff --git a/src/assets/images/illustration/logout.png b/src/assets/images/illustration/logout.png new file mode 100644 index 0000000..da184d3 Binary files /dev/null and b/src/assets/images/illustration/logout.png differ diff --git a/src/assets/images/illustration/material-audio.png b/src/assets/images/illustration/material-audio.png new file mode 100644 index 0000000..ddb7c90 Binary files /dev/null and b/src/assets/images/illustration/material-audio.png differ diff --git a/src/assets/images/illustration/material-image.png b/src/assets/images/illustration/material-image.png new file mode 100644 index 0000000..cab7e0f Binary files /dev/null and b/src/assets/images/illustration/material-image.png differ diff --git a/src/assets/images/illustration/reading.png b/src/assets/images/illustration/reading.png new file mode 100644 index 0000000..de5e213 Binary files /dev/null and b/src/assets/images/illustration/reading.png differ diff --git a/src/assets/images/illustration/report.png b/src/assets/images/illustration/report.png new file mode 100644 index 0000000..b744c10 Binary files /dev/null and b/src/assets/images/illustration/report.png differ diff --git a/src/assets/images/illustration/resultDown.png b/src/assets/images/illustration/resultDown.png new file mode 100644 index 0000000..5fdc034 Binary files /dev/null and b/src/assets/images/illustration/resultDown.png differ diff --git a/src/assets/images/illustration/resultFinish.png b/src/assets/images/illustration/resultFinish.png new file mode 100644 index 0000000..840abfe Binary files /dev/null and b/src/assets/images/illustration/resultFinish.png differ diff --git a/src/assets/images/illustration/resultJump.png b/src/assets/images/illustration/resultJump.png new file mode 100644 index 0000000..81bd670 Binary files /dev/null and b/src/assets/images/illustration/resultJump.png differ diff --git a/src/assets/images/illustration/resultStay.png b/src/assets/images/illustration/resultStay.png new file mode 100644 index 0000000..9688d3f Binary files /dev/null and b/src/assets/images/illustration/resultStay.png differ diff --git a/src/assets/images/illustration/service.png b/src/assets/images/illustration/service.png new file mode 100644 index 0000000..6935d18 Binary files /dev/null and b/src/assets/images/illustration/service.png differ diff --git a/src/assets/images/illustration/submitExercise.png b/src/assets/images/illustration/submitExercise.png new file mode 100644 index 0000000..ca62e87 Binary files /dev/null and b/src/assets/images/illustration/submitExercise.png differ diff --git a/src/assets/images/illustration/successModal.png b/src/assets/images/illustration/successModal.png new file mode 100644 index 0000000..c38f91c Binary files /dev/null and b/src/assets/images/illustration/successModal.png differ diff --git a/src/assets/images/illustration/topic.png b/src/assets/images/illustration/topic.png new file mode 100644 index 0000000..5157849 Binary files /dev/null and b/src/assets/images/illustration/topic.png differ diff --git a/src/assets/images/illustration/vocabulary.png b/src/assets/images/illustration/vocabulary.png new file mode 100644 index 0000000..9d9bf6f Binary files /dev/null and b/src/assets/images/illustration/vocabulary.png differ diff --git a/src/assets/images/loading.gif b/src/assets/images/loading.gif new file mode 100644 index 0000000..95c53dc Binary files /dev/null and b/src/assets/images/loading.gif differ diff --git a/src/assets/images/logo-w.png b/src/assets/images/logo-w.png new file mode 100644 index 0000000..b5d4804 Binary files /dev/null and b/src/assets/images/logo-w.png differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000..1ff3186 Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/assets/styles/Components/AudioPlayer.css b/src/assets/styles/Components/AudioPlayer.css new file mode 100644 index 0000000..55a9204 --- /dev/null +++ b/src/assets/styles/Components/AudioPlayer.css @@ -0,0 +1,128 @@ +.audio-player { + height: 50px; + width: 350px; + background: #444; + /* box-shadow: 0 0 20px 0 #000a; */ + font-family: arial; + color: white; + font-size: 0.75em; + overflow: hidden; + display: grid; + grid-template-rows: 6px auto; + border: 1px solid #444; + border-radius: 8px; + } + + .audio-player .timeline { + background: white; + width: 100%; + position: relative; + cursor: pointer; + /* box-shadow: 0 2px 10px 0 #0008; */ + } + + .audio-player .timeline .progress { + /* background: coral; */ + background: #0090FF; + width: 0%; + height: 100%; + transition: 0.25s; + border-radius: 0; + } + + .audio-player .controls { + display: flex; + justify-content: space-between; + align-items: stretch; + padding: 0 20px; + } + + .audio-player .controls > * { + display: flex; + justify-content: center; + align-items: center; + } + + .audio-player .controls .toggle-play.play { + cursor: pointer; + position: relative; + left: 0; + height: 0; + width: 0; + border: 7px solid #0000; + border-left: 13px solid white; + } + + .audio-player .controls .toggle-play.pause { + height: 15px; + width: 20px; + cursor: pointer; + position: relative; + } + + .audio-player .controls .toggle-play.pause:before { + position: absolute; + top: 0; + left: 0px; + background: white; + content: ""; + height: 15px; + width: 3px; + } + + .audio-player .controls .toggle-play.pause:after { + position: absolute; + top: 0; + right: 8px; + background: white; + content: ""; + height: 15px; + width: 3px; + } + + .audio-player .controls .time { + display: flex; + } + + .audio-player .controls .time > * { + padding: 2px; + } + + .audio-player .controls .volume-container { + cursor: pointer; + } + + .audio-player .controls .volume-container .volume-button { + height: 26px; + display: flex; + align-items: center; + } + + .audio-player .controls .volume-container .volume-button .volume { + transform: scale(0.7); + } + + .audio-player .controls .volume-container .volume-slider { + position: absolute; + left: -3px; + top: 15px; + z-index: -1; + width: 0; + height: 15px; + background: white; + /* box-shadow: 0 0 20px #000a; */ + transition: .25s; + } + + .audio-player .controls .volume-container .volume-slider .volume-percentage { + /* background: coral; */ + background: #0090FF; + height: 100%; + width: 75%; + } + + .audio-player .controls .volume-container:hover .volume-slider { + left: -123px; + width: 120px; + } + \ No newline at end of file diff --git a/src/assets/styles/admin.css b/src/assets/styles/admin.css new file mode 100644 index 0000000..f571361 --- /dev/null +++ b/src/assets/styles/admin.css @@ -0,0 +1,598 @@ +.admin-page{ + background-color: #F1F5FC; + height: 100vh; +} + +.admin-page .min-h-100{ + min-height: calc(100vh - 72px); +} + +.admin-page .admin-container{ + min-height: 100vh; +} + +.admin-page .admin-content{ + height: calc(100vh - 72px); + overflow: auto; +} + +.admin-page .navbar{ + height: 72px; +} + +.admin-page .navbar .btn-group button{ + padding: 8px 12px; +} + +.admin-page .navbar .navbar-brand span{ + display: block; + font-size: 14px; +} + +.admin-page .navbar .navbar-nav a{ + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + border: 2px solid #ffffff; + border-radius: 100%; + color: #ffffff; + margin-right: 20px; +} + +.admin-page .navbar .navbar-nav a:hover{ + background-color: #ffffff; + color: #0090FF; +} + +.admin-page .sidebar { + transition: width 0.3s ease; + /* height: calc(100vh - 72px); */ + height: 100vh; + top: 0; + box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.20); +} + +.admin-page .sidebar .top-logo{ + height: 72px; +} + +.admin-page .sidebar .nav-container{ + /* height: calc(100vh - 122px); */ + height: calc(100vh - 124px); + /* background-color: red; */ +} + +.admin-page .sidebar .nav-container .menu-box{ + height: calc(100% - 66px); + max-height: calc(100% - 52px); + overflow: auto; + padding-right: 4px; +} + +.admin-page .sidebar .nav-container .menu-box::-webkit-scrollbar { + width: 2px; +} +.admin-page .sidebar .nav-container .menu-box::-webkit-scrollbar-track { + background: #f1f1f1; +} +.admin-page .sidebar .nav-container .menu-box::-webkit-scrollbar-thumb { + background: #888; +} +.admin-page .sidebar .nav-container .menu-box::-webkit-scrollbar-thumb:hover { + background: #555; +} + +.admin-page .sidebar input{ + padding: 8px 12px 8px 0px; + margin-bottom: 12px; + background-color: #F1F5FC; + border: none; +} + +.admin-page .sidebar input::placeholder{ + color: #BDBDBD; +} + +.admin-page .sidebar .input-group-text{ + padding: 8px 12px 8px 12px; + margin-bottom: 12px; + background-color: #F1F5FC; + border: none; + color: #BDBDBD; +} + +.admin-page .sidebar .toggle-sidebar{ + transition: width 0.3s ease; + z-index: 99999; + display: block; + width: 30px; + height: 30px; + position: absolute; + top: calc(72px / 2 - 15px); + left: calc(100% - 15px); + padding: 0; +} + +.admin-page .sidebar .img-avatar{ + width: 72px; + height: 72px; + border-radius: 100%; + border: 3px solid #ffffff; +} + +.admin-page .sidebar .toggle-btn { + margin-bottom: 1rem; + width: 100%; +} + +.admin-page .sidebar .nav-link { + display: flex; + align-items: center; + padding: 8px 12px; + margin-bottom: 12px; + background: #ffffff; + color: #526071; + border: 1px solid #F1F5FC; + font-weight: 500; +} + +.admin-page .sidebar .submenu .nav-link{ + color: #959EA9; + margin-left: 24px; + border: none; + font-weight: normal; +} + +.admin-page .sidebar .nav a:hover, +.admin-page .sidebar .nav a.active{ + background-color: #0090FF; + color: #ffffff; + border-color: #0090FF; +} + +.admin-page .sidebar .nav .drop-menu:hover{ + background-color: #ffffff; + border-color: #0090FF; + color: #0090FF; +} + +.admin-page .sidebar .nav a.active-border{ + border-color: #0090FF; + color: #0090FF; +} + +.admin-page .sidebar.minimized { + width: 80px; +} + +.admin-page .sidebar.minimized .toggle-sidebar{ + rotate: 180deg; +} + +.admin-page .sidebar.minimized .img-avatar{ + width: 50px; + height: 50px; + margin-bottom: 16px; +} + +.admin-page .sidebar.minimized .display-username{ + display: none; +} + +.admin-page .sidebar.minimized .nav-link span, +.admin-page .sidebar.minimized .nav-link i.chrevon, +.admin-page .sidebar.minimized .submenu, +.admin-page .sidebar.minimized input{ + display: none; +} + +.admin-page .sidebar.minimized .nav-link i{ + margin-right: 0!important; +} + +.admin-page .sidebar.minimized .nav-link { + display: flex; + justify-content: center; + align-items: center; +} + +.admin-page .sidebar.minimized .nav-link.drop-menu{ + border-color: #F1F5FC!important; + color: #526071!important; +} + +.admin-page .sidebar.minimized .input-group-text{ + cursor: pointer; + width: 52px; + border-radius: 0.375rem!important; + display: flex; + justify-content: center; +} + + + + + +.modal-admin .modal-header, +.modal-admin .modal-body{ + padding: 16px 32px; +} + +.modal-admin .modal-header .modal-title{ + font-size: 18px; + color: #526071; +} + +.admin-page .page-title{ + font-size: 30px; + font-weight: 700; + color: #0090FF; +} + +.admin-page .page-title.strip{ + position: relative; +} + +.admin-page .page-title.strip::after{ + content: ""; + position: absolute; + height: 20px; + border-bottom: 2px solid #0090FF; + /* width: 400px; */ + width: 30vw; + margin-left: 12px; +} + +.admin-page .page-desc{ + font-size: 18px; + color: #526071; + margin-bottom: 32px; +} + +.admin-page .col-tabs-parent{ + background-color: #ffffff; + border-radius: 12px; +} + +.admin-page .col-tabs{ + background-color: #ffffff; + border-radius: 12px; + padding: 8px; +} + +.admin-page .form-selector{ + padding: 8px 16px; +} + +.admin-page .col-tabs .nav-link{ + border-radius: 8px; + font-size: 14px; + padding: 8px 16px; + color: #BDBDBD; + border: 1px solid transparent; +} + +.admin-page .col-tabs .nav-link.active, +.admin-page .col-tabs .dropdown-toggle.show, +.admin-page .col-tabs .nav-item.dropdown.active a.dropdown-toggle{ + background-color: #0090FF; + color: #ffffff; +} + +.admin-page .mini-cards{ + background: #ffffff; + border-radius: 1rem!important; + display: flex; + flex-direction: column; + justify-content: space-evenly; +} + +.admin-page .cards:not(.combine){ + background: #ffffff; + border-radius: 1rem!important; + height: 100%; + border: none!important; +} + + +.admin-page .cards.combine{ + background: #ffffff; + height: 100%; +} + +.admin-page .combine.combine-top{ + border-top-left-radius: 1rem!important; + border-top-right-radius: 1rem!important; +} + +.admin-page .combine.combine-bottom{ + border-bottom-left-radius: 1rem!important; + border-bottom-right-radius: 1rem!important; +} + +.admin-page .cards .cards-title, +.admin-page .cards .accordion-header.cards-title{ + padding: 16px 24px; + border-bottom: 1px solid #F1F5FC; +} + +.admin-page .cards .cards-title.hr{ + border-bottom: 8px solid #F1F5FC!important; +} + +.admin-page .cards .cards-title h4, +.admin-page .accordion.cards .cards-title button, +.admin-page .mini-cards h4{ + font-size: 18px; + color: #526071; + font-weight: 600; + margin-bottom: 0; +} + +.admin-page .cards.cards-exercise .cards-title{ + padding: 12px 24px; +} + +.admin-page .cards.cards-exercise .cards-title h4, +.admin-page .accordion.cards-exercise .cards-title button{ + font-size: 16px; +} + +.admin-page .cards.cards-exercise .cards-title button{ + font-size: 12px; +} + +.admin-page .cards .cards-title p{ + font-size: 12px; + color: #BDBDBD; + margin-bottom: 0; + margin-top: 8px; +} + +.admin-page .cards .cards-title a{ + text-decoration: none; + font-size: 14px; +} + +.admin-page .accordion.cards .accordion-item{ + border-radius: 1rem; + border: none; + overflow: hidden; +} + +.admin-page .cards .accordion-header.cards-title{ + padding: 0; + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; + border-bottom-left-radius: 0!important; + border-bottom-right-radius: 0!important; +} + +.admin-page .accordion.cards .cards-title button{ + padding: 16px 24px; + border-radius: 1rem; + background-color: #ffffff!important; + outline: none!important; + box-shadow: none!important; +} + +.admin-page .cards .cards-body{ + padding: 16px 24px; +} + +.admin-page .cards .cards-body table{ + margin-bottom: 0; +} + +.admin-page .cards .cards-body table th{ + background-color: #0090FF; + font-size: 14px; + color: #ffffff; + border: 0; +} + +.admin-page .cards .cards-body table th:first-of-type{ + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; +} + +.admin-page .cards .cards-body table th:last-of-type{ + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; +} + +.admin-page .cards .cards-body table tr th:first-child, +.admin-page .cards .cards-body table tr td:first-child{ + text-align: center; +} + +.admin-page .cards .cards-body table tbody tr:last-of-type td{ + border: 0; +} + +.admin-page .cards .cards-body table td{ + font-size: 12px; + color: #526071; + border-color: #F1F5FC; +} + +.admin-page .cards .cards-body table th, +.admin-page .cards .cards-body table td{ + padding: 10px 8px; + vertical-align: middle; +} + +.admin-page .cards .cards-body table td.action-col .btn{ + border-radius: 100%; + background-color: #E2F2FF; + border: none!important; + margin: 0 4px; +} + +.admin-page .cards .cards-body table td.action-col .btn-view{ + color: #526071; +} + +.admin-page .cards .cards-body table td.action-col .btn.btn-edit{ + color: #0090FF; +} + +.admin-page .cards .cards-body table td.action-col .btn.btn-delete{ + color: #E9342D; +} + +.admin-page .cards .cards-body table td.action-col .btn-view:hover{ + background-color: #526071; + color: #ffffff; +} + +.admin-page .cards .cards-body table td.action-col .btn.btn-edit:hover{ + background-color: #0090FF; + color: #ffffff; +} + +.admin-page .cards .cards-body table td.action-col .btn.btn-delete:hover{ + background-color: #E9342D; + color: #ffffff; +} + +.admin-page .cards .cards-body .table-input-search{ + margin-bottom: 16px; + /* border-color: #BDBDBD; */ +} + +.admin-page .cards .cards-body form .input-group-icon .input-group-text, +.modal-admin .modal-body form .input-group-icon .input-group-text, +.admin-page .col-tabs-parent .form-selector .input-group-text{ + background-color: #ffffff; + color: #BDBDBD; + border-right: 0; + padding-right: 6px; +} + +.admin-page .cards .cards-body form small, +.modal-admin .modal-body form small{ + font-size: 12px; + color: #526071; +} + +.admin-page .cards .cards-body form .input-group-icon input, +.admin-page .cards .cards-body form .input-group-icon select, +.modal-admin .modal-body form .input-group-icon input, +.modal-admin .modal-body form .input-group-icon select, +.admin-page .col-tabs-parent .form-selector select{ + border-left: 0; + padding-left: 6px; +} + +.admin-page .cards .cards-body form .input-group-icon input::placeholder, +.admin-page .cards .cards-body form .input-group-icon input::placeholder, +.admin-page .cards .cards-body form textarea::placeholder, +.admin-page .cards .cards-body form textarea::placeholder{ + color: #BDBDBD; +} + +.admin-page .cards .cards-body form .input-group-icon.disabled .input-group-text, +.admin-page .cards .cards-body form .input-group-icon.disabled input, +.modal-admin .modal-body form .input-group-icon.disabled .input-group-text, +.modal-admin .modal-body form .input-group-icon.disabled input{ + background-color: #F2F2F2; + color: #333333; +} + +.admin-page .cards .cards-body form .input-group-icon select:required:invalid, +.modal-admin .modal-body form .input-group-icon select:required:invalid, +.admin-page .col-tabs-parent .form-selector select:invalid{ + color: #BDBDBD; +} + +.admin-page .cards .cards-body form .input-group-icon select, +.admin-page .cards .cards-body form .input-group-icon select option, +.modal-admin .modal-body form .input-group-icon select, +.modal-admin .modal-body form .input-group-icon select option, +.admin-page .col-tabs-parent .form-selector select, +.admin-page .col-tabs-parent .form-selector select option{ + color: #212529; +} + + + + + + +.admin-dashboard .student-display{ + display: flex; + align-items: center; + margin-bottom: 16px; +} + +.admin-dashboard .student-display img{ + width: 40px; + height: 40px; + object-fit: cover; + margin-right: 8px; +} + +.admin-dashboard .student-display .student-identity{ + height: 40px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.admin-dashboard .student-display .student-identity h5{ + font-size: 14px; + font-weight: 500; + margin-bottom: 4px; +} + +.admin-dashboard .student-display .student-identity h6{ + font-size: 12px; + font-weight: 400; + margin: 0; + color: #526071; +} + +.admin-dashboard .student-display .student-level{ + font-size: 14px; + font-weight: 400; + color: #959EA9; + margin-bottom: 0; + margin-left: auto; +} + +.react-select-group__control{ + border-radius: 0.375rem!important; + border-left: none!important; + border-top-left-radius: 0!important; + border-bottom-left-radius: 0!important; + border-color: #dee2e6!important; +} + +.react-select-group__placeholder{ + color: #BDBDBD!important; +} + +.group-react-select{ + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} + +/* audio.no-control::-webkit-media-controls{ + display:none !important; +} */ + +.ck-rounded-corners .ck.ck-editor__top .ck-sticky-panel .ck-sticky-panel__content, .ck.ck-menu-bar{ + border-top-left-radius: 0.375rem!important; + border-top-right-radius: 0.375rem!important; +} + +.ck.ck-editor__main > .ck-editor__editable{ + border-bottom-left-radius: 0.375rem!important; + border-bottom-right-radius: 0.375rem!important; + min-height: 20vh; +} \ No newline at end of file diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css new file mode 100644 index 0000000..7ec36fd --- /dev/null +++ b/src/assets/styles/index.css @@ -0,0 +1,407 @@ +body { + font-family: "Inter", sans-serif; + margin: 0; + /* display: flex; + place-items: center; */ + min-width: 320px; + min-height: 100vh; + color: #333333; +} + +.w-fit{ + width: fit-content; +} + +.w-screen{ + width: 100vw; +} + +.h-fit{ + height: fit-content; +} + +.h-screen{ + height: 100vh; +} + +.h-screen-nav{ + height: 100vh; + padding-top: 58px; +} + +.pt-nav{ + padding-top: 58px; +} + +.pt-nav-1{ + padding-top: calc(1.5rem + 58px); +} + +.mb-45{ + margin-bottom: 32px; +} + +.ratio-1{ + aspect-ratio: 1/1; + width: auto; + height: auto; +} + +.lh-normal{ + line-height: normal; +} + +.items-center{ + align-items: center; + justify-content: center; +} + +.fs-7{ + font-size: 0.88rem!important; +} + +.fs-14p{ + font-size: 14px; +} + +.fs-12p{ + font-size: 12px; +} + +.fw-500{ + font-weight: 500; +} + +.rounded-35{ + border-radius: 12px; +} + +.text-gd{ + display: inline-block; + color: transparent; + background-clip: text; + background-image: linear-gradient(to right, #5674ED, #34C3F9); +} + +.text-blue{ + color: #0090FF; +} + +.text-blue-50{ + color: #93C5FD; +} + +.text-red{ + color: #E9342D; +} + +.text-muted-50{ + color: #A3A3A3; +} + +.text-grey{ + color: #959EA9; +} + +.text-navy{ + color: #526071; +} + +.bg-gd { + background-image: linear-gradient(to right, #5674ED, #34C3F9); +} + +.bg-blue{ + background-color: #0090FF; +} + +.bg-blue-50{ + background-color: #EFF6FF; +} + +.bg-red{ + background-color: #E9342D; +} + +.bg-ts{ + background: transparent; +} + +.bg-blur{ + -webkit-backdrop-filter: blur(3px); + backdrop-filter: blur(3px); + background: #ffffff20; +} + +.bg-grey{ + background-color: grey; +} + +.bg-light-grey{ + background-color: lightgrey; +} + +.btn-gd { + background-image: linear-gradient(to right, #5674ED, #34C3F9); + color: #ffffff; + border: none; +} + +.btn-gd:hover{ + background-image: linear-gradient(to left, #5674ED, #34C3F9); + color: #fafafa; +} + +.btn-gd:disabled{ + color: #ffffff; + background-color: #ffffff34; +} + +.btn-check:checked+.btn.btn-gd{ + color: #ffffff; +} + +.btn-outline-muted{ + background-color: #ffffff00; + border-color: #A3A3A3; + color: #A3A3A3; +} + +.btn-outline-muted:hover{ + background-color: #A3A3A3; + border-color: #A3A3A3; + color: #ffffff; +} + +.btn-white{ + background-color: #ffffff; + border: none; + outline: none; +} + +.btn-white:hover, .btn-white:active { + background-color: #fcfcfc!important; + border-color: #fcfcfc!important; + color: #000000!important; +} + +.btn-ts{ + background-color: #ffffff00; + border: none; + outline: none; +} + +.btn-blue { + background-color: #0090FF; + border-color: #0090FF; + color: #ffffff; +} + +.btn-blue:hover, .btn-blue:active { + background-color: #008cf7!important; + border-color: #008cf7!important; + color: #ffffff!important; +} + +.btn-red { + background-color: #E9342D; + border-color: #E9342D; + color: #ffffff; +} + +.btn-red:hover, .btn-red:active { + background-color: #d82721!important; + border-color: #d82721!important; + color: #ffffff!important; +} + +.btn-white-blue{ + background-color: #ffffff; + border-color: #ffffff; + color: #0090FF; +} + +.btn-white-blue:hover{ + background-color: #ffffff; + border-color: #0090FF; + color: #0090FF; +} + +.btn-outline-white{ + background-color: #ffffff00; + border-color: #ffffff; + color: #ffffff; +} + +.btn-outline-white:hover{ + background-color: #ffffff00; + border-color: #0090FF; + color: #ffffff; +} + +.btn-outline-blue{ + background-color: #ffffff00; + border-color: #0090FF; + color: #0090FF; +} + +.btn-outline-blue:hover, .btn-outline-blue:active, .btn-outline-blue.show{ + background-color: #0091ff0c!important; + border-color: #0090FF!important; + color: #0090FF!important; +} + +.btn-white-outline-blue{ + background-color: #ffffff; + border-color: #93C5FD; + color: #0090FF; +} + +.btn-white-outline-blue:hover, .btn-white-outline-blue:active{ + background-color: #0090FF!important; + border-color: #ffffff!important; + color: #ffffff!important; +} + +.btn-nav{ + width: 140px; +} + +.ts{ + color:transparent; +} + +.truncate{ + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +} + +.truncate-2{ + -webkit-line-clamp: 2; +} + +.truncate-4{ + -webkit-line-clamp: 4; +} + +.cursor-pointer{ + cursor: pointer; +} + +.border-dashed{ + border-style: dashed; +} + +.border-blue{ + border-color: #0090FF; +} + +.custom-alert{ + position: absolute; + top: 70px; + right: 12px; +} + +.custom-breadcrumb .breadcrumb{ + margin-bottom: 0; +} + +.custom-breadcrumb .breadcrumb-item{ + padding-left: 12px!important; +} + +.custom-breadcrumb .breadcrumb-item::before{ + padding-right: 12px!important; + font-weight: normal!important; +} + +.custom-breadcrumb .breadcrumb-item:not(.active) a{ + text-decoration: none!important; + color: #212529bf!important; +} + +.custom-breadcrumb .breadcrumb-item.active{ + color: #0090FF; + font-weight: 600; +} + +.custom-paginate .page-link{ + border: none; + padding: 0 12px; + color: #526071; +} + +.custom-paginate .active span{ + color: #0090FF; + background: transparent; +} + +.custom-paginate .disabled span{ + color: #BDBDBD; + background: transparent!important; +} + +.custom-paginate .page-item{ + display: flex; + justify-content: center; + align-items: center; +} + +.custom-paginate .page-item:first-of-type span, +.custom-paginate .page-item:last-of-type span{ + font-size: 24px; +} + + + + + + + + + + + + + +.landing-page section{ + padding-top: 3rem; + margin-bottom: 6rem; +} + +.landing-page section:first-of-type{ + padding-top: 5rem; +} + +.landing-page section:last-of-type{ + /* padding-bottom: 6rem; */ +} + +.landing-page .content-con{ + padding: 6rem; +} + +.landing-page .content-con section:not(:last-of-type){ + margin-bottom: 4.5rem; +} + +footer .social-icon-box a{ + width: 32px; + height: 32px; + background-color: #ffffff; + color: black; + display: flex; + justify-content: center; + align-items: center; + margin: 5px; + border-radius: 100%; +} + +footer{ + background-image: url('../images/footerDecor.png'), linear-gradient(to right, #5674ED, #34C3F9); + background-size: cover; + background-position: top; +} + diff --git a/src/assets/styles/teacher.css b/src/assets/styles/teacher.css new file mode 100644 index 0000000..dd258d6 --- /dev/null +++ b/src/assets/styles/teacher.css @@ -0,0 +1,424 @@ +.teacher-page .min-h-100{ + min-height: calc(100vh - 58px); +} + +.teacher-page .dashboard-container{ + height: calc(100vh - 58px); + padding-top: 58px; +} + +.teacher-page .navbar{ + height: 58px; +} + +.teacher-page .navbar .navbar-nav a{ + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + border: 2px solid #ffffff; + border-radius: 100%; + color: #ffffff; + margin-right: 16px; +} + +.teacher-page .navbar .navbar-nav a:hover{ + background-color: #ffffff; + color: #0090FF; +} + +.teacher-page .sidebar { + transition: width 0.3s ease; + height: calc(100vh - 58px); + top: 58px; +} + +.teacher-page .sidebar .toggle-sidebar{ + transition: width 0.3s ease; + z-index: 99999; + display: block; + width: 30px; + height: 30px; + position: absolute; + top: calc(50% - 30px); + left: calc(100% - 15px); + padding: 0; +} + +.teacher-page .sidebar .img-avatar{ + width: 72px; + height: 72px; + border-radius: 100%; + border: 3px solid #ffffff; + object-fit: cover; +} + +.teacher-page .sidebar .toggle-btn { + margin-bottom: 1rem; + width: 100%; +} + +.teacher-page .sidebar .nav-link { + display: flex; + align-items: center; + padding: 12px 16px; +} + +.teacher-page .sidebar .nav a:hover, .teacher-page .sidebar .nav a.active{ + background-color: #ffffff47; +} + +.teacher-page .sidebar.minimized { + width: 72px; +} + +.teacher-page .sidebar.minimized .toggle-sidebar{ + rotate: 180deg; +} + +.teacher-page .sidebar.minimized .img-avatar{ + width: 50px; + height: 50px; + margin-bottom: 16px; +} + +.teacher-page .sidebar.minimized .display-username{ + display: none; +} + +.teacher-page .sidebar.minimized .nav-link span { + display: none; +} + +.teacher-page .sidebar.minimized .nav-link i{ + margin-right: 0!important; +} + +.teacher-page .sidebar.minimized .nav-link { + display: flex; + justify-content: center; + align-items: center; +} + +.teacher-page .home-page .filled-journey .card img{ + aspect-ratio: 1/1; + width: 10vw; + height: auto; + max-width: 205px; + max-height: 205px; + object-fit: cover; +} + + + + +.modal-admin .modal-header, +.modal-admin .modal-body{ + padding: 16px 32px; +} + +.modal-admin .modal-header .modal-title{ + font-size: 18px; + color: #526071; +} + +.teacher-page .page-title{ + font-size: 30px; + font-weight: 700; + color: #0090FF; +} + +.teacher-page .page-title.strip{ + position: relative; +} + +.teacher-page .page-title.strip::after{ + content: ""; + position: absolute; + height: 20px; + border-bottom: 2px solid #0090FF; + width: 30vw; + margin-left: 12px; +} + +.teacher-page .page-desc{ + font-size: 18px; + color: #526071; + margin-bottom: 32px; +} + +.teacher-page .col-tabs-parent{ + background-color: #ffffff; + border-radius: 12px; +} + +.teacher-page .col-tabs{ + background-color: #ffffff; + border-radius: 12px; + padding: 8px; +} + +.teacher-page .form-selector{ + padding: 8px 16px; +} + +.teacher-page .col-tabs .nav-link{ + border-radius: 8px; + font-size: 14px; + padding: 8px 16px; + color: #BDBDBD; + border: 1px solid transparent; +} + +.teacher-page .col-tabs .nav-link.active, +.teacher-page .col-tabs .dropdown-toggle.show, +.teacher-page .col-tabs .nav-item.dropdown.active a.dropdown-toggle{ + background-color: #0090FF; + color: #ffffff; +} + +.teacher-page .cards:not(.combine){ + background: #ffffff; + border-radius: 1rem!important; + height: 100%; + border: none!important; +} + +.teacher-page .cards.combine{ + background: #ffffff; + height: 100%; +} + +.teacher-page .combine.combine-top{ + border-top-left-radius: 1rem!important; + border-top-right-radius: 1rem!important; +} + +.teacher-page .combine.combine-bottom{ + border-bottom-left-radius: 1rem!important; + border-bottom-right-radius: 1rem!important; +} + +.teacher-page .cards .cards-title, +.teacher-page .cards .accordion-header.cards-title{ + padding: 16px 24px; + border-bottom: 1px solid #F1F5FC; +} + +.teacher-page .cards .cards-title.hr{ + border-bottom: 8px solid #F1F5FC!important; +} + +.teacher-page .cards .cards-title h4, +.teacher-page .accordion.cards .cards-title button{ + font-size: 18px; + color: #526071; + font-weight: 600; + margin-bottom: 0; +} + +.teacher-page .cards.cards-exercise .cards-title{ + padding: 12px 24px; +} + +.teacher-page .cards.cards-exercise .cards-title h4, +.teacher-page .accordion.cards-exercise .cards-title button{ + font-size: 16px; +} + +.teacher-page .cards.cards-exercise .cards-title button{ + font-size: 12px; +} + +.teacher-page .cards .cards-title p{ + font-size: 12px; + color: #BDBDBD; + margin-bottom: 0; + margin-top: 8px; +} + +.teacher-page .cards .cards-title a{ + text-decoration: none; + font-size: 14px; +} + +.teacher-page .accordion.cards .accordion-item{ + border-radius: 1rem; + border: none; + overflow: hidden; +} + +.teacher-page .cards .accordion-header.cards-title{ + padding: 0; + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; + border-bottom-left-radius: 0!important; + border-bottom-right-radius: 0!important; +} + +.teacher-page .accordion.cards .cards-title button{ + padding: 16px 24px; + border-radius: 1rem; + background-color: #ffffff!important; + outline: none!important; + box-shadow: none!important; +} + +.teacher-page .cards .cards-body{ + padding: 16px 24px; +} + +.teacher-page .cards .cards-body table{ + margin-bottom: 0; +} + +.teacher-page .cards .cards-body table th{ + background-color: #0090FF; + font-size: 14px; + color: #ffffff; + border: 0; +} + +.teacher-page .cards .cards-body table th:first-of-type{ + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; +} + +.teacher-page .cards .cards-body table th:last-of-type{ + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; +} + +.teacher-page .cards .cards-body table tr th:first-child, +.teacher-page .cards .cards-body table tr td:first-child{ + text-align: center; +} + +.teacher-page .cards .cards-body table tbody tr:last-of-type td{ + border: 0; +} + +.teacher-page .cards .cards-body table td{ + font-size: 12px; + color: #526071; + border-color: #F1F5FC; +} + +.teacher-page .cards .cards-body table th, +.teacher-page .cards .cards-body table td{ + padding: 10px 8px; + vertical-align: middle; +} + +.teacher-page .cards .cards-body table td.action-col .btn{ + border-radius: 100%; + background-color: #E2F2FF; + border: none!important; + margin: 0 4px; +} + +.teacher-page .cards .cards-body table td.action-col .btn-view{ + color: #526071; +} + +.teacher-page .cards .cards-body table td.action-col .btn.btn-edit{ + color: #0090FF; +} + +.teacher-page .cards .cards-body table td.action-col .btn.btn-delete{ + color: #E9342D; +} + +.teacher-page .cards .cards-body table td.action-col .btn-view:hover{ + background-color: #526071; + color: #ffffff; +} + +.teacher-page .cards .cards-body table td.action-col .btn.btn-edit:hover{ + background-color: #0090FF; + color: #ffffff; +} + +.teacher-page .cards .cards-body table td.action-col .btn.btn-delete:hover{ + background-color: #E9342D; + color: #ffffff; +} + +.teacher-page .cards .cards-body .table-input-search{ + margin-bottom: 16px; + /* border-color: #BDBDBD; */ +} + +.teacher-page .cards .cards-body form .input-group-icon .input-group-text, +.modal-admin .modal-body form .input-group-icon .input-group-text, +.teacher-page .col-tabs-parent .form-selector .input-group-text{ + background-color: #ffffff; + color: #BDBDBD; + border-right: 0; + padding-right: 6px; +} + +.teacher-page .cards .cards-body form small, +.modal-admin .modal-body form small{ + font-size: 12px; + color: #526071; +} + +.teacher-page .cards .cards-body form .input-group-icon input, +.teacher-page .cards .cards-body form .input-group-icon select, +.modal-admin .modal-body form .input-group-icon input, +.modal-admin .modal-body form .input-group-icon select, +.teacher-page .col-tabs-parent .form-selector select{ + border-left: 0; + padding-left: 6px; +} + +.teacher-page .cards .cards-body form .input-group-icon input::placeholder, +.teacher-page .cards .cards-body form .input-group-icon input::placeholder, +.teacher-page .cards .cards-body form textarea::placeholder, +.teacher-page .cards .cards-body form textarea::placeholder{ + color: #BDBDBD; +} + +.teacher-page .cards .cards-body form .input-group-icon.disabled .input-group-text, +.teacher-page .cards .cards-body form .input-group-icon.disabled input, +.modal-admin .modal-body form .input-group-icon.disabled .input-group-text, +.modal-admin .modal-body form .input-group-icon.disabled input{ + background-color: #F2F2F2; + color: #333333; +} + +.teacher-page .cards .cards-body form .input-group-icon select:required:invalid, +.modal-admin .modal-body form .input-group-icon select:required:invalid, +.teacher-page .col-tabs-parent .form-selector select:invalid{ + color: #BDBDBD; +} + +.teacher-page .cards .cards-body form .input-group-icon select, +.teacher-page .cards .cards-body form .input-group-icon select option, +.modal-admin .modal-body form .input-group-icon select, +.modal-admin .modal-body form .input-group-icon select option, +.teacher-page .col-tabs-parent .form-selector select, +.teacher-page .col-tabs-parent .form-selector select option{ + color: #212529; +} + +.react-select-group__control{ + border-radius: 0.375rem!important; + border-left: none!important; + border-top-left-radius: 0!important; + border-bottom-left-radius: 0!important; + border-color: #dee2e6!important; +} + +.react-select-group__placeholder{ + color: #BDBDBD!important; +} + +.group-react-select{ + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} \ No newline at end of file diff --git a/src/assets/styles/user.css b/src/assets/styles/user.css new file mode 100644 index 0000000..8bbbd27 --- /dev/null +++ b/src/assets/styles/user.css @@ -0,0 +1,326 @@ +.dashboard-page .min-h-100{ + min-height: calc(100vh - 58px); +} + +.dashboard-page .dashboard-container{ + height: calc(100vh - 58px); + padding-top: 58px; +} + +.dashboard-page .navbar{ + height: 58px; +} + +.dashboard-page .navbar .navbar-nav a{ + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + border: 2px solid #ffffff; + border-radius: 100%; + color: #ffffff; + margin-right: 16px; +} + +.dashboard-page .navbar .navbar-nav a:hover{ + background-color: #ffffff; + color: #0090FF; +} + +.dashboard-page .sidebar { + transition: width 0.3s ease; + height: calc(100vh - 58px); + top: 58px; +} + +.dashboard-page .sidebar .toggle-sidebar{ + transition: width 0.3s ease; + z-index: 99999; + display: block; + width: 30px; + height: 30px; + position: absolute; + top: calc(50% - 30px); + left: calc(100% - 15px); + padding: 0; +} + +.dashboard-page .sidebar .img-avatar{ + width: 72px; + height: 72px; + border-radius: 100%; + border: 3px solid #ffffff; + object-fit: cover; +} + +.dashboard-page .sidebar .toggle-btn { + margin-bottom: 1rem; + width: 100%; +} + +.dashboard-page .sidebar .nav-link { + display: flex; + align-items: center; + padding: 12px 16px; +} + +.dashboard-page .sidebar .nav a:hover, .dashboard-page .sidebar .nav a.active{ + background-color: #ffffff47; +} + +.dashboard-page .sidebar.minimized { + width: 72px; +} + +.dashboard-page .sidebar.minimized .toggle-sidebar{ + rotate: 180deg; +} + +.dashboard-page .sidebar.minimized .img-avatar{ + width: 50px; + height: 50px; + margin-bottom: 16px; +} + +.dashboard-page .sidebar.minimized .display-username{ + display: none; +} + +.dashboard-page .sidebar.minimized .nav-link span { + display: none; +} + +.dashboard-page .sidebar.minimized .nav-link i{ + margin-right: 0!important; +} + +.dashboard-page .sidebar.minimized .nav-link { + display: flex; + justify-content: center; + align-items: center; +} + +.dashboard-page .home-page .filled-journey .card img{ + aspect-ratio: 1/1; + width: 10vw; + height: auto; + max-width: 205px; + max-height: 205px; + object-fit: cover; +} + +.exercise-history .nav.nav-pills .nav-item a, +.topic-page .nav.nav-pills .nav-item a, +.setting-page .nav.nav-pills .nav-item a{ + color: #959EA9; + font-size: 14px; +} + +.exercise-history .nav.nav-pills .nav-item a.active, +.topic-page .nav.nav-pills .nav-item a.active, +.setting-page .nav.nav-pills .nav-item a.active{ + background-color: #0090FF; + color: #ffffff; +} + +.exercise-history .nav.nav-pills .nav-item a.active, +.setting-page .nav.nav-pills .nav-item a.active{ + border-radius: 0.6rem; + font-weight: bold; +} + +.exercise-history .submit-time{ + color: #959EA9; + font-size: 14px; + font-weight: 400; +} + +.learning-page .card{ + overflow: hidden; +} + +.learning-page .card img{ + aspect-ratio: 5/2; + /* max-height: 165px; */ + width: 100%; + height: auto; + object-fit: cover; + margin-bottom: 12px; + border-radius: 8px; +} + +.learning-page .card .card-body{ + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.learning-page .card .card-body .card-text{ + font-size: 12px; +} + +.topic-page .nav.nav-pills .nav-item a{ + font-size: 14px; +} + +.btn-square-back{ + width: 30px; + height: 30px; + padding: 0; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; +} + +.topic-page .head-title{ + background-size: cover; + background-position: center; +} + +.topic-page .head-title h6{ + color: #737373; +} + +.topic-page .head-title p, .topic-page .topic-content p{ + font-size: 14px; +} + +.exercise-history .custom-breadcrumb .breadcrumb-item:first-of-type{ + padding-left: 0!important; +} + +.material-page ol.breadcrumb, +.level-page ol.breadcrumb{ + margin-bottom: 0; +} + +.exercise-page img, .exercise-page video, +.material-page img, .material-page video{ + max-height: 180px; + width: auto; +} + +.exercise-page .video-box, +.material-page .video-box{ + max-width: 520px; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; +} + +.exercise-page .number-list .number-label{ + color: #959EA9; + display: flex; +} + +.exercise-page .number-list .number-label:not(.active):hover{ + background-color: #0091ff0c; +} + +.exercise-page .number-list .number-label.active{ + color: #ffffff; + background-color: #0090FF; +} + +.exercise-page .number-list .number-label.answered:not(.active){ + color: #0090FF; +} + +.exercise-page .options .form-check, +.exercise-page .options-tf .form-check, +.exercise-page .options-mp .form-check { + width: fit-content; +} + +.exercise-page .options .selected-answer span, +.exercise-page .options-tf .selected-answer span{ + background-color: #0090FF; + border: 1px solid #0090FF; + color: #ffffff; +} + +.exercise-page .options .option-label, +.exercise-page .options-tf .option-label, +.exercise-page .options-mp .option-label { + width: 35px; + height: 35px; + color: #333333; + border: 1px solid #333333; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.exercise-page .options .option-text, +.exercise-page .options-tf .option-text, +.exercise-page .options-mp .option-text { + height: 35px; + min-width: 200px; + padding: 0 14px; + color: #333333; + border: 1px solid #333333; + display: flex; + align-items: center; + border-radius: 0 14px 14px 14px; +} + +/* .exercise-page .options-mp .option-label, +.exercise-page .options-mp .option-text{ + border: 2px solid #ffffff; +} */ + +.level-page .check-icon{ + z-index: 1; + position: absolute; + bottom: -40px; + right: -40px; + aspect-ratio: 1/1; + width: 165px; + height: auto; +} + +.level-page *:not(.check-icon){ + z-index: 2; +} + +.level-page .level-con{ + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.level-page .level-con .level-label{ + padding: 8px; + border-radius: 14px; + border: 1px solid #ffffff; + display: flex; + align-items: center; + color: #ffffff; +} + +.level-page .level-con .level-label span{ + display: flex; + justify-content: center; + align-items: center; + width: 42px; + height: 42px; + border-radius: 8px; + background-color: #ffffff; + color: #0090FF; + font-size: 24px; + font-weight: bold; + margin-left: 12px; +} + +.level-page .level-con.bg-light-grey .level-label span{ + color: lightgrey; +} + +.level-page .level-con p{ + min-height: 55px; + padding-right: 10%; +} \ No newline at end of file diff --git a/src/assets/video/sample.mp4 b/src/assets/video/sample.mp4 new file mode 100644 index 0000000..297ca2a Binary files /dev/null and b/src/assets/video/sample.mp4 differ diff --git a/src/components/layout/admin/AdminLayout.jsx b/src/components/layout/admin/AdminLayout.jsx new file mode 100644 index 0000000..26cdd8e --- /dev/null +++ b/src/components/layout/admin/AdminLayout.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Navbar from './AdminNavbar'; +import SideNav from './AdminSideNav'; + +const AdminLayout = ({ children }) => { + return ( +
+
+
+ +
+ +
+ {children} +
+
+
+
+
+ ); +}; + +export default AdminLayout; diff --git a/src/components/layout/admin/AdminNavbar.jsx b/src/components/layout/admin/AdminNavbar.jsx new file mode 100644 index 0000000..a90fd26 --- /dev/null +++ b/src/components/layout/admin/AdminNavbar.jsx @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; +import { Navbar, Container, Nav, SplitButton, Dropdown, ButtonGroup, Button, Modal } from 'react-bootstrap'; +import logo from '../../../assets/images/logo-w.png'; +import logoutIllustration from '../../../assets/images/illustration/logout.png'; +import { Link } from 'react-router-dom'; +import useAuth from '../../../roles/guest/auth/hooks/useAuth'; +import { API_URL } from '../../../utils/Constant'; +import avatar from '../../../assets/images/default-avatar.jpg'; + +function validName(fullName) { + const nameArray = fullName.split(" "); + const firstTwoWords = nameArray.slice(0, 2).join(" "); + + return firstTwoWords; +} + +function getDateNow() { + const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu']; + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']; + + const now = new Date(); + const dayName = days[now.getDay()]; + const day = String(now.getDate()).padStart(2, '0') + const monthName = months[now.getMonth()]; + const year = now.getFullYear(); + + return `${dayName}, ${day} ${monthName} ${year}`; +} + + +const AdminNavbar = () => { + const { logout } = useAuth(); + const { username, picture } = JSON.parse(localStorage.getItem('userData')); + + const [show, setShow] = useState(false); + const handleClose = () => setShow(false); + const handleShow = () => setShow(true); + + const handleLogout = () => { + logout(); + }; + + return ( + + + +
+ {getDateNow()} + CMS Smart English Adaptive Learning System +
+
+ + + + + + + + + Profile + Logout + + + + + + +

Time to logout?

+ +

Confirm logout? We’ll be here when you return.

+
+ + +
+
+
+ +
+ ); +}; + +export default AdminNavbar; diff --git a/src/components/layout/admin/AdminSideNav.jsx b/src/components/layout/admin/AdminSideNav.jsx new file mode 100644 index 0000000..9d1d9c9 --- /dev/null +++ b/src/components/layout/admin/AdminSideNav.jsx @@ -0,0 +1,144 @@ +import React, { useState } from 'react'; +import { Nav, Button, Collapse, Form, InputGroup } from 'react-bootstrap'; +import { NavLink } from 'react-router-dom'; +import logo from '../../../assets/images/logo.png' + +const AdminSideNav = () => { + const [open1, setOpen1] = useState(false); + const [open2, setOpen2] = useState(false); + const [open3, setOpen3] = useState(false); + + const [isMinimized, setIsMinimized] = useState(false); + + const toggleMinimize = () => { + setIsMinimized(!isMinimized); + if (!isMinimized) { + setOpen1(false); + setOpen2(false); + setOpen3(false); + } + }; + + return ( + + ); +}; + +export default AdminSideNav; diff --git a/src/components/layout/guest/MainNav.jsx b/src/components/layout/guest/MainNav.jsx new file mode 100644 index 0000000..b4adffc --- /dev/null +++ b/src/components/layout/guest/MainNav.jsx @@ -0,0 +1,76 @@ +import React, { useState, useEffect } from 'react'; +import { Navbar, Nav, Container, Button } from 'react-bootstrap'; +import logo from '../../../assets/images/logo.png'; +import logoWhite from '../../../assets/images/logo-w.png'; +import { useLocation } from 'react-router-dom'; + +const MainNav = () => { + const location = useLocation(); + const [scrolled, setScrolled] = useState(false); + + const handleScroll = () => { + if (window.scrollY > 80) { + setScrolled(true); + } else { + setScrolled(false); + } + }; + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + const getNavbarMenu = () => { + if (location.pathname === '/') { + return 'd-block w-100'; + } else{ + return 'd-none' + } + }; + + return ( + + + {/* + {' '} + */} + + {' '} + +
+ + + + + +
+
+
+ ); +}; + +export default MainNav; diff --git a/src/components/layout/teacher/TeacherLayout.jsx b/src/components/layout/teacher/TeacherLayout.jsx new file mode 100644 index 0000000..7709ec8 --- /dev/null +++ b/src/components/layout/teacher/TeacherLayout.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import Navbar from './TeacherNavbar'; +import SideNav from './TeacherSideNav'; + +const TeacherLayout = ({ children }) => { + + return ( +
+ +
+
+ +
+ {children} +
+
+
+
+ ); +}; + +export default TeacherLayout; diff --git a/src/components/layout/teacher/TeacherNavbar.jsx b/src/components/layout/teacher/TeacherNavbar.jsx new file mode 100644 index 0000000..cecfd26 --- /dev/null +++ b/src/components/layout/teacher/TeacherNavbar.jsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { Navbar, Container, Nav, Modal, Button, OverlayTrigger, Tooltip } from 'react-bootstrap'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import logo from '../../../assets/images/logo.png'; +import logoW from '../../../assets/images/logo-w.png'; +import logoutIllustration from '../../../assets/images/illustration/logout.png'; +import useAuth from '../../../roles/guest/auth/hooks/useAuth'; + +import Report from '../../../roles/user/report/views/Report'; + +import { unSlugify } from '../../../utils/Constant'; + +const TeacherNavbar = () => { + const { logout } = useAuth(); + + const [show, setShow] = useState(false); + const handleClose = () => setShow(false); + const handleShow = () => setShow(true); + + const [showReport, setShowReport] = useState(false); + const handleCloseReport = () => setShowReport(false); + const handleShowReport = () => setShowReport(true); + + const handleLogout = () => { + logout(); + }; + + return ( + <> + + + + logo + + +
+ +
+ + + +
+
+ + + +

Time to logout?

+ +

Confirm logout? We’ll be here when you return.

+
+ + +
+
+
+ + + + + + ); +}; + +export default TeacherNavbar; diff --git a/src/components/layout/teacher/TeacherSideNav.jsx b/src/components/layout/teacher/TeacherSideNav.jsx new file mode 100644 index 0000000..9456d80 --- /dev/null +++ b/src/components/layout/teacher/TeacherSideNav.jsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { Nav, Button } from 'react-bootstrap'; +import { NavLink } from 'react-router-dom'; +import avatar from '../../../assets/images/default-avatar.jpg'; +import { API_URL } from '../../../utils/Constant'; + +function validName(fullName) { + const nameArray = fullName.split(" "); + const firstTwoWords = nameArray.slice(0, 2).join(" "); + + return firstTwoWords; +} + +const TeacherSideNav = () => { + const { username, picture } = JSON.parse(localStorage.getItem('userData')); + + const [isMinimized, setIsMinimized] = useState(false); + const toggleMinimize = () => { + setIsMinimized(!isMinimized); + }; + + return ( + + ); +}; + +export default TeacherSideNav; diff --git a/src/components/layout/user/UserLayout.jsx b/src/components/layout/user/UserLayout.jsx new file mode 100644 index 0000000..0c3c953 --- /dev/null +++ b/src/components/layout/user/UserLayout.jsx @@ -0,0 +1,68 @@ +import React from 'react'; +import Navbar from './UserNavbar'; +import SideNav from './UserSideNav'; +import { useLocation } from 'react-router-dom'; + +const UserLayout = ({ children }) => { + const location = useLocation(); + + const getSideNav = () => { + let showIt = true; + switch (location.pathname) { + case '/learning/home': + showIt = true; + break; + + case '/learning/home/': + showIt = true; + break; + + case '/learning/module': + showIt = true; + break; + + case '/learning/module/': + showIt = true; + break; + + case '/learning/history': + showIt = true; + break; + + case '/learning/history/': + showIt = true; + break; + + case '/learning/setting': + showIt = true; + break; + + case '/learning/setting/': + showIt = true; + break; + + default: + showIt = false; + break; + } + + return showIt; + }; + + return ( +
+ +
+
+ {/* */} + {getSideNav() ? : ""} +
+ {children} +
+
+
+
+ ); +}; + +export default UserLayout; diff --git a/src/components/layout/user/UserNavbar.jsx b/src/components/layout/user/UserNavbar.jsx new file mode 100644 index 0000000..bbfd89d --- /dev/null +++ b/src/components/layout/user/UserNavbar.jsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react'; +import { Navbar, Container, Nav, Modal, Button, OverlayTrigger, Tooltip } from 'react-bootstrap'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import logo from '../../../assets/images/logo.png'; +import logoW from '../../../assets/images/logo-w.png'; +import logoutIllustration from '../../../assets/images/illustration/logout.png'; +import useAuth from '../../../roles/guest/auth/hooks/useAuth'; + +import Report from '../../../roles/user/report/views/Report'; + +import { unSlugify } from '../../../utils/Constant'; + +const UserNavbar = () => { + const { user, logout } = useAuth(); + + const location = useLocation(); + const navigate = useNavigate(); + + const pathSegments = location.pathname.split('/'); + const hasTopicAndLevel = pathSegments.length == 7 && pathSegments[4] && pathSegments[5]; + + const [show, setShow] = useState(false); + const handleClose = () => setShow(false); + const handleShow = () => setShow(true); + + const [showReport, setShowReport] = useState(false); + const handleCloseReport = () => setShowReport(false); + const handleShowReport = () => setShowReport(true); + + const handleLogout = () => { + logout(); + }; + + const lastSegment = location.pathname.split('/').filter(Boolean).pop(); + const bgBlue = () => { + let blue = true; + switch (lastSegment) { + case 'exercise': + blue = false; + break; + + default: + blue = true; + break; + } + + return blue; + }; + + return ( + <> + + + + logo + + +
+ {hasTopicAndLevel ? ( + lastSegment === 'exercise' ? ( +
+ {`${unSlugify(pathSegments[3])} : ${unSlugify(pathSegments[4])} `} + - {unSlugify(pathSegments[5])} +
+ ):( +
+ {unSlugify(pathSegments[4])} +
+ ) + ):('')} +
+ + + +
+
+ + + +

Time to logout?

+ +

Confirm logout? We’ll be here when you return.

+
+ + +
+
+
+ + + + + + ); +}; + +export default UserNavbar; diff --git a/src/components/layout/user/UserSideNav.jsx b/src/components/layout/user/UserSideNav.jsx new file mode 100644 index 0000000..aa27451 --- /dev/null +++ b/src/components/layout/user/UserSideNav.jsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { Nav, Button } from 'react-bootstrap'; +import { NavLink } from 'react-router-dom'; +import avatar from '../../../assets/images/default-avatar.jpg'; +import { API_URL } from '../../../utils/Constant'; + +function validName(fullName) { + const nameArray = fullName.split(" "); + const firstTwoWords = nameArray.slice(0, 2).join(" "); + + return firstTwoWords; +} + +const UserSideNav = () => { + const { username, picture } = JSON.parse(localStorage.getItem('userData')); + + const [isMinimized, setIsMinimized] = useState(false); + const toggleMinimize = () => { + setIsMinimized(!isMinimized); + }; + + return ( + + ); +}; + +export default UserSideNav; diff --git a/src/components/ui/AudioPlayer.jsx b/src/components/ui/AudioPlayer.jsx new file mode 100644 index 0000000..015f7fe --- /dev/null +++ b/src/components/ui/AudioPlayer.jsx @@ -0,0 +1,101 @@ +import React, { useState, useRef, useEffect } from 'react'; +import audio from '../../assets/audio/sample.mp3' +import '../../assets/styles/Components/AudioPlayer.css'; + +const AudioPlayer = ({ progressControl = false }, audioSrc, title = "audio") => { + const audioRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + const [progress, setProgress] = useState(0); + const [duration, setDuration] = useState(0); + const [currentTime, setCurrentTime] = useState(0); + + const togglePlay = () => { + const audio = audioRef.current; + if (isPlaying) { + audio.pause(); + } else { + audio.play(); + } + setIsPlaying(!isPlaying); + }; + + const handleTimeUpdate = () => { + const audio = audioRef.current; + setCurrentTime(audio.currentTime); + setProgress((audio.currentTime / audio.duration) * 100); + }; + + const handleLoadedMetadata = () => { + const audio = audioRef.current; + setDuration(audio.duration); + }; + + const handleEnded = () => { + setIsPlaying(false); + setProgress(0); + setCurrentTime(0); + }; + + const formatTime = (time) => { + const minutes = Math.floor(time / 60); + const seconds = Math.floor(time % 60); + return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; + }; + + useEffect(() => { + const audio = audioRef.current; + audio.addEventListener('loadedmetadata', handleLoadedMetadata); + audio.addEventListener('ended', handleEnded); + + return () => { + audio.removeEventListener('loadedmetadata', handleLoadedMetadata); + audio.removeEventListener('ended', handleEnded); + }; + }, []); + + return ( +
+ + +
{ + if (!progressControl) { + const audio = audioRef.current; + const rect = e.target.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + const clickRatio = clickX / rect.width; + audio.currentTime = audio.duration * clickRatio; + } + }} + > +
+
+ +
+
+
+
+
+
{formatTime(currentTime)}
+
/
+
{formatTime(duration)}
+
+
{title}
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default AudioPlayer; \ No newline at end of file diff --git a/src/components/ui/AudioSvg.jsx b/src/components/ui/AudioSvg.jsx new file mode 100644 index 0000000..ae7cbfd --- /dev/null +++ b/src/components/ui/AudioSvg.jsx @@ -0,0 +1,382 @@ +import * as React from "react"; +const AudioSvg = (props) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default AudioSvg; diff --git a/src/components/ui/Button.jsx b/src/components/ui/Button.jsx new file mode 100644 index 0000000..35e0000 --- /dev/null +++ b/src/components/ui/Button.jsx @@ -0,0 +1,32 @@ +import React from 'react'; + +const Button = ({ children, onClick, type = 'button', className = '', variant = '', size='', disabled }) => { + let buttonClasses = ''; + if (size === 'sm') { + buttonClasses += 'font-medium rounded-md text-xs text-center px-3 py-2 focus:outline-none '; + } else { + buttonClasses += 'font-medium rounded-md text-sm text-center px-5 py-2.5 focus:outline-none '; + } + + switch (variant) { + case 'primary': + buttonClasses += 'text-white bg-blue-700 hover:bg-blue-800'; + break; + + case 'gd': + buttonClasses += 'text-white bg-gradient-to-r from-blue-primary to-teal-primary hover:bg-gradient-to-bl '; + break; + + default: + buttonClasses += 'text-gray-900 bg-white hover:bg-gray-100'; + break; + } + + return ( + + ); +}; + +export default Button; diff --git a/src/components/ui/DocImporter.jsx b/src/components/ui/DocImporter.jsx new file mode 100644 index 0000000..cbb040d --- /dev/null +++ b/src/components/ui/DocImporter.jsx @@ -0,0 +1,158 @@ +import React, { useState } from "react"; +import Mammoth from "mammoth"; +import AudioPlayer from "./AudioPlayer"; + +const DocImporter = () => { + const [questions, setQuestions] = useState([]); + + const handleFileChange = async (event) => { + const file = event.target.files[0]; + if (file) { + try { + const arrayBuffer = await file.arrayBuffer(); + const { value } = await Mammoth.extractRawText({ arrayBuffer }); + // const parsedQuestions = parseQuestions(value); + console.log(value); + const parsedQuestions = parseQuestionsNew(value); + setQuestions(parsedQuestions); + } catch (error) { + console.error("Error reading .docx file", error); + } + } + }; + + const parseQuestionsNew = (text) => { + const questionsArray = []; + const questions = text.split("#Question"); + if (questions.length >= 5) { + questions.forEach((questionText) => { + if (questionText.trim()) { + const parts = questionText.trim().split("\n").map(line => line.trim()); + + const number = parts.findIndex(part => part.startsWith("#Question:")); + const question = parts.findIndex(part => part.startsWith("Question:")); + const optionA = parts.findIndex(part => part.startsWith("Option A:")); + const optionB = parts.findIndex(part => part.startsWith("Option B:")); + const optionC = parts.findIndex(part => part.startsWith("Option C:")); + const optionD = parts.findIndex(part => part.startsWith("Option D:")); + const optionE = parts.findIndex(part => part.startsWith("Option E:")); + const correct = parts.findIndex(part => part.startsWith("Correct:")); + const weight = parts.findIndex(part => part.startsWith("Weight:")); + const imageUrlIndex = parts.findIndex(part => part.startsWith("Image URL:")); + const audioUrlIndex = parts.findIndex(part => part.startsWith("Audio URL:")); + const videoUrlIndex = parts.findIndex(part => part.startsWith("Video URL:")); + + const questionData = { + Number: parts[0]?.split(": ")[1], + Question: question !== -1 ? parts[question].split(": ")[1] || "Tidak ada" : "Tidak ada", + // Option: [ + // optionA !== -1 ? parts[optionA].split(": ")[1] || "Tidak ada" : "Tidak ada", + // optionB !== -1 ? parts[optionB].split(": ")[1] || "Tidak ada" : "Tidak ada", + // optionC !== -1 ? parts[optionC].split(": ")[1] || "Tidak ada" : "Tidak ada", + // optionD !== -1 ? parts[optionD].split(": ")[1] || "Tidak ada" : "Tidak ada", + // optionE !== -1 ? parts[optionE].split(": ")[1] || "Tidak ada" : "Tidak ada" + // ], + OptionA: optionA !== -1 ? parts[optionA].split(": ")[1] || "Tidak ada" : "Tidak ada", + OptionB: optionB !== -1 ? parts[optionB].split(": ")[1] || "Tidak ada" : "Tidak ada", + OptionC: optionC !== -1 ? parts[optionC].split(": ")[1] || "Tidak ada" : "Tidak ada", + OptionD: optionD !== -1 ? parts[optionD].split(": ")[1] || "Tidak ada" : "Tidak ada", + OptionE: optionE !== -1 ? parts[optionE].split(": ")[1] || "Tidak ada" : "Tidak ada", + Correct: correct !== -1 ? parts[correct].split(": ")[1] || "Tidak ada" : "Tidak ada", + Weight: weight !== -1 ? parts[weight].split(": ")[1] || "Tidak ada" : "Tidak ada", + imageUrl: imageUrlIndex !== -1 ? parts[imageUrlIndex].split(": ")[1] || "Tidak ada" : "Tidak ada", + audioUrl: audioUrlIndex !== -1 ? parts[audioUrlIndex].split(": ")[1] || "Tidak ada" : "Tidak ada", + videoUrl: videoUrlIndex !== -1 ? parts[videoUrlIndex].split(": ")[1] || "Tidak ada" : "Tidak ada", + }; + questionsArray.push(questionData); + } + }); + } + + // console.info(questionsArray); + return questionsArray; + }; + + const parseQuestions = (text) => { + const questionsArray = []; + const questions = text.split("Nomor"); + + questions.forEach((questionText) => { + if (questionText.trim()) { + const parts = questionText.trim().split("\n").map(line => line.trim()); + const imageUrlIndex = parts.findIndex(part => part.startsWith("Image URL:")); + const tipeSoalIndex = parts.findIndex(part => part.startsWith("Tipe Soal:")); + + const cleanChoice = (choice) => choice.replace(/^[A-Z]\. /, ''); + + const question = { + nomor: parts[0]?.split(": ")[1], + pertanyaan: parts[2]?.split(": ")[1], + pilihan: [ + cleanChoice(parts[4]) || "Tidak ada", + cleanChoice(parts[6]) || "Tidak ada", + cleanChoice(parts[8]) || "Tidak ada", + cleanChoice(parts[10]) || "Tidak ada" + ], + jawabanBenar: parts[12]?.split(": ")[1], + imageUrl: imageUrlIndex !== -1 ? parts[imageUrlIndex].split(": ")[1] || "Tidak ada" : "Tidak ada", + tipeSoal: tipeSoalIndex !== -1 ? parts[tipeSoalIndex].split(": ")[1] || "Tidak ada" : "Tidak ada" + }; + questionsArray.push(question); + } + }); + + console.info(questionsArray); + return questionsArray; + }; + + + return ( +
+

Import Soal dari File .docx

+ + + {questions.length > 0 && ( +
+

Soal yang diimpor:

+ +
+ )} +
+ ); +}; + +export default DocImporter; diff --git a/src/components/ui/TablePaginate.jsx b/src/components/ui/TablePaginate.jsx new file mode 100644 index 0000000..4908662 --- /dev/null +++ b/src/components/ui/TablePaginate.jsx @@ -0,0 +1,79 @@ +import React, { useState, useEffect } from 'react'; +import { Pagination } from 'react-bootstrap'; + +const TablePaginate = ({ totalPages, currentPage, onPageChange }) => { + const [pageNumbers, setPageNumbers] = useState([]); + + useEffect(() => { + const visiblePages = getVisiblePages(currentPage, totalPages); + setPageNumbers(visiblePages); + }, [totalPages, currentPage]); + + const getVisiblePages = (current, total) => { + setPageNumbers([]); + let pages = []; + + if (total <= 7) { + for (let i = 1; i <= total; i++) { + pages.push(i); + } + } else { + pages.push(1); + pages.push(2); + + if (current < 3 && current > total - 2) { + pages.push("..."); + }else{ + if (current == 3) { + pages.push(3); + }else if (current > 3) { + pages.push("..."); + } + + if (current > 3 && current < total - 2) { + pages.push(current); + } + + if (current == total - 2) { + pages.push(total - 2); + }else if(current < total - 2){ + pages.push("..."); + } + } + + pages.push(total - 1); + pages.push(total); + } + return pages; + }; + + return ( + + onPageChange(currentPage - 1)} + /> + + {pageNumbers.map((number, index) => ( + number === "..." ? ( + + ) : ( + onPageChange(number)} + > + {number} + + ) + ))} + + onPageChange(currentPage + 1)} + /> + + ); +}; + +export default TablePaginate; diff --git a/src/components/ui/adminMessageModal/ModalOperation.jsx b/src/components/ui/adminMessageModal/ModalOperation.jsx new file mode 100644 index 0000000..8715180 --- /dev/null +++ b/src/components/ui/adminMessageModal/ModalOperation.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Modal, Button, Spinner, ModalBody } from 'react-bootstrap'; + +import create from '../../../assets/images/illustration/submitExercise.png'; +import cue from '../../../assets/images/illustration/successModal.png'; +import ask from '../../../assets/images/illustration/IllustrationForgot.png' + +const ModalOperation = ({ + show, + handleClose, + title, + description, + loading, + successMessage, + confirmAction, + handleConfirm +}) => { + return ( + + {loading?( + +
+ + + + + + +
+
+ ):( + confirmAction?( + +

Delete This Data

+ + {/*

Once deleted, this cannot be restored. Do you wish to go ahead?

*/} +

{description}

+
+ + +
+
+ ):( + title === "ERROR"?( + +

ERROR!

+ +

{successMessage}

+ +
+ ):( + +

Data Successfully {title}!

+ +

{successMessage}

+ +
+ ) + ) + )} +
+ ); +}; + +export default ModalOperation; diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..96cdcad --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'bootstrap-icons/font/bootstrap-icons.css'; +import './assets/styles/index.css'; +import 'react-loading-skeleton/dist/skeleton.css'; +import App from './App.jsx'; + +ReactDOM.createRoot(document.getElementById('root')).render( + // + + //, +); diff --git a/src/roles/admin/AdminRoutes.jsx b/src/roles/admin/AdminRoutes.jsx new file mode 100644 index 0000000..22c8f03 --- /dev/null +++ b/src/roles/admin/AdminRoutes.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; + +import AdminLayout from '../../components/layout/admin/AdminLayout'; +import NotFound from './NotFound'; + +import Dashboard from './dashboard/views/Dashboard'; +import ManageStudents from './manage_students/views/ManageStudents'; +import ManageTeachers from './manage_teachers/views/ManageTeachers'; +import ManageClasses from './manage_classes/views/ManageClasses'; +import ClassDetail from './manage_classes/views/ClassDetail'; +import ManageSections from './manage_section/views/ManageSections'; +import ManageTopics from './manage_topics/views/ManageTopics'; +import ManageMaterials from './manage_materials/views/ManageMaterials'; +import EditorMaterial from './manage_materials/views/EditorMaterial'; +import ManageExercises from './manage_exercises/views/ManageExercises'; +import OldManageExercises from './manage_exercises/views/OldManageExercises'; +import ExerciseDetail from './manage_exercises/views/ExerciseDetail'; +import UpdateExercises from './manage_exercises/views/UpdateExercise'; +import ManageProgress from './manage_progress/views/ManageProgress'; +import StudentProgress from './manage_progress/views/StudentProgress'; +import ClassProgress from './manage_progress/views/ClassProgress'; +import ManageReports from './manage_reports/views/ManageReports'; +import Setting from './setting/views/Setting'; +import '../../assets/styles/admin.css' + +import ProtectedRoute from '../../utils/ProtectedRoute'; + +const AdminRoutes = () => { + return ( + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> */} + + + + ); +}; + +export default AdminRoutes; diff --git a/src/roles/admin/NotFound.jsx b/src/roles/admin/NotFound.jsx new file mode 100644 index 0000000..49974c6 --- /dev/null +++ b/src/roles/admin/NotFound.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const NotFound = () => { + return ( +
+

Not Found

+
+ ); +}; + +export default NotFound; diff --git a/src/roles/admin/dashboard/hooks/useDashboard.jsx b/src/roles/admin/dashboard/hooks/useDashboard.jsx new file mode 100644 index 0000000..ce92c06 --- /dev/null +++ b/src/roles/admin/dashboard/hooks/useDashboard.jsx @@ -0,0 +1,94 @@ +import { useState, useEffect } from 'react'; +import dashboardService from '../services/serviceDashboard'; + +const useDashboard = () => { + const [totalStudent, setTotalStudent] = useState("-"); + const [totalTeacher, setTotalTeacher] = useState("-"); + const [reports, setReports] = useState([]); + const [loadingReports, setLoadingReports] = useState(true); + const [activity, setActivity] = useState([]); + const [loadingActivity, setLoadingActivity] = useState(true); + const [error, setError] = useState(null); + + const fetchTotalStudent = async () => { + try { + const data = await dashboardService.fetchTotalStudent("", "", 1, 0); + setTotalStudent(data.payload.totalStudents); + } catch (err) { + setError(err); + } + }; + + const fetchTotalTeacher = async () => { + try { + const data = await dashboardService.fetchTotalTeacher("", "", 1, 0); + setTotalTeacher(data.payload.totalTeachers); + } catch (err) { + setError(err); + } + }; + + const fetchActivity = async () => { + setLoadingActivity(true); + try { + const data = await dashboardService.fetchActivity("", "", 1, 5); + setActivity(data.payload.monitorings); + } catch (err) { + setError(err); + } finally { + setLoadingActivity(false); + } + }; + + const fetchReport = async () => { + setLoadingReports(true); + try { + const data = await dashboardService.fetchReport("", "", 1, 5); + setReports(data.payload.reports); + } catch (err) { + setError(err); + } finally { + setLoadingReports(false); + } + }; + + useEffect(() => { + fetchTotalStudent(), + fetchTotalTeacher(), + fetchActivity(); + fetchReport(); + }, []); + + + + function formatLocalDate(isoDate) { + const date = new Date(isoDate); + + const options = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZone: 'Asia/Jakarta' + }; + + return new Intl.DateTimeFormat('id-ID', options).format(date) + .replace(/\//g, '-') + .replace(/\./g, ':') + ' WIB'; + } + + return { + error, + totalStudent, + totalTeacher, + reports, + activity, + loadingReports, + loadingActivity, + formatLocalDate + }; +}; + +export default useDashboard; diff --git a/src/roles/admin/dashboard/services/serviceDashboard.jsx b/src/roles/admin/dashboard/services/serviceDashboard.jsx new file mode 100644 index 0000000..6caec6b --- /dev/null +++ b/src/roles/admin/dashboard/services/serviceDashboard.jsx @@ -0,0 +1,48 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchTotalStudent = async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/user/student?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching reports:', error); + throw error; + } +}; + +const fetchTotalTeacher = async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/user/teacher?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching reports:', error); + throw error; + } +}; + +const fetchActivity = async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/monitoring/progress?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching reports:', error); + throw error; + } +}; + +const fetchReport = async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/report?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching reports:', error); + throw error; + } +}; + +export default{ + fetchTotalStudent, + fetchTotalTeacher, + fetchReport, + fetchActivity +}; diff --git a/src/roles/admin/dashboard/views/Dashboard.jsx b/src/roles/admin/dashboard/views/Dashboard.jsx new file mode 100644 index 0000000..9393a47 --- /dev/null +++ b/src/roles/admin/dashboard/views/Dashboard.jsx @@ -0,0 +1,152 @@ +import React from 'react'; +import { Table, Row, Col, Card, Button, Spinner } from 'react-bootstrap'; +import { NavLink } from 'react-router-dom'; +import useDashboard from '../hooks/useDashboard'; + +function validName(text) { + const words = text.trim().split(" "); + return words.slice(0, 2).join(" "); +} + +const AdminDashboard = () => { + const { username } = JSON.parse(localStorage.getItem('userData')); + const { + error, + totalStudent, + totalTeacher, + reports, + activity, + loadingActivity, + loadingReports, + formatLocalDate + } = useDashboard(); + return ( +
+

Hi, {validName(username)}!

+

Together, we can make every learning journey smarter and more adaptive.

+ + +
+

All Student

+
+

{totalStudent}

+
+
+

All Teacher

+
+

{totalTeacher}

+
+ + +
+
+

Recent Student Activities

+
+
+ + + + + + + + + + + + {loadingActivity?( + + + + ):( + activity.length > 0?( + activity.map((data, index) => ( + + + + + + + + )) + ):( + + + + ) + )} + +
NoNISNNameSectionTopic
+ + + + + + +
{index + 1}{data.NISN}{data.NAME_USERS}{data.NAME_SECTION}{data.NAME_TOPIC}
+

Empty Data

+
+
+
+ +
+ + +
+
+

Issue Reports

+ See more +
+
+ + + + + + + + + + + + {loadingReports?( + + + + ):( + reports.length > 0?( + reports.map((data, index) => ( + + + + + + + + )) + ):( + + + + ) + )} + +
NoEmail AddressFull NameReportTime
+ + + + + + +
{index + 1}{data.USER_EMAIL}{data.USER_EMAIL}{data.REPORTS}{formatLocalDate(data.TIME_REPORT)}
+

Empty Data

+
+
+
+ +
+
+ ); +}; + +export default AdminDashboard; diff --git a/src/roles/admin/manage_classes/hooks/useClassDetail.jsx b/src/roles/admin/manage_classes/hooks/useClassDetail.jsx new file mode 100644 index 0000000..b624632 --- /dev/null +++ b/src/roles/admin/manage_classes/hooks/useClassDetail.jsx @@ -0,0 +1,37 @@ +import { useState, useEffect } from 'react'; +import classService from '../services/serviceClasses'; + +const useClassDetail = (classId) => { + const [classData, setClassData] = useState([]); + const [nameClass, setClassName] = useState('class'); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + try { + const getNameClass = await classService.getClassById(classId); + setClassName(getNameClass.payload.NAME_CLASS); + + const data = await classService.getClassStudent(classId); + if ([data.payload].length > 0) { + setClassData(data.payload); + } else { + setClassData([]); + } + } catch (error) { + setError(error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [classId]); + + return { classData, nameClass, loading, error }; +}; + +export default useClassDetail; \ No newline at end of file diff --git a/src/roles/admin/manage_classes/hooks/useClasses.jsx b/src/roles/admin/manage_classes/hooks/useClasses.jsx new file mode 100644 index 0000000..620dfe3 --- /dev/null +++ b/src/roles/admin/manage_classes/hooks/useClasses.jsx @@ -0,0 +1,257 @@ +import { useState, useEffect } from 'react'; +import classService from '../services/serviceClasses'; + +const useClasses = () => { + const [classes, setClasses] = useState([]); + const [freeStudent, setStudentOption] = useState([]); + const [studentSlug, setStudentSlug] = useState({}); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [selectedClass, setSelectedClass] = useState(null); + const [formData, setFormData] = useState({ + className: '', + capacity: '', + }); + const [assignStudents, setAssignStudents] = useState([]); + const [assignClass, setAssignClass] = useState(''); + + const [show, setShow] = useState(false); + const [showAssign, setShowAssign] = useState(false); + + const [showLoader, setShowLoader] = useState(false); + const [loaderState, setLoaderState] = useState({ loading: false, successMessage: '', title: '', description: '', confirmAction: false }); + + const resetForm = () =>{ + setFormData({ + className: '', + capacity: '', + }); + } + + const handleFormChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const assignStudentChange = (selectedOptions) => { + setAssignStudents(selectedOptions); + }; + const assignClassChange = (e) => { + setAssignClass(e.target.value); + }; + + const handleShow = (data) => { + setSelectedClass(data); + setFormData({ + className: data?.NAME_CLASS || '', + capacity: data?.TOTAL_STUDENT || '', + }); + setShow(true); + }; + const handleClose = () => { + setShow(false); + setShowAssign(false); + resetForm(); + setSelectedClass(null) + }; + const handleShowAssign = () => setShowAssign(true); + + const handleCloseLoader = () => setShowLoader(false); + const handleShowLoader = (title, description, loading = false, successMessage = '', confirmAction = false) => { + setLoaderState({ title, description, loading, successMessage, confirmAction }); + setShowLoader(true); + }; + + const fetchDataStudent = async () => { + setLoading(true); + try { + const studentArray = {}; + const studentForOption = []; + const dataStudent = await classService.fetchDataFreeStudent(); + dataStudent.payload.map((s, index) => { + studentArray[s.NISN] = s.NAME_USERS; + studentForOption[index] = {value: s.NISN, label: `${s.NISN} - ${s.NAME_USERS}`} + }); + setStudentSlug(studentArray); + setStudentOption(studentForOption); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + const createClass = async () => { + const newClass ={ + NAME_CLASS: formData.className, + TOTAL_STUDENT: formData.capacity, + }; + + handleShowLoader('Created', '', true) + try { + const createdClass = await classService.createData(newClass); + setClasses((prevClasses) => [...prevClasses, createdClass.payload]); + } catch (err) { + setError(err); + }finally{ + resetForm(); + setLoaderState(prev => ({ + ...prev, + loading: false, + successMessage: 'Your new entry has been successfully created and saved.' + })); + } + }; + + const editClass = async () => { + const id = selectedClass.ID_CLASS; + const data = formData; + const updateClassData ={ + NAME_CLASS: data.className, + TOTAL_STUDENT: parseInt(data.capacity), + }; + handleClose(); + handleShowLoader('Updated', '', true); + try { + const classData = await classService.updateData(id, updateClassData); + setClasses((prevClasses) => + prevClasses.map((s) => (s.ID_CLASS === id ? classData.payload : s)) + ); + } catch (err) { + setError(err); + }finally{ + setLoaderState(prev => ({ + ...prev, + loading: false, + successMessage: 'Your data has been successfully updated.' + })); + } + }; + + const deleteClass = async (id) => { + handleShowLoader('Deleted', 'Are you sure you want to delete this class?', false, '', true); + const confirmDelete = async () => { + handleCloseLoader(); + handleShowLoader('Deleted', '', true); + try { + await classService.deleteData(id); + } catch (err) { + setError(err); + }finally{ + setClasses((prevClasses) => prevClasses.filter((s) => s.ID_CLASS !== id)); + setLoaderState(prev => ({ + ...prev, + loading: false, + successMessage: 'Your data has been successfully deleted.' + })); + } + } + setLoaderState(prev => ({ ...prev, handleConfirm: confirmDelete })); + }; + + const assignStudentToClass = async () => { + handleClose(); + handleShowLoader('Updated', '', true) + + const selectedStudent = assignStudents.map((selectedData) => ({ + NAME_USERS: studentSlug[selectedData.value], + NISN: selectedData.value + })); + + const assignData = { + NAME_CLASS: assignClass, + STUDENTS: selectedStudent + }; + + try { + await classService.studentToClass(assignData); + } catch (err) { + setError(err); + }finally{ + fetchDataStudent(); + setLoaderState(prev => ({ + ...prev, + loading: false, + successMessage: 'Your data has been successfully updated.' + })); + } + }; + + const [search, setSearch] = useState(""); + const [sort, setSort] = useState(""); + const [page, setCurrentPage] = useState(1); + const [limit, setLimit] = useState(7); + const [totalPages, setTotalPages] = useState(0); + const [totalData, setTotalData] = useState(null); + + const fetchData = async () => { + setLoading(true); + try { + const data = await classService.fetchData(search, sort, page, limit); + setTotalPages(data.payload.totalPages); + setTotalData(data.payload.totalItems); + setClasses(data.payload.classes); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + const handleSerachChange = () => { + fetchData(); + setCurrentPage(1); + } + + const handlePageChange = (pages) => { + setCurrentPage(pages); + } + + const handleLimitsChange = (e) => { + setLimit(e.target.value); + setCurrentPage(1); + } + + useEffect(() => { + fetchDataStudent(); + }, []); + + useEffect(() => { + fetchData(); + }, [page, limit]); + + + return { + classes, + freeStudent, + loading, + error, + formData, + show, + showAssign, + showLoader, + loaderState, + createClass, + editClass, + deleteClass, + assignStudentToClass, + handleFormChange, + assignStudentChange, + assignClassChange, + resetForm, + handleShow, + handleShowAssign, + handleClose, + handleCloseLoader, + + totalPages, + totalData, + setSearch, + page, + handlePageChange, + handleLimitsChange, + handleSerachChange + }; +}; + +export default useClasses; diff --git a/src/roles/admin/manage_classes/services/serviceClasses.jsx b/src/roles/admin/manage_classes/services/serviceClasses.jsx new file mode 100644 index 0000000..fce69fd --- /dev/null +++ b/src/roles/admin/manage_classes/services/serviceClasses.jsx @@ -0,0 +1,93 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchData= async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/class/admin?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching class:', error); + throw error; + } +}; + +const fetchDataFreeStudent = async () => { + try { + const response = await axiosInstance.get(`/class/student/unassigned`); + return response.data; + + } catch (error) { + console.error('Error fetching class:', error); + throw error; + } +}; + +const getClassById = async (id) => { + try { + const response = await axiosInstance.get(`/class/${id}`); + return response.data; + } catch (error) { + console.error(`Error fetching class with ID ${id}:`, error); + throw error; + } +}; + +const getClassStudent = async (id) => { + try { + const response = await axiosInstance.get(`/class/student/${id}`); + return response.data; + } catch (error) { + console.error(`Error fetching class with ID ${id}:`, error); + throw error; + } +} + +const createData = async (classData) => { + try { + const response = await axiosInstance.post(`/class`, classData); + return response.data; + } catch (error) { + console.error('Error adding class:', error); + throw error; + } +}; + +const updateData = async (id, classData) => { + try { + const response = await axiosInstance.put(`/class/update/${id}`, classData); + return response.data; + } catch (error) { + console.error(`Error updating class with ID ${id}:`, error); + throw error; + } +}; + +const deleteData = async (id) => { + try { + const response = await axiosInstance.delete(`/class/delete/${id}`); + return response.data; + } catch (error) { + console.error(`Error deleting class with ID ${id}:`, error); + throw error; + } +}; + +const studentToClass = async (asignData) => { + try { + const response = await axiosInstance.post(`/class/student/update`, asignData); + return response.data; + } catch (error) { + console.error(`Error asign student to class :`, error); + throw error; + } +}; + +export default{ + fetchData, + fetchDataFreeStudent, + getClassById, + getClassStudent, + createData, + updateData, + deleteData, + studentToClass +}; diff --git a/src/roles/admin/manage_classes/views/ClassDetail.jsx b/src/roles/admin/manage_classes/views/ClassDetail.jsx new file mode 100644 index 0000000..adb15d0 --- /dev/null +++ b/src/roles/admin/manage_classes/views/ClassDetail.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Table, Row, Col, Button, Form, Dropdown, DropdownButton, Breadcrumb, Spinner } from 'react-bootstrap'; +import { Link, useParams } from 'react-router-dom'; +import useClassDetail from '../hooks/useClassDetail'; + +const ClassDetail = () => { + const { classId } = useParams(); + const { classData, nameClass, loading, error } = useClassDetail(classId); + + if (error) { + return <>{error}; + } + + return ( +
+ + + + + Academic + Classes + Class Details + + + + + +
+
+

{nameClass}

+
+
+
+ +
+ + + + + + + + + + {loading?( + + + + ):( + classData.length > 0 ?( + classData.map((data, index) => ( + + + + + + )) + ):( + + + + ) + )} + +
NoNISNStudent Name
+ + + + + + +
{index + 1}{data.NISN}{data.NAME_USERS}
+

This class is empty

+
+
+
+ +
+
+ ); +}; + +export default ClassDetail; diff --git a/src/roles/admin/manage_classes/views/ManageClasses.jsx b/src/roles/admin/manage_classes/views/ManageClasses.jsx new file mode 100644 index 0000000..fc75d88 --- /dev/null +++ b/src/roles/admin/manage_classes/views/ManageClasses.jsx @@ -0,0 +1,304 @@ +import React, { useState } from 'react'; +import { NavLink } from 'react-router-dom'; +import { Table, Row, Col, Nav, Tab, Button, Form, InputGroup, Spinner, Modal } from 'react-bootstrap'; +import Select from 'react-select'; +import useClasses from '../hooks/useClasses'; +import ModalOperation from '../../../../components/ui/adminMessageModal/ModalOperation'; +import TablePaginate from '../../../../components/ui/TablePaginate'; + +const ManageClasses = () => { + const { + classes, + freeStudent, + loading, + error, + formData, + show, + showAssign, + showLoader, + loaderState, + createClass, + editClass, + deleteClass, + assignStudentToClass, + handleFormChange, + assignStudentChange, + assignClassChange, + resetForm, + handleShow, + handleShowAssign, + handleClose, + handleCloseLoader, + + page, + totalData, + totalPages, + setSearch, + handlePageChange, + handleLimitsChange, + handleSerachChange + } = useClasses(); + + if (error) { + return (<>{error}); + } + + return ( +
+
+

Classes

+ +
+

Description of Classes.

+ + + + + + + + + + +
+
+

Class List

+
+
+
{ e.preventDefault(); handleSerachChange(); }}> + { setSearch(e.target.value); }} + /> + + + + + + + + + + + + + {loading?( + + + + ):( + classes.map((data, index) => ( + + + + + + + )) + )} + +
NoClass NameCapacityAction
+ + + + + + +
{index + 1}{data.NAME_CLASS}{data.TOTAL_STUDENT} + + + +
+
+
+ Item per page + + + + + + of {totalData} +
+ +
+
+
+
+ +
+
+

Add Class Data

+
+
+
{ e.preventDefault(); createClass(); }}> + + + Class Name* + + + + + + + Class Capacity* + + + + + + +
+ + +
+
+
+
+
+
+ +
+
+ + + + Update Class Data + + +
{ e.preventDefault(); editClass(); }}> + + Class Name* + + + + + + + Class Capacity* + + + + + +
+ +
+
+
+
+ + + + Assign Student to Class + + +
{ e.preventDefault(); assignStudentToClass(); }}> + + Class* + + + + + { + classes.map((data, index) => ( + + )) + } + + + + + Student* + + + + + +
+ +
+
+
+
+ + +
+ ); +}; + +export default ManageClasses; diff --git a/src/roles/teacher/monitoring/hooks/useProgress.jsx b/src/roles/teacher/monitoring/hooks/useProgress.jsx new file mode 100644 index 0000000..3a689b8 --- /dev/null +++ b/src/roles/teacher/monitoring/hooks/useProgress.jsx @@ -0,0 +1,142 @@ +import { useState, useEffect } from 'react'; +import progressService from '../services/serviceProgress'; + +const useProgress = () => { + const [students, setStudents] = useState([]); + const [classes, setClasses] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [searchStudent, setSearchStudent] = useState(""); + const [sortStudent, setSortStudent] = useState(""); + const [pageStudent, setCurrentPageStudent] = useState(1); + const [limitStudent, setLimitStudent] = useState(20); + const [totalPagesStudent, setTotalPagesStudent] = useState(0); + const [totalDataStudent, setTotalDataStudent] = useState(null); + + const [searchClass, setSearchClass] = useState(""); + const [sortClass, setSortClass] = useState(""); + const [pageClass, setCurrentPageClass] = useState(1); + const [limitClass, setLimitClass] = useState(1000); + const [totalPagesClass, setTotalPagesClass] = useState(0); + const [totalDataClass, setTotalDataClass] = useState(null); + + const [selectedId, setSelectedId] = useState(''); + const [selected, setSelected] = useState([]); + const [show, setShow] = useState(false); + const [loadingModal, setLoadingModal] = useState(true); + + const fetchDataStudents = async () => { + setLoading(true); + try { + const student = await progressService.fetchDataStudent(searchStudent, sortStudent, pageStudent, limitStudent); + setStudents(student.payload.monitorings); + setTotalPagesStudent(student.payload.totalPages); + setTotalDataStudent(student.payload.totalItems); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + const fetchDataClasses = async () => { + setLoading(true); + try { + const classes = await progressService.fetchDataClass(searchClass, sortClass, pageClass, limitClass); + setClasses(classes.payload.classes); + setTotalDataClass(classes.payload.totalItems); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + const handleSearchChangeStudents = () => { + fetchDataStudents(); + setCurrentPageStudent(1); + } + + const handlePageChangeStudents = (pages) => { + setCurrentPageStudent(pages); + } + + const handleLimitsChangeStudents = (e) => { + setLimitStudent(e.target.value); + setCurrentPageStudent(1); + } + + const handleSearchChangeClasses = () => { + fetchDataClasses(); + setCurrentPageClass(1); + } + + const handlePageChangeClasses = (pages) => { + setCurrentPageClass(pages); + } + + const handleLimitsChangeClasses = (e) => { + setLimitClass(e.target.value); + setCurrentPageClass(1); + } + + useEffect(() => { + fetchDataStudents(); + }, [pageStudent, limitStudent]); + + useEffect(() => { + fetchDataClasses(); + }, [pageClass, limitClass]); + + const getClassTopic = async (id) => { + setLoadingModal(true); + try { + const classes = await progressService.getTopicByClass(id, '', '', '', 1000); + setSelected(classes.payload.data); + } catch (err) { + console.log(err); + }finally{ + setLoadingModal(false); + } + }; + + const handleShow = (data) => { + setSelectedId(data); + getClassTopic(data); + setShow(true); + }; + const handleClose = () => {setShow(false); setSelected([])}; + + return { + students, + classes, + loading, + error, + + totalPagesStudent, + totalDataStudent, + setSearchStudent, + pageStudent, + handlePageChangeStudents, + handleLimitsChangeStudents, + handleSearchChangeStudents, + + totalPagesClass, + totalDataClass, + setSearchClass, + pageClass, + handlePageChangeClasses, + handleLimitsChangeClasses, + handleSearchChangeClasses, + + selectedId, + selected, + show, + loadingModal, + handleShow, + handleClose, + }; +}; + +export default useProgress; diff --git a/src/roles/teacher/monitoring/hooks/useProgressClass.jsx b/src/roles/teacher/monitoring/hooks/useProgressClass.jsx new file mode 100644 index 0000000..9389238 --- /dev/null +++ b/src/roles/teacher/monitoring/hooks/useProgressClass.jsx @@ -0,0 +1,101 @@ +import { useState, useEffect } from 'react'; +import progressService from '../services/serviceProgress'; + +const useProgressClass = (progressId) => { + const [progress, setProgress] = useState([]); + const [section, setSection] = useState('section'); + const [topic, setTopic] = useState('topic'); + const [name, setName] = useState('class'); + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [search, setSearch] = useState(""); + const [sort, setSort] = useState(""); + const [page, setCurrentPage] = useState(1); + const [limit, setLimit] = useState(20); + const [totalPages, setTotalPages] = useState(0); + const [totalData, setTotalData] = useState(null); + + const fetchData = async () => { + setLoading(true); + const [classId, topicId] = progressId.split("&"); + + const dataId = { + ID_CLASS: classId, + ID_TOPIC: topicId, + }; + + try { + const data = await progressService.fetchDataClassProgress(dataId, search, sort, page, limit); + setProgress(data.payload.levels); + setTotalPages(data.payload.totalPages); + setTotalData(data.payload.totalItems); + + setSection(data.payload.NAME_SECTION) + setTopic(data.payload.NAME_TOPIC) + setName(data.payload.NAME_CLASS) + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + const handleSearchChange = () => { + fetchData(); + setCurrentPage(1); + } + + const handlePageChange = (pages) => { + setCurrentPage(pages); + } + + const handleLimitsChange = (e) => { + setLimit(e.target.value); + setCurrentPage(1); + } + + useEffect(() => { + fetchData(); + }, [page, limit, progressId]); + + function formatLocalDate(isoDate) { + const date = new Date(isoDate); + + const options = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZone: 'Asia/Jakarta' + }; + + return new Intl.DateTimeFormat('id-ID', options).format(date) + .replace(/\//g, '-') + .replace(/\./g, ':') + ' WIB'; + } + + return { + progress, + section, + topic, + name, + nisn, + loading, + error, + + totalPages, + totalData, + setSearch, + page, + handlePageChange, + handleLimitsChange, + handleSearchChange, + formatLocalDate + }; +}; + +export default useProgressClass; diff --git a/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx b/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx new file mode 100644 index 0000000..a89b9f2 --- /dev/null +++ b/src/roles/teacher/monitoring/hooks/useProgressStudent.jsx @@ -0,0 +1,97 @@ +import { useState, useEffect } from 'react'; +import progressService from '../services/serviceProgress'; + +const useProgressStudent = (monitoringId) => { + const [progress, setProgress] = useState([]); + const [section, setSection] = useState('section'); + const [topic, setTopic] = useState('topic'); + const [name, setName] = useState('name'); + const [nisn, setNisn] = useState('nisn'); + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [search, setSearch] = useState(""); + const [sort, setSort] = useState(""); + const [page, setCurrentPage] = useState(1); + const [limit, setLimit] = useState(20); + const [totalPages, setTotalPages] = useState(0); + const [totalData, setTotalData] = useState(null); + + const fetchData = async () => { + setLoading(true); + try { + const data = await progressService.fetchDataStudentProgress(monitoringId, search, sort, page, limit); + setProgress(data.payload.levels); + console.log(data.payload); + setTotalPages(data.payload.totalPages); + setTotalData(data.payload.totalItems); + + setSection(data.payload.NAME_SECTION) + setTopic(data.payload.NAME_TOPIC) + setName(data.payload.NAME_USERS) + setNisn(data.payload.NISN) + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + const handleSearchChange = () => { + fetchData(); + setCurrentPage(1); + } + + const handlePageChange = (pages) => { + setCurrentPage(pages); + } + + const handleLimitsChange = (e) => { + setLimit(e.target.value); + setCurrentPage(1); + } + + useEffect(() => { + fetchData(); + }, [page, limit, monitoringId]); + + function formatLocalDate(isoDate) { + const date = new Date(isoDate); + + const options = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZone: 'Asia/Jakarta' + }; + + return new Intl.DateTimeFormat('id-ID', options).format(date) + .replace(/\//g, '-') + .replace(/\./g, ':') + ' WIB'; + } + + return { + progress, + section, + topic, + name, + nisn, + loading, + error, + + totalPages, + totalData, + setSearch, + page, + handlePageChange, + handleLimitsChange, + handleSearchChange, + formatLocalDate + }; +}; + +export default useProgressStudent; diff --git a/src/roles/teacher/monitoring/services/serviceProgress.jsx b/src/roles/teacher/monitoring/services/serviceProgress.jsx new file mode 100644 index 0000000..251b396 --- /dev/null +++ b/src/roles/teacher/monitoring/services/serviceProgress.jsx @@ -0,0 +1,70 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchDataStudent = async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/monitoring/progress?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching progress:', error); + throw error; + } +}; + +const fetchDataClass = async (search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/class/admin?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching progress:', error); + throw error; + } +}; + +const getStudentById = async (id) => { + try { + const response = await axiosInstance.get(`/progress/${id}`); + return response.data; + } catch (error) { + console.error(`Error fetching progress with student ID ${id}:`, error); + throw error; + } +}; + +const getTopicByClass = async (id, search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/monitoring/class/${id}?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching progress:', error); + throw error; + } +}; + +const fetchDataStudentProgress = async (id, search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/monitoring/progress/${id}?search=${search}&sort=${sort}&page=${page}&limit=${limit}`); + return response.data; + } catch (error) { + console.error('Error fetching progress:', error); + throw error; + } +}; + +const fetchDataClassProgress = async (dataId, search, sort, page, limit) => { + try { + const response = await axiosInstance.get(`/monitoring/class?search=${search}&sort=${sort}&page=${page}&limit=${limit}`, dataId); + return response.data; + } catch (error) { + console.error('Error fetching progress:', error); + throw error; + } +}; + +export default{ + fetchDataStudent, + fetchDataClass, + getStudentById, + getTopicByClass, + fetchDataStudentProgress, + fetchDataClassProgress +}; diff --git a/src/roles/teacher/monitoring/views/ClassProgress.jsx b/src/roles/teacher/monitoring/views/ClassProgress.jsx new file mode 100644 index 0000000..ece6258 --- /dev/null +++ b/src/roles/teacher/monitoring/views/ClassProgress.jsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { Table, Row, Col, Button, Form, Dropdown, DropdownButton, Breadcrumb, Spinner } from 'react-bootstrap'; +import { Link, useParams } from 'react-router-dom'; +import useProgressClass from '../hooks/useProgressClass'; +import TablePaginate from '../../../../components/ui/TablePaginate'; + +const ClassProgress = () => { + const { progressId } = useParams(); + const { + progress, + section, + topic, + name, + nisn, + loading, + error, + + totalPages, + totalData, + setSearch, + page, + handlePageChange, + handleLimitsChange, + handleSearchChange, + formatLocalDate + } = useProgressClass(progressId); + return ( +
+ + + + + Monitoring Progress + Progress Details + + + + + + + + +
+
+

{nisn} - {name}

+ {section} / {topic} +
+
+
{ e.preventDefault(); handleSearchChange(); }}> + { setSearch(e.target.value); }} + /> + + + + + + + + + + + + + + + + {loading?( + + + + ):( + progress.length > 0?( + progress.map((data, index) => ( + + + + + + + + + + )) + ):( + + + + ) + )} + +
NoNISNFull NameLevelScoreStart ExerciseFinish Exercise
+ + + + + + +
{index + 1}{data.NISN}{data.NAME_USERS}{data.NAME_LEVEL}{data.SCORE}{formatLocalDate(data.STUDENT_START)}{formatLocalDate(data.STUDENT_FINISH)}
+

Empty Data

+
+
+
+ Item per page + + + + + + of {totalData} +
+ +
+
+
+ +
+
+ ); +}; + +export default ClassProgress; diff --git a/src/roles/teacher/monitoring/views/Monitoring.jsx b/src/roles/teacher/monitoring/views/Monitoring.jsx new file mode 100644 index 0000000..c51c576 --- /dev/null +++ b/src/roles/teacher/monitoring/views/Monitoring.jsx @@ -0,0 +1,263 @@ +import React, { useState } from 'react'; +import { Table, Row, Col, Nav, Tab, Button, Form, Card, Modal, Spinner } from 'react-bootstrap'; +import useProgress from '../hooks/useProgress'; +import TablePaginate from '../../../../components/ui/TablePaginate'; +import { Link } from 'react-router-dom'; + +const Monitoring = () => { + const { + students, + classes, + loading, + error, + + totalPagesStudent, + totalDataStudent, + setSearchStudent, + pageStudent, + handlePageChangeStudents, + handleLimitsChangeStudents, + handleSearchChangeStudents, + + totalPagesClass, + totalDataClass, + setSearchClass, + pageClass, + handlePageChangeClasses, + handleLimitsChangeClasses, + handleSearchChangeClasses, + + selectedId, + selected, + show, + loadingModal, + handleShow, + handleClose, + } = useProgress(); + + if (error) { + <>{error} + } + + return ( +
+

Learning Progress

+

Follow student activity closely and monitor their English learning journey.

+ + + + + + + + + + +
+
+

Student List

+
+
+
{ e.preventDefault(); handleSearchChangeStudents(); }}> + { setSearchStudent(e.target.value); }} + /> + + + + + + + + + + + + + + + {loading?( + + + + ):( + students.length > 0?( + students.map((data, index) => ( + + + + + + + + + )) + ):( + + + + ) + )} + +
NoNISNFull NameSectionTopicAction
+ + + + + + +
{index + 1}{data.NISN}{data.NAME_USERS}{data.NAME_SECTION}{data.NAME_TOPIC} + +
+

Empty Data

+
+
+
+ Item per page + + + + + + of {totalDataStudent} +
+ +
+
+
+
+ +
+
+

Class List

+
+
+
{ e.preventDefault(); handleSearchChangeClasses(); }}> + { setSearchClass(e.target.value); }} + /> + + + + {loading?( +
+ + + + + + +
+ ):( + classes.map((data, index) => ( + + + + {data.NAME_CLASS} + + {data.STUDENTS < data.TOTAL_STUDENT?( + <> + {data.STUDENTS} + out of {data.TOTAL_STUDENT} students + + ):( + `${data.TOTAL_STUDENT} students` + )} + + {/* */} + + + + + )) + )} +
+
+
+
+
+ +
+
+ + + + Select Topic + + + + + + + Vocabulary + + The legend of naresh + + + + + + + {loadingModal?( +
+ + + + + + +
+ ):( + selected.length > 0?( + + {selected.map((data, index) => ( + + + + {data.NAME_SECTION} + + {data.NAME_TOPIC} + + + + + + ))} + + ):( +

No Progress from this class

+ ) + )} +
+
+
+ ); +}; + +export default Monitoring; diff --git a/src/roles/teacher/monitoring/views/StudentProgress.jsx b/src/roles/teacher/monitoring/views/StudentProgress.jsx new file mode 100644 index 0000000..6fcb899 --- /dev/null +++ b/src/roles/teacher/monitoring/views/StudentProgress.jsx @@ -0,0 +1,139 @@ +import React from 'react'; +import { Table, Row, Col, Button, Form, Dropdown, DropdownButton, Breadcrumb, Spinner } from 'react-bootstrap'; +import { Link, useParams } from 'react-router-dom'; +import useProgressStudent from '../hooks/useProgressStudent'; +import TablePaginate from '../../../../components/ui/TablePaginate'; + +const StudentProgress = () => { + const { progressId } = useParams(); + const { + progress, + section, + topic, + name, + nisn, + loading, + error, + + totalPages, + totalData, + setSearch, + page, + handlePageChange, + handleLimitsChange, + handleSearchChange, + formatLocalDate + } = useProgressStudent(progressId); + return ( +
+ + + + + Monitoring Progress + Progress Details + + + + + + + + +
+
+

{nisn} - {name}

+ {section} / {topic} +
+
+
{ e.preventDefault(); handleSearchChange(); }}> + { setSearch(e.target.value); }} + /> + + + + + + + + + + + + + + + {loading?( + + + + ):( + progress.length > 0?( + progress.map((data, index) => ( + + + + + + + + + )) + ):( + + + + ) + )} + +
NoLevelScoreFeedbackStart ExerciseFinish Exercise
+ + + + + + +
{index + 1}{data.NAME_LEVEL}{data.SCORE}{data.FEEDBACK_STUDENT}{formatLocalDate(data.STUDENT_START)}{formatLocalDate(data.STUDENT_FINISH)}
+

Empty Data

+
+
+
+ Item per page + + + + + + of {totalData} +
+ +
+
+
+ +
+
+ ); +}; + +export default StudentProgress; diff --git a/src/roles/teacher/setting/hooks/useSettings.jsx b/src/roles/teacher/setting/hooks/useSettings.jsx new file mode 100644 index 0000000..c9235f4 --- /dev/null +++ b/src/roles/teacher/setting/hooks/useSettings.jsx @@ -0,0 +1,163 @@ +import { useState, useEffect } from 'react'; +import settingService from '../services/SettingService'; +import { API_URL } from '../../../../utils/Constant'; + +const useSettings = () => { + const [profile, setProfile] = useState(null); + const [selectedImage, setSelectedImage] = useState(null); + const [idUser, setIdUser] = useState(null); + const [loading, setLoading] = useState(true); + const [loadingUpdate, setLoadingUpdate] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + const thumbPath = `${API_URL}/uploads/avatar/`; + + useEffect(() => { + const fetchData = async () => { + try { + const data = await settingService.fetchProfile(); + setProfile(data.payload); + setIdUser(data.payload.ID) + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setProfile((prevProfile) => ({ + ...prevProfile, + [name]: value, + })); + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file && file.size <= 5 * 1024 * 1024) { + setSelectedImage(file); + } else { + setError('File size must be less than 5 MB'); + } + }; + + const handleSubmitProfile = async (e) => { + e.preventDefault(); + setLoadingUpdate(true); + setError(null); + setSuccess(false); + + const formData = new FormData(); + formData.append('NAME_USERS', profile.NAME_USERS); + formData.append('EMAIL', profile.EMAIL); + formData.append('NISN', profile.NISN); + + if (selectedImage) { + formData.append('PICTURE', selectedImage); + } + + try { + await settingService.updateProfile(idUser, formData); + setSuccess(true); + } catch (err) { + setError('Failed to update profile'); + } finally { + setLoadingUpdate(false); + } + }; + + + + //Change Pass + const [formData, setFormData] = useState({ + oldPassword: '', + newPassword: '', + confirmPassword: '', + }); + + const [loadingPass, setLoadingPass] = useState(false); + const [errorPass, setErrorPass] = useState(null); + const [successPass, setSuccessPass] = useState(false); + + const handleInputPassChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleChangePass = async (e) => { + e.preventDefault(); + setLoadingPass(true); + setErrorPass(null); + setSuccessPass(false); + + const passwordData = { + OLD_PASSWORD: formData.oldPassword, + PASSWORD: formData.newPassword, + CONFIRM_PASSWORD: formData.confirmPassword, + }; + + // Validasi bahwa password baru dan confirm password cocok + if (formData.newPassword !== formData.confirmPassword) { + setErrorPass("New Password and Confirm Password don't match"); + setLoadingPass(false); + return; + } + + try { + // Panggil API untuk update password + await settingService.updatePassword(idUser, passwordData); + setSuccessPass(true); // Tampilkan pesan sukses jika berhasil + } catch (err) { + setErrorPass('Failed to update password: ' + err.message); + } finally { + setLoadingPass(false); // Reset loading setelah selesai + } + }; + + const resetForm = () => { + setFormData({ + oldPassword: '', + newPassword: '', + confirmPassword: '', + }); + setSuccess(false); + }; + + const resetStatus = () => { + setLoadingUpdate(false); + setError(null); + setSuccess(false); + setLoadingPass(false); + setErrorPass(null); + setSuccessPass(false); + } + + return { + profile, + selectedImage, + loading, + loadingUpdate, + error, + success, + handleInputChange, + handleImageChange, + handleSubmitProfile, + thumbPath, + + // Change Pass + formData, + handleInputPassChange, + handleChangePass, + loadingPass, + errorPass, + successPass, + resetForm, + resetStatus + }; + +}; + +export default useSettings; diff --git a/src/roles/teacher/setting/services/SettingService.jsx b/src/roles/teacher/setting/services/SettingService.jsx new file mode 100644 index 0000000..fb395d3 --- /dev/null +++ b/src/roles/teacher/setting/services/SettingService.jsx @@ -0,0 +1,55 @@ +import axios from 'axios'; +import { API_URL } from '../../../../utils/Constant'; + +const config = { + headers: { + Authorization: localStorage.getItem('token') + } +}; + +const fetchProfile = async () => { + try { + const response = await axios.get(`${API_URL}/getMe`, config); + return response.data; + } catch (error) { + throw error; + } +}; + +const updateProfile = async (id, formData) => { + + const cfg = { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: localStorage.getItem('token') + }, + }; + + try { + const response = await axios.put(`${API_URL}/user/update/${id}`, formData, cfg); + return response.data; + } catch (error) { + throw error; + } +}; + +const updatePassword = async (userId, passwordData) => { + const cfg = { + headers: { + Authorization: localStorage.getItem('token'), + 'Content-Type': 'application/json', + }, + }; + try { + const response = await axios.put(`${API_URL}/user/update/password/${userId}`, passwordData, cfg); + return response.data; + } catch (error) { + throw error; + } +}; + +export default { + fetchProfile, + updateProfile, + updatePassword +}; diff --git a/src/roles/teacher/setting/views/Setting.jsx b/src/roles/teacher/setting/views/Setting.jsx new file mode 100644 index 0000000..9adbb1f --- /dev/null +++ b/src/roles/teacher/setting/views/Setting.jsx @@ -0,0 +1,236 @@ +import React, { useState } from 'react'; +import { Table, Row, Col, Nav, Tab, Button, Form, InputGroup, Dropdown, DropdownButton, Modal } from 'react-bootstrap'; +import avatar from '../../../../assets/images/default-avatar.jpg'; +import ilustration from '../../../../assets/images/illustration/changePass.png'; +import successModal from '../../../../assets/images/illustration/successModal.png'; +import errorModal from '../../../../assets/images/illustration/IllustrationForgot.png'; +import useSettings from '../hooks/useSettings'; + +const Setting = () => { + const { + profile, + selectedImage, + loading, + loadingUpdate, + error, + success, + handleInputChange, + handleImageChange, + handleSubmitProfile, + formData, + handleInputPassChange, + handleChangePass, + loadingPass, + errorPass, + successPass, + resetForm, + resetStatus, + thumbPath, + } = useSettings(); + + const [showModal, setShowModal] = useState(false); + + const handleCloseModal = () => { + setShowModal(false); + resetForm(); + }; + + const handleFormProfile = async (e) => { + resetStatus(); + setShowModal(true); + await handleSubmitProfile(e); + }; + + const handleFormPass = async (e) => { + resetStatus(); + setShowModal(true); + await handleChangePass(e); + }; + + return ( +
+

Change based on your own!

+

Update your profile and password to personalize your experience and keep the account secure.

+ + + + + + + + + + + + +
+
+
+ + NIP* + + + + + Full Name* + + + + + Email Address* + + + + +
+
+
+ + +
+
+
+

Avatar

+ avatar +
+ +
+ Ensure the file size is no larger than 5 MB +
+
+
+ +
+
+ + + +
+
+
+ + Old Password* + + + + New Password* + + + + Confirm New Password* + + + +
+
+
+ + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ + + +

+ {error && "ERROR"} + {success && 'Looking Sharp!'} + {errorPass && "ERROR"} + {successPass && 'Security Boosted!'} +

+ + { + loadingUpdate || loadingPass ?( +
+ + + + + + +
+ ):( + imgModal + ) + } + +

+ {error && error} + {success && 'Your profile has been successfully updated.'} + {errorPass && errorPass} + {successPass && "Your password change was successful. You're good to go!"} +

+ + +
+
+
+ ); +}; + +export default Setting; diff --git a/src/roles/user/NotFound.jsx b/src/roles/user/NotFound.jsx new file mode 100644 index 0000000..49974c6 --- /dev/null +++ b/src/roles/user/NotFound.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const NotFound = () => { + return ( +
+

Not Found

+
+ ); +}; + +export default NotFound; diff --git a/src/roles/user/UserRoutes.jsx b/src/roles/user/UserRoutes.jsx new file mode 100644 index 0000000..f0d6ac7 --- /dev/null +++ b/src/roles/user/UserRoutes.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; + +import UserLayout from '../../components/layout/user/UserLayout'; +import NotFound from './NotFound'; + +import Dashboard from './dashboard/views/Dashboard'; +import Learning from './learning/views/Learning'; +import Setting from './setting/views/Setting'; + +import Topic from './topic/views/Topic'; +import Level from './level/views/Level'; +import Exercise from './exercise/views/Exercise'; +import ExerciseResult from './exercise/views/ExerciseResult'; +import ExerciseHistory from './history/views/ExerciseHistory'; +import Material from './material/views/Material'; + +import ProtectedRoute from '../../utils/ProtectedRoute'; +import { SlugProvider } from '../../utils/SlugContext'; + +import '../../assets/styles/user.css'; +import useAuth from '../guest/auth/hooks/useAuth'; + +import ExerciseTest from './exerciseCombine/views/Exercise'; + +const UserRoutes = () => { + const { logout } = useAuth(); + return ( + + + + }> + } /> + + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + + + + + ); +}; + +export default UserRoutes; diff --git a/src/roles/user/dashboard/hooks/useDashboards.jsx b/src/roles/user/dashboard/hooks/useDashboards.jsx new file mode 100644 index 0000000..34dee5e --- /dev/null +++ b/src/roles/user/dashboard/hooks/useDashboards.jsx @@ -0,0 +1,30 @@ +import { useState, useEffect } from 'react'; +import homeService from '../services/dashboardService'; +import { API_URL } from '../../../../utils/Constant'; + +const useDashboards = () => { + const [journey, setJourney] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const thumbPath = `${API_URL}/uploads/section/`; + + useEffect(() => { + const fetchData = async () => { + try { + const data = await homeService.fetchJourney(); + if (data.payload !== null) { + setJourney(data.payload); + } + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + + return { journey, loading, error, thumbPath }; +}; + +export default useDashboards; diff --git a/src/roles/user/dashboard/services/dashboardService.jsx b/src/roles/user/dashboard/services/dashboardService.jsx new file mode 100644 index 0000000..89bac0d --- /dev/null +++ b/src/roles/user/dashboard/services/dashboardService.jsx @@ -0,0 +1,14 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchJourney = async () => { + try { + const response = await axiosInstance.get(`/topic/complete`); + return response.data; + } catch (error) { + throw error; + } +}; + +export default { + fetchJourney, +}; diff --git a/src/roles/user/dashboard/views/Dashboard.jsx b/src/roles/user/dashboard/views/Dashboard.jsx new file mode 100644 index 0000000..2b020ce --- /dev/null +++ b/src/roles/user/dashboard/views/Dashboard.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Container, Row, Col, Card, Button, ProgressBar } from 'react-bootstrap'; +import newBie from '../../../../assets/images/illustration/emptyJourney.png'; +import reading from '../../../../assets/images/illustration/reading.png'; +import listening from '../../../../assets/images/illustration/listening.png'; +import { Link, NavLink } from 'react-router-dom'; +import useDashboards from '../hooks/useDashboards'; +import { slugify } from '../../../../utils/Constant'; +import Skeleton from 'react-loading-skeleton'; + +const Dashboard = () => { + + const { journey, loading, error, thumbPath} = useDashboards(); + const noData = () =>{ return journey.lenght > 0 ? false : true }; + + if (error) { + return
Error: {error.message}
; + } + + return ( +
+

Hi, Dimas Anhar !

+

Let’s evolve together with adaptive learning tailored just for you.

+

Your Last Journey!

+ + + + {loading ?( + <> + +
+ +
+ + +
+ +
+
+ +
+ +
+ + +
+ +
+
+ + ) : ( + noData() ?( + +

Still new?

+

Begin your journey!

+ + + + ):( + journey.map(data => ( + + + +
+ {data.NAME_SECTION} + + {data.DESCRIPTION_SECTION} + +
+
+
+
+ + {((data.COMPLETED_TOPICS / data.TOTAL_TOPICS)*100).toFixed(0)}% +
+

{data.COMPLETED_TOPICS} / {data.TOTAL_TOPICS} Topics

+
+ +
+
+
+ )) + ) + )} + +
+
+
+ ); +}; + +export default Dashboard; diff --git a/src/roles/user/exercise/hooks/useExercises.jsx b/src/roles/user/exercise/hooks/useExercises.jsx new file mode 100644 index 0000000..35555fb --- /dev/null +++ b/src/roles/user/exercise/hooks/useExercises.jsx @@ -0,0 +1,119 @@ +import { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import exerciseService from '../services/exerciseService'; +import { useSlugContext } from '../../../../utils/SlugContext'; +import { unSlugify } from '../../../../utils/Constant'; + +export const useExercises = (topic, level) => { + const [questions, setQuestions] = useState([]); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); + const [answers, setAnswers] = useState({}); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [levelId, setLevelId] = useState(null); + const [learningId, setLearningId] = useState(null); + const { topicSlugMap } = useSlugContext(); + + const { section } = useParams(); + const navigate = useNavigate(); + + const fetchData = async () => { + try { + const getLevelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level)); + setLevelId(getLevelId); + + const stdLearning = await exerciseService.createStdLearning({ ID_LEVEL: getLevelId }); + setLearningId(stdLearning.payload.ID_STUDENT_LEARNING); + + const data = await exerciseService.fetchExercise(getLevelId); + setQuestions(data); + if (localStorage.getItem(getLevelId)) { + // const savedAnswers = JSON.parse(localStorage.getItem(getLevelId)); + const savedAnswers = JSON.parse(localStorage.getItem(stdLearning.payload.ID_STUDENT_LEARNING)); + setAnswers(savedAnswers); + }else{ + setAnswers(new Array(data.length).fill(null)); + } + } catch (error) { + console.error("something wrong : ", error); + setError(error); + }finally{ + setIsLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, [topic, level]); + + const handleAnswer = (index, answer, qId) => { + const newAnswers = [...answers]; + newAnswers[index] = { + ID_STUDENT_LEARNING: learningId, + ID_ADMIN_EXERCISE: qId, + ANSWER_STUDENT: answer + }; + setAnswers(newAnswers); + // localStorage.setItem(levelId, JSON.stringify(newAnswers)); + localStorage.setItem(learningId, JSON.stringify(newAnswers)); + }; + + const nextQuestion = () => { + if (currentQuestionIndex < questions.length - 1) { + setCurrentQuestionIndex(currentQuestionIndex + 1); + } + }; + + const prevQuestion = () => { + if (currentQuestionIndex > 0) { + setCurrentQuestionIndex(currentQuestionIndex - 1); + } + }; + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === 'ArrowLeft') { + prevQuestion(); + } else if (event.key === 'ArrowRight') { + nextQuestion(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [prevQuestion, nextQuestion]); + + const handleSubmit = () => { + const allAnswered = answers.every((answer) => answer !== null); + return allAnswered; + }; + + const handleConfirmSubmit = async () => { + const answer = JSON.parse(localStorage.getItem(levelId)); + try { + const sendAnswer = await exerciseService.sumbitAnswer({ answers: answer }); + } catch (error) { + console.error(error); + }finally{ + navigate(`/learning/module/${section}/${topic}/${level}/result`); + } + }; + + return { + questions, + currentQuestion: questions[currentQuestionIndex], + currentQuestionIndex, + setCurrentQuestionIndex, + answers, + isLoading, + error, + handleAnswer, + nextQuestion, + prevQuestion, + handleSubmit, + handleConfirmSubmit, + }; +}; diff --git a/src/roles/user/exercise/hooks/useResults.jsx b/src/roles/user/exercise/hooks/useResults.jsx new file mode 100644 index 0000000..0579696 --- /dev/null +++ b/src/roles/user/exercise/hooks/useResults.jsx @@ -0,0 +1,96 @@ +import { useState, useEffect } from 'react'; +import exerciseService from '../services/exerciseService'; +import { useSlugContext } from '../../../../utils/SlugContext'; +import { unSlugify } from '../../../../utils/Constant'; + +import down from '../../../../assets/images/illustration/resultDown.png'; +import stay from '../../../../assets/images/illustration/resultStay.png'; +import jump from '../../../../assets/images/illustration/resultJump.png'; +import finish from '../../../../assets/images/illustration/resultFinish.png'; + +function getLevelNumber(str) { + const match = str.match(/\d+/); + return match ? parseInt(match[0]) : 0; +} + +function getStatus(curent, next) { + const setCurrent = getLevelNumber(curent); + const setNext = getLevelNumber(next); + let status = ''; + + if (setCurrent < setNext) { + status = 'jump'; + } else if(setCurrent > setNext){ + status = 'down'; + }else{ + status = 'stay'; + } + + return status; +} + +const useResults = (topic, level) => { + const { topicSlugMap } = useSlugContext(); + const [results, setResult] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [resultScore, setResultScore] = useState(null); + const [resultImage, setResultImage] = useState(null); + const [resultText, setResultText] = useState(null); + const [resultColor, setResultColor] = useState(null); + const [nextLevel, setNextLevel] = useState(null); + + const fetchData = async () => { + try { + const getLevelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level)); + const getStdLearning = await exerciseService.checkStdLearning(getLevelId); + const data = await exerciseService.getScore(getStdLearning); + setResult(data.payload); + + const status = getStatus(data.payload.CURRENT_LEVEL_NAME, data.payload.NEXT_LEARNING_NAME); + setResultScore(data.payload.SCORE); + setNextLevel(getLevelNumber(data.payload.NEXT_LEARNING_NAME)); + + if (data.payload.IS_PASS === 1) { + setResultImage(finish); + setResultText(<>You conquered LEVEL 6 !
You're a rock star!); + setNextLevel("COMPLETE"); + setResultColor('text-blue'); + + }else{ + setNextLevel(data.payload.NEXT_LEARNING_NAME); + if (status === "jump") { + setResultImage(jump); + setResultText(<>Great job! You can jump to...); + setResultColor('text-blue'); + + } else if (status === "down") { + setResultImage(down); + setResultText(<>Good effort! To improve, you can focus on!); + setResultColor('text-red'); + + } else if (status === "stay") { + setResultImage(stay); + setResultText(<> Learning is a journey, let's explore this level further to deepen your knowledge.); + setResultColor(''); + + } + } + + } catch (error) { + console.error("something wrong : ", error); + setError(error); + }finally{ + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, [topic, level]); + + return { loading, error, resultScore, resultImage, resultText, resultColor, nextLevel }; +}; + +export default useResults; diff --git a/src/roles/user/exercise/services/exerciseService.jsx b/src/roles/user/exercise/services/exerciseService.jsx new file mode 100644 index 0000000..a76ac61 --- /dev/null +++ b/src/roles/user/exercise/services/exerciseService.jsx @@ -0,0 +1,87 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const getSoalNumber = (title) => { + const match = title.match(/\d+$/); + return match ? parseInt(match[0], 10) : 0; +}; + +const getLevelId = async (topicId, levelName) => { + try { + const response = await axiosInstance.get(`/level/topic/${topicId}`); + const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName); + return filteredData[0].ID_LEVEL; + } catch (error) { + return []; + } +}; + +const createStdLearning = async (data) => { + try { + const response = await axiosInstance.post(`/stdLearning`, data); + return response.data; + } catch (error) { + console.error('Error creating std_learning:', error); + throw error; + } +}; + +const fetchExercise = async (idLevel) => { + try { + const response = await axiosInstance.get(`/exercise/level/${idLevel}`); + // return response.data; + + const sortedData = response.data.payload.EXERCISES.sort((a, b) => { + return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE); + }); + + return sortedData; + } catch (error) { + console.error('Error fetching exercise data:', error); + throw error; + } +}; + + + + +const sumbitAnswer = async (dataAnswer) => { + try { + const response = await axiosInstance.post(`/stdExercise`, dataAnswer); + return response.data; + } catch (error) { + console.error('Error submit exercise:', error); + throw error; + } +}; + +const checkStdLearning = async (level) => { + try { + const response = await axiosInstance.get(`/stdLearning/level/${level}`); + return response.data.payload.ID_STUDENT_LEARNING; + } catch (error) { + // console.error('Error checking std_learning:', error); + // throw error; + return null; + } +}; + +const getScore = async (stdLearning) => { + try { + const response = await axiosInstance.get(`/stdLearning/score/${stdLearning}`); + return response.data; + } catch (error) { + console.error('Error fetching result:', error); + throw error; + } +}; + + +export default { + getLevelId, + checkStdLearning, + createStdLearning, + fetchExercise, + + sumbitAnswer, + getScore +}; diff --git a/src/roles/user/exercise/views/Exercise.jsx b/src/roles/user/exercise/views/Exercise.jsx new file mode 100644 index 0000000..5ed9d63 --- /dev/null +++ b/src/roles/user/exercise/views/Exercise.jsx @@ -0,0 +1,190 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { useExercises } from '../hooks/useExercises'; +import { Container, Row, Col, ListGroup, Button, Modal, Alert, OverlayTrigger, Popover } from 'react-bootstrap'; +import MultipleChoiceQuestion from './components/MultipleChoiceQuestion'; +import TrueFalseQuestion from './components/TrueFalseQuestion'; +import MatchingPairsQuestion from './components/MatchingPairsQuestion'; +import Skeleton from 'react-loading-skeleton'; + +import ilustration from '../../../../assets/images/illustration/submitExercise.png'; + +const Exercise = () => { + const [showModal, setShowModal] = useState(false); + const [showAlert, setShowAlert] = useState(false); + const { topic, level } = useParams(); + const { + questions, + currentQuestion, + currentQuestionIndex, + setCurrentQuestionIndex, + answers, + isLoading, + error, + handleAnswer, + nextQuestion, + prevQuestion, + handleSubmit, + handleConfirmSubmit + } = useExercises(topic, level); + + const handleSubmitWrapper = () => { + if (handleSubmit()) { + setShowModal(true); + } else { + setShowAlert(true); + } + }; + const handleCloseModal = () => setShowModal(false); + + const renderQuestion = () => { + switch (currentQuestion.QUESTION_TYPE) { + case 'MCQ': + return ( + + ); + case 'TFQ': + return ( + + ); + case 'MPQ': + return ( + + ); + default: + return

Unknown question type.

; + } + }; + + const popover = ( + + Tips + +
    +
  • Click the left arrow key to go to the previous question
  • +
  • Click the right arrow key to go to the next question
  • +
+
+
+ ); + + if (isLoading) { + return ( +
+
+ +
+
+ + + +
+
+ ); + } + + if (error) return

Error loading questions.

; + + return ( + + + +
+
+

Pretest

+ + + +
+ + {questions.map((q, index) => ( + setCurrentQuestionIndex(index)} + className={`border-0 rounded-3 number-label ${answers[index] !== null ? 'answered fw-bold' : ''}`} + style={{ cursor: 'pointer' }} + > + + + {index+1} + + ))} + +
+ + + +
+
+ +
{`Questions ${currentQuestionIndex + 1} of ${questions.length}`}
+ + +
+
+ {renderQuestion()} +
+
+ +
+ + setShowAlert(false)} dismissible> + ATTENTION! + Please answer all questions before submitting. + + + + +

Proceed with Submission?

+ +

Confirm submission? There's no going back.

+
+ + +
+
+
+
+ ); +}; + +export default Exercise; + + + diff --git a/src/roles/user/exercise/views/ExerciseResult.jsx b/src/roles/user/exercise/views/ExerciseResult.jsx new file mode 100644 index 0000000..5b60a12 --- /dev/null +++ b/src/roles/user/exercise/views/ExerciseResult.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Link, useParams, useLocation } from 'react-router-dom'; +import { Container, Row, Col, Button } from 'react-bootstrap'; +import useResults from '../hooks/useResults'; + +import Skeleton from 'react-loading-skeleton'; + +const ExerciseResult = () => { + const { section, topic, level } = useParams(); + + const { loading, error, resultScore, resultImage, resultText, resultColor, nextLevel } = useResults(topic, level); + + return ( + + + + {loading ? + <> + + + + + + + : + <> +

Your Result!

+
{resultScore} / 100
+ / +

+ {resultText} +

+

{nextLevel}

+ + + } + +
+
+ ); +}; + +export default ExerciseResult; + diff --git a/src/roles/user/exercise/views/components/ExerciseMedia.jsx b/src/roles/user/exercise/views/components/ExerciseMedia.jsx new file mode 100644 index 0000000..b31ee5b --- /dev/null +++ b/src/roles/user/exercise/views/components/ExerciseMedia.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { API_URL } from '../../../../../utils/Constant'; + +const ExerciseMedia = ({ image, audio, video }) => { + const mediaPath = `${API_URL}/uploads/exercise`; + + return ( +
+ {image !== null && ( +
+ + {/*

{`${API_URL}/exercise/image/${image}`}

*/} +
+ )} + {audio !== null && ( +
+ {/* */} +

{`${mediaPath}/audio/${audio}`}

+
+ )} + {video !== null && ( + // +
+

{video}

+
+ )} +
+ ); +}; + +export default ExerciseMedia; diff --git a/src/roles/user/exercise/views/components/MatchingPairsQuestion.jsx b/src/roles/user/exercise/views/components/MatchingPairsQuestion.jsx new file mode 100644 index 0000000..b112dd6 --- /dev/null +++ b/src/roles/user/exercise/views/components/MatchingPairsQuestion.jsx @@ -0,0 +1,219 @@ +import React, { useState, useEffect } from 'react'; + +import MediaViewer from './MediaViewer'; +import { API_URL } from '../../../../../utils/Constant'; + +// const colors = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9']; +// const colors = ['#0090FF', '#FC6454', '#46E59A', '#FBD025', '#E355D5']; +const colors = ['#FC6454', '#FBD025', '#46E59A', '#0090FF','#E355D5']; + +const shuffleArray = (array) => { + return array + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); +}; + +function arrayToString(arr) { + return arr.join(', '); +} + +function stringToArray(str) { + return str.split(', '); +} + +const MatchingPairsQuestion = ({ question, onAnswer, studentAnswer, index }) => { + const savedAnswer = studentAnswer !== null ? studentAnswer.ANSWER_STUDENT : null; + const [pairs, setPairs] = useState([]); + const [selectedLeft, setSelectedLeft] = useState(null); + const [selectedRight, setSelectedRight] = useState(null); + const [rightOptions, setRightOptions] = useState([]); + const [isComplete, setIsComplete] = useState(false); + const [isShuffled, setIsShuffled] = useState(false); + + useEffect(() => { + const handleClickOutside = (event) => { + if (!event.target.closest('.mp-choice')) { + setSelectedLeft(null); + setSelectedRight(null); + } + }; + + document.addEventListener('click', handleClickOutside); + + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, []); + + useEffect(() => { + const initialPairs = question.matchingPairs.map((pair) => ({ + left: pair.LEFT_PAIR, + right: '', + color: colors[question.matchingPairs.indexOf(pair) % colors.length], + })); + + if (savedAnswer !== null) { + const arrSavedAnswer = stringToArray(savedAnswer); + const updatedPairs = initialPairs.map((pair, index) => ({ + ...pair, + right: arrSavedAnswer[index], + })); + setPairs(updatedPairs); + } else { + setPairs(initialPairs); + } + }, [question, savedAnswer]); + + useEffect(() => { + setIsShuffled(true); + setRightOptions(shuffleArray(question.matchingPairs.map((pair) => pair.RIGHT_PAIR))); + }, [question]); + + const handleLeftClick = (index) => { + setSelectedLeft(index); + const status = pairs.findIndex(item => item.right === rightOptions[selectedRight]); + if (selectedRight !== null) { + makePair(index, selectedRight, status); + } + }; + + const handleRightClick = (index) => { + setSelectedRight(index); + const status = pairs.findIndex(item => item.right === rightOptions[index]); + if (selectedLeft !== null) { + makePair(selectedLeft, index, status); + } + }; + + const makePair = (leftIndex, rightIndex, changePair) => { + const newPairs = [...pairs]; + if (changePair > -1) { + setIsComplete(false); + pairs[changePair].right = ''; + } + + const selectedRightValue = rightOptions[rightIndex]; + newPairs[leftIndex].right = selectedRightValue; + setPairs(newPairs); + // console.log(newPairs); + + setSelectedLeft(null); + setSelectedRight(null); + + const allPairsMatched = newPairs.every((pair) => pair.right !== ''); + if (allPairsMatched && !isComplete) { + setIsComplete(true); + + const rightAnswers = newPairs.map((pair) => pair.right); + onAnswer(index, arrayToString(rightAnswers), question.ID_ADMIN_EXERCISE); + } + }; + + const mediaUrls = []; + const mediaPath = `${API_URL}/uploads/exercise`; + + if (question.IMAGE) mediaUrls.push(`${mediaPath}/image/${question.IMAGE}`); + if (question.AUDIO) mediaUrls.push(`${mediaPath}/audio/${question.AUDIO}`); + if (question.VIDEO) mediaUrls.push(question.VIDEO); + + return ( +
+ + {mediaUrls.length > 0 && } + + +

{question.QUESTION}

+
+ {/* Bagian kiri */} +
+ {pairs.map((pair, index) => ( +
handleLeftClick(index)} + style={{ + display: 'flex', alignItems: 'center', cursor: 'pointer', + }} + > + + {index + 1} + + + {pair.left} + +
+ ))} +
+ + {/* Bagian kanan */} +
+ {rightOptions.map((right, index) => ( +
handleRightClick(index)} + style={{ + display: 'flex', alignItems: 'center', cursor: 'pointer', + }} + > + pair.right === right) ? '#ffffff' : '#000000', + backgroundColor: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '#ccc' : '#ffffff'), + border: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '2px dashed #ffffff' : '1px solid #000000'), + }} + > + {index + 1} + + pair.right === right) ? '#ffffff' : '#000000'), + backgroundColor: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '#ccc' : '#ffffff'), + border: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '2px dashed #ffffff' : '1px solid #000000'), + }} + > + {right} + +
+ ))} +
+
+
+ ); +}; + +export default MatchingPairsQuestion; diff --git a/src/roles/user/exercise/views/components/MediaViewer.jsx b/src/roles/user/exercise/views/components/MediaViewer.jsx new file mode 100644 index 0000000..869c9dd --- /dev/null +++ b/src/roles/user/exercise/views/components/MediaViewer.jsx @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from 'react'; +import Skeleton from 'react-loading-skeleton'; + +const MediaViewer = ({ mediaUrls }) => { + const [loadedMedia, setLoadedMedia] = useState([]); + const [loading, setLoading] = useState(true); + + function isMediaUrl(url) { + const urls = url[0]; + const audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac']; + const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp']; + + const lowercasedUrl = urls.toLowerCase(); + const isAudio = audioExtensions.some(extension => lowercasedUrl.endsWith(extension)); + const isImage = imageExtensions.some(extension => lowercasedUrl.endsWith(extension)); + + return isAudio || isImage; + } + + const preloadMedia = (urls) => { + return Promise.all( + urls.map((url) => { + return new Promise((resolve, reject) => { + const mediaType = url.split('.').pop(); + + if (['jpg', 'jpeg', 'png', 'gif'].includes(mediaType)) { + const img = new Image(); + img.src = url; + img.onload = () => resolve({ type: 'image', url }); + img.onerror = () => reject(`Failed to load image: ${url}`); + } else if (['mp3', 'wav'].includes(mediaType)) { + const audio = new Audio(); + audio.src = url; + audio.onloadedmetadata = () => resolve({ type: 'audio', url }); + audio.onerror = () => reject(`Failed to load audio: ${url}`); + } else { + resolve({ type: 'unknown', url }); + } + }); + }) + ); + }; + + const renderVideo = (url) => { + // Cek apakah ini link YouTube + if (url.includes('youtube.com') || url.includes('youtu.be')) { + const youtubeId = url.includes('youtube.com') + ? new URLSearchParams(new URL(url).search).get('v') // Ambil ID dari link YouTube + : url.split('/').pop(); // Ambil ID dari youtu.be link + + return ( + + ); + } else if (url.includes('drive.google.com')) { + const driveId = url.includes('file/d/') ? url.split('file/d/')[1].split('/')[0] : ''; + return ( + + ); + } else { + return ( + + ); + } + }; + + useEffect(() => { + if (mediaUrls && mediaUrls.length > 0 && isMediaUrl(mediaUrls)) { + preloadMedia(mediaUrls) + .then((loaded) => { + setLoadedMedia(loaded); + setLoading(false); + }) + .catch((error) => { + console.error(error); + setLoading(false); + }); + } else { + setLoading(false); + } + }, [mediaUrls]); + + if (loading) { + return ( + + ); + } + + return ( +
+ {isMediaUrl(mediaUrls) ? ( + loadedMedia.map((media, index) => { + if (media.type === 'image') { + return {`Media; + } else if (media.type === 'audio') { + return ; + } else { + return
Unknown media type
; + } + }) + ) : ( + renderVideo(mediaUrls[0]) // Panggil fungsi renderVideo dengan URL yang benar + )} +
+ ); +}; + +export default MediaViewer; diff --git a/src/roles/user/exercise/views/components/MultipleChoiceQuestion.jsx b/src/roles/user/exercise/views/components/MultipleChoiceQuestion.jsx new file mode 100644 index 0000000..5305bbc --- /dev/null +++ b/src/roles/user/exercise/views/components/MultipleChoiceQuestion.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import MediaViewer from './MediaViewer'; +import { API_URL } from '../../../../../utils/Constant'; + +const MultipleChoiceQuestion = ({ question, onAnswer, studentAnswer, index }) => { + const savedAnswer = studentAnswer !== null ? studentAnswer.ANSWER_STUDENT : null; + const options = question.multipleChoices[0]; + + const mediaUrls = []; + const mediaPath = `${API_URL}/uploads/exercise`; + + if (question.IMAGE) mediaUrls.push(`${mediaPath}/image/${question.IMAGE}`); + if (question.AUDIO) mediaUrls.push(`${mediaPath}/audio/${question.AUDIO}`); + if (question.VIDEO) mediaUrls.push(question.VIDEO); + + const handleChange = (value) => { + onAnswer(index, value, question.ID_ADMIN_EXERCISE); + }; + + function getOptionLetter(option) { + const match = option.match(/OPTION_(\w)/); + return match ? match[1] : null; + } + + return ( +
+ {mediaUrls.length > 0 && } + +

{question.QUESTION}

+
+ {['OPTION_A', 'OPTION_B', 'OPTION_C', 'OPTION_D', 'OPTION_E'].map( + (optionKey) => { + if (options[optionKey]) { + return ( +
handleChange(getOptionLetter(optionKey))} + > + {getOptionLetter(optionKey)} + {options[optionKey]} +
+ ); + } + return null; + } + )} +
+
+ ); +}; + +export default MultipleChoiceQuestion; diff --git a/src/roles/user/exercise/views/components/TrueFalseQuestion.jsx b/src/roles/user/exercise/views/components/TrueFalseQuestion.jsx new file mode 100644 index 0000000..f39d959 --- /dev/null +++ b/src/roles/user/exercise/views/components/TrueFalseQuestion.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import MediaViewer from './MediaViewer'; +import { API_URL } from '../../../../../utils/Constant'; + +const TrueFalseQuestion = ({ question, onAnswer, studentAnswer, index }) => { + const savedAnswer = studentAnswer !== null ? studentAnswer.ANSWER_STUDENT : null; + const mediaUrls = []; + const mediaPath = `${API_URL}/uploads/exercise`; + + if (question.IMAGE) mediaUrls.push(`${mediaPath}/image/${question.IMAGE}`); + if (question.AUDIO) mediaUrls.push(`${mediaPath}/audio/${question.AUDIO}`); + if (question.VIDEO) mediaUrls.push(question.VIDEO); + + const handleChange = (value) => { + onAnswer(index, value, question.ID_ADMIN_EXERCISE); + }; + + return ( +
+ {mediaUrls.length > 0 && } + +

{question.QUESTION}

+
+ +
handleChange('1')} + > + A + TRUE +
+ +
handleChange('0')} + > + B + FALSE +
+ +
+
+ ); +}; + +export default TrueFalseQuestion; diff --git a/src/roles/user/exerciseCombine/hooks/useExercises.jsx b/src/roles/user/exerciseCombine/hooks/useExercises.jsx new file mode 100644 index 0000000..0e2d850 --- /dev/null +++ b/src/roles/user/exerciseCombine/hooks/useExercises.jsx @@ -0,0 +1,119 @@ +import { useState, useEffect } from 'react'; +import exerciseService from '../services/exerciseService'; +import { useSlugContext } from '../../../../utils/SlugContext'; +import { unSlugify } from '../../../../utils/Constant'; + +export const useExercise = (topic, level) => { + const [questions, setQuestions] = useState([]); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); + const [answers, setAnswers] = useState({}); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [levelId, setLevelId] = useState(null); + const [learningId, setLearningId] = useState(null); + const { topicSlugMap } = useSlugContext(); + + const fetchData = async () => { + try { + const getLevelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level)); + setLevelId(getLevelId); + const stdLearning = await exerciseService.checkStdLearning(getLevelId); + if (stdLearning === null) { + const newLearningId = await exerciseService.createStdLearning({ ID_LEVEL: getLevelId }); + setLearningId(newLearningId); + }else{ + setLearningId(stdLearning); + } + + + const data = await exerciseService.fetchExercise(getLevelId); + setQuestions(data); + if (localStorage.getItem(getLevelId)) { + const savedAnswers = JSON.parse(localStorage.getItem(getLevelId)); + setAnswers(savedAnswers); + }else{ + setAnswers(new Array(data.length).fill(null)); + } + } catch (error) { + console.error("something wrong : ", error); + setError(error); + }finally{ + setIsLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, [topic, level]); + + const handleAnswer = (index, answer) => { + // const newAnswers = { ...answers, [index]: answer }; + const newAnswers = [...answers]; + newAnswers[index] = answer; + setAnswers(newAnswers); + localStorage.setItem(levelId, JSON.stringify(newAnswers)); + }; + + const handleMatchingPairs = (questionId, pairs) => { + const isComplete = pairs.every(pair => pair.left && pair.right); + if (isComplete) { + handleAnswer(questionId, pairs); + } + }; + + const nextQuestion = () => { + if (currentQuestionIndex < questions.length - 1) { + setCurrentQuestionIndex(currentQuestionIndex + 1); + } + }; + + const prevQuestion = () => { + if (currentQuestionIndex > 0) { + setCurrentQuestionIndex(currentQuestionIndex - 1); + } + }; + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === 'ArrowLeft') { + prevQuestion(); + } else if (event.key === 'ArrowRight') { + nextQuestion(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [prevQuestion, nextQuestion]); + + const handleSubmit = () => { + const allAnswered = answers.every((answer) => answer !== null); + return allAnswered; + }; + + const handleConfirmSubmit = () => { + setShowModal(false); + navigate(`/learning/module/${section}/${topic}/${level}/result`, { + state: { answers } + }); + }; + + return { + questions, + currentQuestion: questions[currentQuestionIndex], + currentQuestionIndex, + setCurrentQuestionIndex, + answers, + isLoading, + error, + handleAnswer, + handleMatchingPairs, + nextQuestion, + prevQuestion, + handleSubmit, + handleConfirmSubmit + }; +}; diff --git a/src/roles/user/exerciseCombine/services/exerciseService.jsx b/src/roles/user/exerciseCombine/services/exerciseService.jsx new file mode 100644 index 0000000..9b4056b --- /dev/null +++ b/src/roles/user/exerciseCombine/services/exerciseService.jsx @@ -0,0 +1,72 @@ +import axios from 'axios'; +import { API_URL } from '../../../../utils/Constant'; + +const config = { + headers: { + Authorization: localStorage.getItem('token') + } +}; + +const getSoalNumber = (title) => { + const match = title.match(/\d+$/); // Mencari angka di akhir string + return match ? parseInt(match[0], 10) : 0; // Mengembalikan angka atau 0 jika tidak ditemukan +}; + +// Fungsi untuk cek apakah std_learning sudah ada +const checkStdLearning = async (level) => { + try { + const response = await axios.get(`${API_URL}/stdLearning/level/${level}`, config); + return response.data; + } catch (error) { + // console.error('Error checking std_learning:', error); + // throw error; + return null; + } +}; + +const getLevelId = async (topicId, levelName) => { + try { + const response = await axios.get(`${API_URL}/level/topic/${topicId}`, config); + const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName); + return filteredData[0].ID_LEVEL; + } catch (error) { + return []; + } +}; + +// Fungsi untuk membuat std_learning +const createStdLearning = async (data) => { + try { + const response = await axios.post(`${API_URL}/stdLearning`, data, config); + return response.data; + } catch (error) { + console.error('Error creating std_learning:', error); + throw error; + } +}; + +// Fungsi untuk mendapatkan data exercise +const fetchExercise = async (idLevel) => { + try { + const response = await axios.get(`${API_URL}/exercise/level/${idLevel}`, config); + // return response.data; + + const sortedData = response.data.payload.sort((a, b) => { + return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE); + }); + + return sortedData; + } catch (error) { + console.error('Error fetching exercise data:', error); + throw error; + } +}; + + + +export default { + getLevelId, + checkStdLearning, + createStdLearning, + fetchExercise +}; diff --git a/src/roles/user/exerciseCombine/views/Exercise.jsx b/src/roles/user/exerciseCombine/views/Exercise.jsx new file mode 100644 index 0000000..4357e48 --- /dev/null +++ b/src/roles/user/exerciseCombine/views/Exercise.jsx @@ -0,0 +1,207 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { useExercise } from '../hooks/useExercises'; +import { Container, Row, Col, ListGroup, Button, Modal, Alert, OverlayTrigger, Popover } from 'react-bootstrap'; +import MultipleChoiceQuestion from './components/MultipleChoiceQuestion'; +import TrueFalseQuestion from './components/TrueFalseQuestion'; +import MatchingPairsQuestion from './components/MatchingPairsQuestion'; +import Skeleton from 'react-loading-skeleton'; + +import ilustration from '../../../../assets/images/illustration/submitExercise.png'; + +const Exercise = () => { + const [showModal, setShowModal] = useState(false); + const [showAlert, setShowAlert] = useState(false); + const { topic, level } = useParams(); + const { + questions, + currentQuestion, + currentQuestionIndex, + setCurrentQuestionIndex, + answers, + isLoading, + error, + handleAnswer, + nextQuestion, + prevQuestion, + handleSubmit, + handleConfirmSubmit + } = useExercise(topic, level); + + const handleSubmitWrapper = () => { + if (handleSubmit()) { + setShowModal(true); + } else { + setShowAlert(true); + } + }; + const handleCloseModal = () => setShowModal(false); + + const renderQuestion = () => { + switch (currentQuestion.QUESTION_TYPE) { + case 'MCQ': + return ( + + ); + case 'TFQ': + return ( + + ); + case 'MPQ': + return ( + + ); + default: + return

Unknown question type.

; + } + }; + + const popover = ( + + Tips + +
    +
  • Click the left arrow key to go to the previous question
  • +
  • Click the right arrow key to go to the next question
  • +
+
+
+ ); + + if (isLoading) { + return ( +
+
+ +
+
+ + + +
+
+ ); + } + + if (error) return

Error loading questions.

; + + return ( + + + +
+
+

Pretest

+ + + +
+ + {questions.map((q, index) => ( + setCurrentQuestionIndex(index)} + className={`border-0 rounded-3 number-label ${answers[index] !== null ? 'answered fw-bold' : ''}`} + style={{ cursor: 'pointer' }} + > + + + {index+1} + + ))} + +
+ + + +
+
+ +
{`Questions ${currentQuestionIndex + 1} of ${questions.length}`}
+ + +
+
+ {renderQuestion()} +
+
+ +
+ + setShowAlert(false)} dismissible> + ATTENTION! + Please answer all questions before submitting. + + + {/* + + Confirmation + + +

Are you sure you want to submit your answer?

+
+ + + + +
*/} + + + +

Proceed with Submission?

+ +

Confirm submission? There's no going back.

+
+ + +
+
+
+
+ ); +}; + +export default Exercise; + + + diff --git a/src/roles/user/exerciseCombine/views/components/ExerciseMedia.jsx b/src/roles/user/exerciseCombine/views/components/ExerciseMedia.jsx new file mode 100644 index 0000000..cdbe29e --- /dev/null +++ b/src/roles/user/exerciseCombine/views/components/ExerciseMedia.jsx @@ -0,0 +1,44 @@ +import React from 'react'; + +const ExerciseMedia = ({ question, onAnswer, savedAnswer, index }) => { + const handleChange = (value) => { + onAnswer(index, value); + }; + + return ( +
+

{question.TITLE}

+

{question.QUESTION}

+ {question.VIDEO && ( + + )} + {question.IMAGE && Question} + {question.AUDIO && } +
+ +
handleChange('1')} + > + A + TRUE +
+ +
handleChange('0')} + > + B + FALSE +
+ +
+
+ ); +}; + +export default ExerciseMedia; diff --git a/src/roles/user/exerciseCombine/views/components/MatchingPairsQuestion.jsx b/src/roles/user/exerciseCombine/views/components/MatchingPairsQuestion.jsx new file mode 100644 index 0000000..84eabd8 --- /dev/null +++ b/src/roles/user/exerciseCombine/views/components/MatchingPairsQuestion.jsx @@ -0,0 +1,227 @@ +import React, { useState, useEffect } from 'react'; + +// const colors = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9']; +// const colors = ['#0090FF', '#FC6454', '#46E59A', '#FBD025', '#E355D5']; +const colors = ['#FC6454', '#FBD025', '#46E59A', '#0090FF','#E355D5']; + +const shuffleArray = (array) => { + return array + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); +}; + +function arrayToString(arr) { + return arr.join(', '); +} + +function stringToArray(str) { + return str.split(', '); +} + +const MatchingPairsQuestion = ({ question, onAnswer, savedAnswer, index }) => { + const [pairs, setPairs] = useState([]); + const [selectedLeft, setSelectedLeft] = useState(null); + const [selectedRight, setSelectedRight] = useState(null); + const [rightOptions, setRightOptions] = useState([]); + const [isComplete, setIsComplete] = useState(false); + const [isShuffled, setIsShuffled] = useState(false); + + useEffect(() => { + const handleClickOutside = (event) => { + if (!event.target.closest('.mp-choice')) { + setSelectedLeft(null); + setSelectedRight(null); + } + }; + + document.addEventListener('click', handleClickOutside); + + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, []); + + useEffect(() => { + const initialPairs = question.matchingPairs.map((pair) => ({ + left: pair.LEFT_PAIR, + right: '', + color: colors[question.matchingPairs.indexOf(pair) % colors.length], + })); + + if (savedAnswer) { + const arrSavedAnswer = stringToArray(savedAnswer); + const updatedPairs = initialPairs.map((pair, index) => ({ + ...pair, + right: arrSavedAnswer[index], + })); + setPairs(updatedPairs); + } else { + setPairs(initialPairs); + } + + if (!isShuffled) { + setIsShuffled(true); + setRightOptions(shuffleArray(question.matchingPairs.map((pair) => pair.RIGHT_PAIR))); + } + }, [question, savedAnswer]); + + const handleLeftClick = (index) => { + setSelectedLeft(index); + const status = pairs.findIndex(item => item.right === rightOptions[selectedRight]); + if (selectedRight !== null) { + makePair(index, selectedRight, status); + } + }; + + const handleRightClick = (index) => { + setSelectedRight(index); + const status = pairs.findIndex(item => item.right === rightOptions[index]); + if (selectedLeft !== null) { + makePair(selectedLeft, index, status); + } + }; + + const makePair = (leftIndex, rightIndex, changePair) => { + const newPairs = [...pairs]; + if (changePair > -1) { + setIsComplete(false); + pairs[changePair].right = ''; + } + + const selectedRightValue = rightOptions[rightIndex]; + newPairs[leftIndex].right = selectedRightValue; + setPairs(newPairs); + // console.log(newPairs); + + setSelectedLeft(null); + setSelectedRight(null); + + const allPairsMatched = newPairs.every((pair) => pair.right !== ''); + if (allPairsMatched && !isComplete) { + setIsComplete(true); + + const rightAnswers = newPairs.map((pair) => pair.right); + console.log(rightAnswers); + onAnswer(index, arrayToString(rightAnswers)); + } + }; + + return ( +
+ {question.IMAGE !== null && ( +
+ {/* */} +

{question.IMAGE}

+
+ )} + {question.AUDIO !== null && ( +
+ {/* */} +

{question.AUDIO}

+
+ )} + {question.VIDEO !== null && ( + // +
+

{question.VIDEO}

+
+ )} + +

{question.QUESTION}

+
+ {/* Bagian kiri */} +
+ {pairs.map((pair, index) => ( +
handleLeftClick(index)} + style={{ + display: 'flex', alignItems: 'center', cursor: 'pointer', + }} + > + + {index + 1} + + + {pair.left} + +
+ ))} +
+ + {/* Bagian kanan */} +
+ {rightOptions.map((right, index) => ( +
handleRightClick(index)} + style={{ + display: 'flex', alignItems: 'center', cursor: 'pointer', + }} + > + pair.right === right) ? '#ffffff' : '#000000', + backgroundColor: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '#ccc' : '#ffffff'), + border: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '2px dashed #ffffff' : '1px solid #000000'), + }} + > + {index + 1} + + pair.right === right) ? '#ffffff' : '#000000'), + backgroundColor: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '#ccc' : '#ffffff'), + border: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '2px dashed #ffffff' : '1px solid #000000'), + }} + > + {right} + +
+ ))} +
+
+
+ ); +}; + +export default MatchingPairsQuestion; diff --git a/src/roles/user/exerciseCombine/views/components/MultipleChoiceQuestion.jsx b/src/roles/user/exerciseCombine/views/components/MultipleChoiceQuestion.jsx new file mode 100644 index 0000000..c3dc15d --- /dev/null +++ b/src/roles/user/exerciseCombine/views/components/MultipleChoiceQuestion.jsx @@ -0,0 +1,64 @@ +import React from 'react'; + +const MultipleChoiceQuestion = ({ question, onAnswer, savedAnswer, index }) => { + const options = question.multipleChoices[0]; + + const handleChange = (value) => { + onAnswer(index, value); + }; + + function getOptionLetter(option) { + const match = option.match(/OPTION_(\w)/); + return match ? match[1] : null; + } + + return ( +
+ {question.IMAGE !== null && ( +
+ {/* */} +

{question.IMAGE}

+
+ )} + {question.AUDIO !== null && ( +
+ {/* */} +

{question.AUDIO}

+
+ )} + {question.VIDEO !== null && ( + // +
+

{question.VIDEO}

+
+ )} + +

{question.QUESTION}

+
+ {['OPTION_A', 'OPTION_B', 'OPTION_C', 'OPTION_D', 'OPTION_E'].map( + (optionKey) => { + if (options[optionKey]) { + return ( +
handleChange(getOptionLetter(optionKey))} + > + {getOptionLetter(optionKey)} + {options[optionKey]} +
+ ); + } + return null; + } + )} +
+
+ ); +}; + +export default MultipleChoiceQuestion; diff --git a/src/roles/user/exerciseCombine/views/components/TrueFalseQuestion.jsx b/src/roles/user/exerciseCombine/views/components/TrueFalseQuestion.jsx new file mode 100644 index 0000000..3c957bf --- /dev/null +++ b/src/roles/user/exerciseCombine/views/components/TrueFalseQuestion.jsx @@ -0,0 +1,58 @@ +import React from 'react'; + +const TrueFalseQuestion = ({ question, onAnswer, savedAnswer, index }) => { + const handleChange = (value) => { + onAnswer(index, value); + }; + + return ( +
+ {question.IMAGE !== null && ( +
+ {/* */} +

{question.IMAGE}

+
+ )} + {question.AUDIO !== null && ( +
+ {/* */} +

{question.AUDIO}

+
+ )} + {question.VIDEO !== null && ( + // +
+

{question.VIDEO}

+
+ )} + +

{question.QUESTION}

+
+ +
handleChange('1')} + > + A + TRUE +
+ +
handleChange('0')} + > + B + FALSE +
+ +
+
+ ); +}; + +export default TrueFalseQuestion; diff --git a/src/roles/user/exerciseOld/hooks/useExercises.jsx b/src/roles/user/exerciseOld/hooks/useExercises.jsx new file mode 100644 index 0000000..f0ec3ae --- /dev/null +++ b/src/roles/user/exerciseOld/hooks/useExercises.jsx @@ -0,0 +1,248 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import exerciseService from '../services/exerciseService'; +import { useSlugContext } from '../../../../utils/SlugContext'; +import { unSlugify } from '../../../../utils/Constant'; + +import Popover from 'react-bootstrap/Popover'; + +import image from '../../../../assets/images/illustration/material-image.png'; +import audio from '../../../../assets/audio/sample.mp3'; +import video from '../../../../assets/video/sample.mp4'; + +const basicQuestions = [ + { + id: 1, + question: "Which sentence is correct?", + options: ["She don't like apples.", "She doesn't like apples.", "She not likes apples.", "She don't likes apples."], + source: null, + correctAnswer: 1, + type: 'text' + }, + { + id: 2, + question: "Choose the correct form of the verb: 'He ___ to the store yesterday.'", + options: ["go", "went", "going", "goes"], + source: image, + correctAnswer: 1, + type: 'image' + }, + { + id: 3, + question: "Select the correct sentence:", + options: ["They was playing football.", "They were playing football.", "They playing football.", "They played football were."], + source: audio, + correctAnswer: 1, + type: 'audio' + }, + { + id: 4, + question: "Fill in the blank: 'I have ___ new car.'", + options: ["a", "an", "the", "some"], + source: video, + correctAnswer: 1, + type: 'video' + }, + { + id: 5, + question: "Which sentence is in the past perfect tense?", + options: ["I will have finished my homework.", "I had finished my homework.", "I finished my homework.", "I am finishing my homework."], + source: null, + correctAnswer: 1, + type: 'text' + }, + { + id: 6, + question: "Choose the correct pronoun: 'Neither of the boys lost ___ keys.'", + options: ["his", "their", "her", "its"], + source: null, + correctAnswer: 1, + type: 'text' + }, + { + id: 7, + question: "Identify the error in the sentence: 'She can sings very well.'", + options: ["sings", "can", "very", "well"], + source: null, + correctAnswer: 0, + type: 'text' + }, + { + id: 8, + question: "Select the correct sentence:", + options: ["There is many apples on the table.", "There are many apples on the table.", "There are much apples on the table.", "There is much apples on the table."], + source: null, + correctAnswer: 1, + type: 'text' + }, + { + id: 9, + question: "Choose the correct conditional form: 'If it rains, we ___ inside.'", + options: ["stay", "stays", "will stay", "stayed"], + source: null, + correctAnswer: 2, + type: 'text' + }, + { + id: 10, + question: "Fill in the blank: 'She ___ to the gym every day.'", + options: ["when", "going", "gone", "goes"], + source: null, + correctAnswer: 3, + type: 'text' + } +]; + +export const useExercises = ( topic, level ) => { + const [questions, setQuestions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [answers, setAnswers] = useState([]); + const [currentQuestion, setCurrentQuestion] = useState(0); + const { topicSlugMap } = useSlugContext(); + const navigate = useNavigate(); + + useEffect(() => { + const checkingData = async () => { + let levelId =''; + try { + levelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level)); + } catch (error) { + console.error("Failed to fetch questions:", error); + }finally{ + const datas = await exerciseService.latestStdLearning(topicSlugMap[topic], levelId); + if (datas === null) { + const createLearning = await exerciseService.createStdLearning({ ID_LEVEL: levelId }); + if (createLearning.data.statusCode === 201) { + fetchData(); + }else{ + checkingData(); + } + }else{ + fetchData(); + } + } + }; + + const fetchData = async () => { + try { + const levelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level)); + const stdLearning = await exerciseService.latestStdLearning(topicSlugMap[topic], levelId); + if (stdLearning === null) { + await exerciseService.createStdLearning({ ID_LEVEL: levelId }); + } + + const datas = await exerciseService.fetchExercise(levelId); + console.log(datas); + + // const data = allQuestions[topic]?.[level] || []; + const data = basicQuestions || []; + // setQuestions(data); + + setQuestions(datas); + setAnswers(new Array(data.length).fill(null)); + } catch (error) { + console.error("something wrong : ", error); + setError(error); + }finally{ + setLoading(false); + } + }; + + // checkingData(); + fetchData(); + }, [topic, level]); + + useEffect(() => { + const savedAnswers = JSON.parse(localStorage.getItem('answers')); + if (savedAnswers) { + setAnswers(savedAnswers); + } + }, []); + + useEffect(() => { + if (answers.some(answer => answer !== null)) { + localStorage.setItem('answers', JSON.stringify(answers)); + console.log(localStorage.getItem('answers')); + } + }, [answers]); + + const popover = ( + + Tips + + {/* Click the left arrow key to go to the previous question */} + {/* Click the right arrow key to go to the next question */} +
    +
  • Click the left arrow key to go to the previous question
  • +
  • Click the right arrow key to go to the next question
  • +
+
+
+ ); + + const handleAnswerSelect = (index) => { + const newAnswers = [...answers]; + newAnswers[currentQuestion] = index; + setAnswers(newAnswers); + }; + + const handleNextQuestion = () => { + setCurrentQuestion((prev) => (prev < questions.length - 1 ? prev + 1 : prev)); + }; + + const handlePrevQuestion = () => { + setCurrentQuestion((prev) => (prev > 0 ? prev - 1 : prev)); + }; + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === 'ArrowLeft') { + handlePrevQuestion(); + } else if (event.key === 'ArrowRight') { + handleNextQuestion(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [handlePrevQuestion, handleNextQuestion]); + + const handleSubmit = () => { + const allAnswered = answers.every((answer) => answer !== null); + return allAnswered; + }; + + const getCurrentQuestionData = () => { + return questions[currentQuestion] || {}; + }; + + const handleConfirmSubmit = () => { + setShowModal(false); + navigate(`/learning/module/${section}/${topic}/${level}/result`, { + state: { answers } + }); + }; + + return { + questions, + loading, + error, + answers, + currentQuestion, + popover, + setAnswers, + setCurrentQuestion, + handleAnswerSelect, + handleNextQuestion, + handlePrevQuestion, + handleSubmit, + getCurrentQuestionData, + handleConfirmSubmit + }; +}; + +export default useExercises; diff --git a/src/roles/user/exerciseOld/hooks/useMatchingPairs.jsx b/src/roles/user/exerciseOld/hooks/useMatchingPairs.jsx new file mode 100644 index 0000000..05b155a --- /dev/null +++ b/src/roles/user/exerciseOld/hooks/useMatchingPairs.jsx @@ -0,0 +1,88 @@ +import React, { useState, useEffect } from 'react'; + +const useMatchingPairs = () => { + const COLORS = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9']; + const [rightShuffled, setRightShuffled] = useState([]); + const [selectedLeft, setSelectedLeft] = useState(null); + const [selectedRight, setSelectedRight] = useState(null); + const [matchedPairs, setMatchedPairs] = useState([]); + const [colorMap, setColorMap] = useState({}); + const leftItems = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']; + const rightItems = ['Item A', 'Item B', 'Item C', 'Item D', 'Item E']; + const [shuffle, setShuffle] = useState(true); + + // Mengacak bagian kanan ketika komponen pertama kali dirender + useEffect(() => { + if (shuffle) { + shuffleRightItems(); + } + }, [rightItems]); + + // Fungsi untuk mengacak urutan bagian kanan + const shuffleRightItems = () => { + const shuffled = [...rightItems].sort(() => Math.random() - (leftItems.length/10)); + setRightShuffled(shuffled); + setShuffle(false); + }; + + // Fungsi untuk menangani klik pada bagian kiri + const handleLeftChoose = (index) => { + setSelectedLeft(index); + + if (selectedRight !== null) { + matchPair(index, selectedRight); + resetSelections(); + } + }; + + // Fungsi untuk menangani klik pada bagian kanan + const handleRightChoose = (index) => { + setSelectedRight(index); + + if (selectedLeft !== null) { + matchPair(selectedLeft, index); + resetSelections(); + } + console.log(matchedPairs); + }; + + // Fungsi untuk mencocokkan pasangan + const matchPair = (leftIndex, rightIndex) => { + if (!matchedPairs[leftIndex]) { + const newMatchedPairs = [...matchedPairs]; + newMatchedPairs[leftIndex] = rightShuffled[rightIndex]; + + const colorIndex = leftIndex % COLORS.length; + setColorMap((prevMap) => ({ + ...prevMap, + [leftIndex]: COLORS[colorIndex], + })); + + setMatchedPairs(newMatchedPairs); + } + }; + + // Fungsi untuk mereset seleksi setelah pencocokan + const resetSelections = () => { + setSelectedLeft(null); + setSelectedRight(null); + }; + + const resetMatchPair = () => { + setMatchedPairs([]); + setColorMap({}); + resetSelections(); + }; + + return ( + rightShuffled, + colorMap, + handleLeftChoose, + handleRightChoose, + matchPair, + resetSelections, + resetMatchPair + ); +}; + +export default useMatchingPairs; diff --git a/src/roles/user/exerciseOld/hooks/useResults.jsx b/src/roles/user/exerciseOld/hooks/useResults.jsx new file mode 100644 index 0000000..73043c2 --- /dev/null +++ b/src/roles/user/exerciseOld/hooks/useResults.jsx @@ -0,0 +1,93 @@ +import { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import useExercises from './useExercises'; + +// correct answer +// 1. b +// 2. b +// 3. b +// 4. b +// 5. b +// 6. b +// 7. a +// 8. b +// 9. c +//10. d + +const useResults = (answers) => { + const { level } = useParams(); + const [score, setScore] = useState(0); + const [nextLevel, setNextLevel] = useState(1); + const [savedLevel, setSavedLevel] = useState(JSON.parse(localStorage.getItem('savedLevels')) || []); + const [savedScore, setSavedScore] = useState(JSON.parse(localStorage.getItem('savedScores')) || []); + + const { questions } = useExercises(); + + const updateSavedScore = (scr) => { + const newSavedScore = [...savedScore]; + const index = parseInt(level.slice(-1) - 1); + newSavedScore[index] = scr; + setSavedScore(newSavedScore); + localStorage.setItem('savedScores', JSON.stringify(newSavedScore)); + }; + + const updateSavedLevel = (index, newValue) => { + const newSavedLevel = [...savedLevel]; + newSavedLevel[index] = newValue; + setSavedLevel(newSavedLevel); + localStorage.setItem('savedLevels', JSON.stringify(newSavedLevel)); + }; + + const unlockSavedLevel = () => { + const newSavedLevel = savedLevel.map(() => false); + setSavedLevel(newSavedLevel); + localStorage.setItem('savedLevels', JSON.stringify(newSavedLevel)); + }; + + useEffect(() => { + if (answers && questions) { + let totalScore = 0; + + answers.forEach((answer, index) => { + const question = questions[index]; + if (question) { + if (answer === question.correctAnswer) { + totalScore += 100 / questions.length; + } + } + }); + + setScore(totalScore); + updateSavedScore(totalScore); + if (totalScore >= 90 && level === 'level-5') { //all done + setNextLevel(0); + unlockSavedLevel(); + return; + } else if (totalScore >= 90 && level !== 'level-5') { + setNextLevel(5); + updateSavedLevel(4, false); + return; + } else if (totalScore >= 75) { + setNextLevel(4); + updateSavedLevel(3, false); + return; + } else if (totalScore >= 60) { + setNextLevel(3); + updateSavedLevel(2, false); + return; + } else if (totalScore >= 40) { + setNextLevel(2); + updateSavedLevel(1, false); + return; + } else { + setNextLevel(1); + } + + } + }, [answers, questions]); + + localStorage.removeItem('answers'); + return { score, nextLevel }; +}; + +export default useResults; diff --git a/src/roles/user/exerciseOld/services/exerciseService.jsx b/src/roles/user/exerciseOld/services/exerciseService.jsx new file mode 100644 index 0000000..5af9062 --- /dev/null +++ b/src/roles/user/exerciseOld/services/exerciseService.jsx @@ -0,0 +1,87 @@ +import axios from 'axios'; +import { API_URL } from '../../../../utils/Constant'; + +const config = { + headers: { + Authorization: localStorage.getItem('token') + } +}; + +const getSoalNumber = (title) => { + const match = title.match(/\d+$/); // Mencari angka di akhir string + return match ? parseInt(match[0], 10) : 0; // Mengembalikan angka atau 0 jika tidak ditemukan +}; + +// Fungsi untuk cek apakah std_learning sudah ada +const latestStdLearning = async (topic, level) => { + try { + const response = await axios.get(`${API_URL}/stdLearning/level/${level}`, config); + return response.data; + } catch (error) { + // console.error('Error checking std_learning:', error); + // throw error; + return null; + } +}; + +const getLevelId = async (topicId, levelName) => { + try { + const response = await axios.get(`${API_URL}/level/topic/${topicId}`, config); + const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName); + return filteredData[0].ID_LEVEL; + } catch (error) { + return []; + } +}; + + + +// Fungsi untuk cek apakah std_learning sudah ada +const checkStdLearning = async (token) => { + try { + const response = await axios.get(`${API_URL}/std_learning`, config); + return response.data; + } catch (error) { + console.error('Error checking std_learning:', error); + throw error; + } +}; + +// Fungsi untuk membuat std_learning +const createStdLearning = async (data) => { + try { + const response = await axios.post(`${API_URL}/stdLearning`, data, config); + return response.data; + } catch (error) { + console.error('Error creating std_learning:', error); + throw error; + } +}; + +// Fungsi untuk mendapatkan data exercise +const fetchExercise = async (idLevel) => { + try { + const response = await axios.get(`${API_URL}/exercise/level/${idLevel}`, config); + // return response.data; + + const sortedData = response.data.payload.sort((a, b) => { + return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE); + }); + + return sortedData; + } catch (error) { + console.error('Error fetching exercise data:', error); + throw error; + } +}; + + + +export default { + latestStdLearning, + getLevelId, + + checkStdLearning, + createStdLearning, + fetchExercise +}; diff --git a/src/roles/user/exerciseOld/views/Exercise.jsx b/src/roles/user/exerciseOld/views/Exercise.jsx new file mode 100644 index 0000000..7ed9e8d --- /dev/null +++ b/src/roles/user/exerciseOld/views/Exercise.jsx @@ -0,0 +1,331 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Row, Col, ListGroup, Button, Modal, Alert, OverlayTrigger, Popover } from 'react-bootstrap'; +import { useNavigate, useParams } from 'react-router-dom'; +import useExercises from '../hooks/useExercises'; +import ilustration from '../../../../assets/images/illustration/submitExercise.png'; +import Skeleton from 'react-loading-skeleton'; +import MatchingPairs from './components/MatchingPair'; + +const Exercise = () => { + const [showModal, setShowModal] = useState(false); + const [showAlert, setShowAlert] = useState(false); + + const [activeIndex, setActiveIndex] = useState(null); + + const { section, topic, level } = useParams(); + const navigate = useNavigate(); + + const { + questions, + loading, + error, + answers, + currentQuestion, + popover, + setAnswers, + setCurrentQuestion, + handleAnswerSelect, + handleNextQuestion, + handlePrevQuestion, + handleSubmit, + getCurrentQuestionData, + handleConfirmSubmit + } = useExercises( topic, level ); + + const handleSubmitWrapper = () => { + if (handleSubmit()) { + setShowModal(true); + } else { + setShowAlert(true); + } + }; + + // const handleConfirmSubmit = () => { + // setShowModal(false); + // navigate(`/learning/module/${section}/${topic}/${level}/result`, { + // state: { answers } + // }); + // }; + + const handleCloseModal = () => setShowModal(false); + + const currentQuestionData = getCurrentQuestionData(); + const questionId = currentQuestionData.id || ''; + + + const handleSelectLeft = (index) => { + setActiveIndex(index); + }; + + + + if (loading) { + return ( +
+
+ +
+
+ + + +
+
+ ); + } + + return ( + + + +
+
+

Pretest

+ + + +
+ + {questions.map((q, index) => ( + setCurrentQuestion(index)} + className={`border-0 rounded-3 number-label ${answers[index] !== null ? 'answered fw-bold' : ''}`} + style={{ cursor: 'pointer' }} + > + + + {index+1} + + ))} + +
+ + + +
+
+ +
{`Questions ${currentQuestion + 1} of ${questions.length}`}
+ + +
+
+ {currentQuestionData.image && ( +
+ question illustration +
+ )} + {currentQuestionData.IMAGE !== null && ( +
+ {/* */} +

{currentQuestionData.IMAGE}

+
+ )} + {currentQuestionData.AUDIO !== null && ( +
+ {/* */} + {/* */} +

{currentQuestionData.AUDIO}

+
+ )} + {currentQuestionData.VIDEO !== null && ( + // +
+

{currentQuestionData.VIDEO}

+
+ )} +

{currentQuestionData.QUESTION}

+ {currentQuestionData.QUESTION_TYPE === "MCQ" && ( +
+ {/* {currentQuestionData.options?.map((option, idx) => ( +
handleAnswerSelect(idx)} + > + {String.fromCharCode(65 + idx)} + {option} +
+ ))} */} +
handleAnswerSelect(0)} + > + A + {currentQuestionData.multipleChoices[0].OPTION_A} +
+
handleAnswerSelect(1)} + > + B + {currentQuestionData.multipleChoices[0].OPTION_B} +
+
handleAnswerSelect(2)} + > + C + {currentQuestionData.multipleChoices[0].OPTION_C} +
+
handleAnswerSelect(3)} + > + D + {currentQuestionData.multipleChoices[0].OPTION_D} +
+ {currentQuestionData.multipleChoices[0].OPTION_E && ( +
handleAnswerSelect(4)} + > + E + {currentQuestionData.multipleChoices[0].OPTION_E} +
+ )} +
+ )} + {currentQuestionData.QUESTION_TYPE === "TFQ" && ( +
+
handleAnswerSelect(0)} + > + A + TRUE +
+
handleAnswerSelect(1)} + > + B + FALSE +
+
+ )} + {currentQuestionData.QUESTION_TYPE === "MPQ" &&( + //
+ //
+ // {currentQuestionData.matchingPairs?.map((option, idx) => ( + //
handleLeftChoose(idx)} + // > + // {idx + 1} + // {/* {idx + 1} */} + // {/* {option.LEFT_PAIR} */} + // {option.LEFT_PAIR} + //
+ // ))} + //
+ //
+ // {currentQuestionData.matchingPairs?.map((option, idx) => ( + //
handleRightChoose(idx)} + // > + // {idx + 1} + // {/* {idx + 1} */} + // {/* {option.LEFT_PAIR} */} + // {option.RIGHT_PAIR} + //
+ // ))} + //
+ //
+ + )} +
+
+ +
+ + setShowAlert(false)} dismissible> + ATTENTION! + Please answer all questions before submitting. + + + {/* + + Confirmation + + +

Are you sure you want to submit your answer?

+
+ + + + +
*/} + + + +

Proceed with Submission?

+ +

Confirm submission? There's no going back.

+
+ + +
+
+
+
+ ); +}; + +export default Exercise; + + + diff --git a/src/roles/user/exerciseOld/views/ExerciseResult.jsx b/src/roles/user/exerciseOld/views/ExerciseResult.jsx new file mode 100644 index 0000000..98361da --- /dev/null +++ b/src/roles/user/exerciseOld/views/ExerciseResult.jsx @@ -0,0 +1,134 @@ +// import React from 'react'; +// import { Link, useParams, useLocation } from 'react-router-dom'; +// import { Container, Row, Col, Button } from 'react-bootstrap'; +// import staydown from '../../assets/images/user/resultStay.png' +// import jump from '../../assets/images/user/resultJump.png' +// import finish from '../../assets/images/user/resultFinish.png' + +// const ExerciseResult = () => { +// const { section, topic } = useParams(); +// const location = useLocation(); +// localStorage.removeItem('answers'); + +// return ( +// +// + +// {/* nilai sudah diatas KKM */} +// +//

Your Result!

+// / +//

+// You scored 60/100. Great job! You can jump to... +//

+//

LEVEL 3

+// +// + +// {/* jika nilai dibawah KKM */} +// +//

Your Result!

+// / +//

+// You scored 30/100. Good effort! To improve, you can focus on... +//

+//

LEVEL 3

+// +// + +// {/* jika nilai sudah bagus tetapi belum cukup untuk ke level selanjutnya */} +// +//

Your Result!

+// / +//

+// You scored 30/100. Learning is a journey.
+// Let's explore this level further to deepen your knowledge. +//

+//

LEVEL 2

+// +// + +// {/* jika yang telah dikerjakan adalah level 5 */} +// +//

Your Result!

+// / +//

+// Way to go! You conquered LEVEL 5 with a 90/100! You're a rock star! +//

+//

LEVEL 2

+// +// +//
+//
+// ); +// }; + +// export default ExerciseResult; + + + + +import React from 'react'; +import { Link, useParams, useLocation } from 'react-router-dom'; +import { Container, Row, Col, Button } from 'react-bootstrap'; +import staydown from '../../../../assets/images/illustration/resultStay.png'; +import jump from '../../../../assets/images/illustration/resultJump.png'; +import finish from '../../../../assets/images/illustration/resultFinish.png'; +import useResults from '../hooks/useResults'; + +const ExerciseResult = () => { + const { section, topic, level } = useParams(); + const location = useLocation(); + const { answers } = location.state || { answers: [] }; + + const { score, nextLevel } = useResults(answers); + + let resultImage; + let resultText; + let resultColor; + + if (nextLevel === 0) { + resultImage = finish; + resultColor = 'text-blue'; + resultText = ( + <> Way to go! You conquered LEVEL 5 with a {score}/100! You're a rock star! + ); + } else if (score >= 60) { + resultImage = jump; + resultColor = 'text-blue'; + resultText = ( + <> You scored {score}/100. Great job! You can jump to... + ); + } else if (score >= 40) { + resultImage = staydown; + resultColor = ''; + resultText = ( + <> You scored {score}/100. Learning is a journey.
Let's explore this level further to deepen your knowledge. + ); + } else { + resultImage = staydown; + resultColor = 'text-red'; + resultText = ( + <> You scored {score}/100. Good effort! To improve, you can focus on... + ); + } + + return ( + + + +

Your Result!

+ / +

+ {resultText} +

+

{nextLevel === 0 ? '' : `LEVEL ${nextLevel}`}

+ + +
+
+ ); +}; + +export default ExerciseResult; + diff --git a/src/roles/user/exerciseOld/views/components/MatchingPair.jsx b/src/roles/user/exerciseOld/views/components/MatchingPair.jsx new file mode 100644 index 0000000..29bb458 --- /dev/null +++ b/src/roles/user/exerciseOld/views/components/MatchingPair.jsx @@ -0,0 +1,139 @@ +import React, { useState, useEffect } from 'react'; + +const COLORS = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9']; + +const MatchingPairs = ( stateData ) => { + const [selectedLeft, setSelectedLeft] = useState(null); + const [selectedRight, setSelectedRight] = useState(null); + const [matchedPairs, setMatchedPairs] = useState([]); + const [colorMap, setColorMap] = useState({}); + const [leftItems, setLeftPairs] = useState([]); + const [rightItems, setRightPairs] = useState([]); + + + useEffect(() => { + console.log(stateData); + const pairItem = stateData.pairData; + const left = pairItem.map(pair => pair.LEFT_PAIR); + const right = pairItem.map(pair => pair.RIGHT_PAIR); + const shuffledRight = [...right].sort(() => Math.random() - (left.length/10)); + + setLeftPairs(left); + setRightPairs(shuffledRight); + }, []); + + + // Fungsi untuk menangani klik pada bagian kiri + const handleLeftClick = (index) => { + setSelectedLeft(index); + + if (selectedRight !== null) { + matchPair(index, selectedRight); + resetSelections(); + } + }; + + // Fungsi untuk menangani klik pada bagian kanan + const handleRightClick = (index) => { + setSelectedRight(index); + + if (selectedLeft !== null) { + matchPair(selectedLeft, index); + resetSelections(); + } + console.log(selectedRight); + }; + + // Fungsi untuk mencocokkan pasangan + const matchPair = (leftIndex, rightIndex) => { + if (!matchedPairs[leftIndex]) { + const newMatchedPairs = [...matchedPairs]; + newMatchedPairs[leftIndex] = rightItems[rightIndex]; + + const colorIndex = leftIndex % COLORS.length; + setColorMap((prevMap) => ({ + ...prevMap, + [leftIndex]: COLORS[colorIndex], + })); + + setMatchedPairs(newMatchedPairs); + } + }; + + // Fungsi untuk mereset seleksi setelah pencocokan + const resetSelections = () => { + setSelectedLeft(null); + setSelectedRight(null); + }; + + const resetAnswers = () => { + setMatchedPairs([]); + setColorMap({}); + resetSelections(); + }; + + const isAllMatched = () => { + console.log('gatau'); + return matchedPairs.length === leftItems.length && matchedPairs.every((pair) => pair !== undefined && pair !== null); + }; + + return ( +
+
+ {/* Bagian Kiri */} +
+

Left Items

+
    + {leftItems.map((item, index) => ( +
  • handleLeftClick(index)} + style={{ + cursor: 'pointer', + backgroundColor: colorMap[index] || 'transparent', + padding: '10px', + marginBottom: '5px', + border: selectedLeft === index ? '2px solid black' : '2px solid #fff', + }} + > + {item} +
  • + ))} +
+
+ + {/* Bagian Kanan */} +
+

Right Items (Shuffled)

+
    + {rightItems.map((item, index) => ( +
  • handleRightClick(index)} + style={{ + cursor: 'pointer', + backgroundColor: matchedPairs.includes(item) ? colorMap[matchedPairs.indexOf(item)] : 'transparent', + padding: '10px', + marginBottom: '5px', + border: selectedRight === index ? '2px solid black' : '2px solid #fff', + }} + > + {item} +
  • + ))} +
+
+
+ {/*
+ {isAllMatched() ? ( +
Semua pasangan sudah terjawab!
+ ) : ( +
Belum semua pasangan terjawab.
+ )} +
*/} + +
+ ); +}; + +export default MatchingPairs; diff --git a/src/roles/user/exerciseTest/hooks/useExercise.jsx b/src/roles/user/exerciseTest/hooks/useExercise.jsx new file mode 100644 index 0000000..ddf110f --- /dev/null +++ b/src/roles/user/exerciseTest/hooks/useExercise.jsx @@ -0,0 +1,95 @@ +import { useState, useEffect } from 'react'; +import exerciseService from '../services/exerciseServices'; +import { useSlugContext } from '../../../../utils/SlugContext'; +import { unSlugify } from '../../../../utils/Constant'; + +export const useExercise = (topic, level) => { + const [questions, setQuestions] = useState([]); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); + const [answers, setAnswers] = useState({}); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [levelId, setLevelId] = useState(null); + const [learningId, setLearningId] = useState(null); + const { topicSlugMap } = useSlugContext(); + + const fetchData = async () => { + try { + const getLevelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level)); + setLevelId(getLevelId); + const stdLearning = await exerciseService.checkStdLearning(getLevelId); + if (stdLearning === null) { + const newLearningId = await exerciseService.createStdLearning({ ID_LEVEL: getLevelId }); + setLearningId(newLearningId); + }else{ + setLearningId(stdLearning); + } + + + const data = await exerciseService.fetchExercise(getLevelId); + setQuestions(data); + if (localStorage.getItem(getLevelId)) { + const savedAnswers = JSON.parse(localStorage.getItem(getLevelId)); + setAnswers(savedAnswers); + }else{ + setAnswers(new Array(data.length).fill(null)); + } + } catch (error) { + console.error("something wrong : ", error); + setError(error); + }finally{ + setIsLoading(false); + } + }; + + // Fetch data soal dari API saat komponen dimount + useEffect(() => { + fetchData(); + }, [topic, level]); + + // Fungsi untuk menangani jawaban user dan menyimpannya ke localStorage + const handleAnswer = (index, answer) => { + // const newAnswers = { ...answers, [index]: answer }; + const newAnswers = [...answers]; + newAnswers[index] = answer; + setAnswers(newAnswers); + console.log(newAnswers); + localStorage.setItem(levelId, JSON.stringify(newAnswers)); + }; + + // Fungsi untuk menyimpan pasangan jawaban Matching Pair ketika sudah lengkap + const handleMatchingPairs = (questionId, pairs) => { + const isComplete = pairs.every(pair => pair.left && pair.right); // Pastikan semua pasangan sudah lengkap + if (isComplete) { + handleAnswer(questionId, pairs); // Simpan jawaban jika sudah lengkap + } + }; + + // Fungsi untuk pindah ke soal selanjutnya + const nextQuestion = () => { + if (currentQuestionIndex < questions.length - 1) { + setCurrentQuestionIndex(currentQuestionIndex + 1); + } + }; + + // Fungsi untuk kembali ke soal sebelumnya + const prevQuestion = () => { + if (currentQuestionIndex > 0) { + setCurrentQuestionIndex(currentQuestionIndex - 1); + } + }; + + return { + questions, + currentQuestion: questions[currentQuestionIndex], + currentQuestionIndex, + setCurrentQuestionIndex, + answers, + isLoading, + error, + handleAnswer, + handleMatchingPairs, + nextQuestion, + prevQuestion, + }; +}; diff --git a/src/roles/user/exerciseTest/services/exerciseServices.jsx b/src/roles/user/exerciseTest/services/exerciseServices.jsx new file mode 100644 index 0000000..9b4056b --- /dev/null +++ b/src/roles/user/exerciseTest/services/exerciseServices.jsx @@ -0,0 +1,72 @@ +import axios from 'axios'; +import { API_URL } from '../../../../utils/Constant'; + +const config = { + headers: { + Authorization: localStorage.getItem('token') + } +}; + +const getSoalNumber = (title) => { + const match = title.match(/\d+$/); // Mencari angka di akhir string + return match ? parseInt(match[0], 10) : 0; // Mengembalikan angka atau 0 jika tidak ditemukan +}; + +// Fungsi untuk cek apakah std_learning sudah ada +const checkStdLearning = async (level) => { + try { + const response = await axios.get(`${API_URL}/stdLearning/level/${level}`, config); + return response.data; + } catch (error) { + // console.error('Error checking std_learning:', error); + // throw error; + return null; + } +}; + +const getLevelId = async (topicId, levelName) => { + try { + const response = await axios.get(`${API_URL}/level/topic/${topicId}`, config); + const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName); + return filteredData[0].ID_LEVEL; + } catch (error) { + return []; + } +}; + +// Fungsi untuk membuat std_learning +const createStdLearning = async (data) => { + try { + const response = await axios.post(`${API_URL}/stdLearning`, data, config); + return response.data; + } catch (error) { + console.error('Error creating std_learning:', error); + throw error; + } +}; + +// Fungsi untuk mendapatkan data exercise +const fetchExercise = async (idLevel) => { + try { + const response = await axios.get(`${API_URL}/exercise/level/${idLevel}`, config); + // return response.data; + + const sortedData = response.data.payload.sort((a, b) => { + return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE); + }); + + return sortedData; + } catch (error) { + console.error('Error fetching exercise data:', error); + throw error; + } +}; + + + +export default { + getLevelId, + checkStdLearning, + createStdLearning, + fetchExercise +}; diff --git a/src/roles/user/exerciseTest/views/Exercise.jsx b/src/roles/user/exerciseTest/views/Exercise.jsx new file mode 100644 index 0000000..216bf56 --- /dev/null +++ b/src/roles/user/exerciseTest/views/Exercise.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { useExercise } from '../hooks/useExercise'; +import MultipleChoiceQuestion from './MultipleChoiceQuestion'; +import TrueFalseQuestion from './TrueFalseQuestion'; +import MatchingPairsQuestion from './MatchingPairsQuestion'; +import { useParams } from 'react-router-dom'; + +const Exercise = () => { + const { topic, level } = useParams(); + const { + questions, + currentQuestion, + currentQuestionIndex, + setCurrentQuestionIndex, + answers, + isLoading, + error, + handleAnswer, + nextQuestion, + prevQuestion, + } = useExercise(topic, level); + + if (isLoading) return

Loading...

; + if (error) return

Error loading questions.

; + + const renderQuestion = () => { + switch (currentQuestion.QUESTION_TYPE) { + case 'MCQ': + return ( + + ); + case 'TFQ': + return ( + + ); + case 'MPQ': + return ( + + ); + default: + return

Unknown question type.

; + } + }; + + return ( +
+

Exercise

+
+ {/* List nomor soal */} +
+ {questions.map((q, index) => ( + + ))} +
+ {/* Soal saat ini */} +
{renderQuestion()}
+ {/* Navigasi Next dan Previous */} +
+ + +
+
+
+ ); +}; + +export default Exercise; diff --git a/src/roles/user/exerciseTest/views/MatchingPairsQuestion.jsx b/src/roles/user/exerciseTest/views/MatchingPairsQuestion.jsx new file mode 100644 index 0000000..13069bf --- /dev/null +++ b/src/roles/user/exerciseTest/views/MatchingPairsQuestion.jsx @@ -0,0 +1,222 @@ +// import React, { useState, useEffect } from 'react'; + +// const MatchingPairsQuestion = ({ question, onAnswer, savedAnswer, index }) => { +// const [pairs, setPairs] = useState([]); + +// useEffect(() => { +// const initialPairs = question.matchingPairs.map((pair) => ({ +// left: pair.LEFT_PAIR, +// right: '', +// })); +// if (savedAnswer) { +// setPairs(savedAnswer); +// } else { +// setPairs(initialPairs); +// } +// }, [question, savedAnswer]); + +// const rightOptions = question.matchingPairs.map((pair) => pair.RIGHT_PAIR); + +// const handleSelect = (leftIndex, value) => { +// const newPairs = [...pairs]; +// newPairs[leftIndex].right = value; +// setPairs(newPairs); + +// const isComplete = newPairs.every((pair) => pair.right !== ''); +// if (isComplete) { +// onAnswer(index , newPairs); +// } +// }; + +// return ( +//
+//

{question.TITLE}

+//

{question.QUESTION}

+// {question.VIDEO && ( +// +// )} +// {question.IMAGE && Question} +// {question.AUDIO && } +//
+// {pairs.map((pair, index) => ( +//
+//
{pair.left}
+// +//
+// ))} +//
+//
+// ); +// }; + +// export default MatchingPairsQuestion; + + + + + +// components/MatchingPairsQuestion.js +import React, { useState, useEffect } from 'react'; + +const colors = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9']; + +const shuffleArray = (array) => { + return array + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); +}; + +function arrayToString(arr) { + return arr.join(', '); +} + +function stringToArray(str) { + return str.split(', '); +} + +const MatchingPairsQuestion = ({ question, onAnswer, savedAnswer, index }) => { + const [pairs, setPairs] = useState([]); + const [selectedLeft, setSelectedLeft] = useState(null); + const [selectedRight, setSelectedRight] = useState(null); + const [rightOptions, setRightOptions] = useState([]); + const [isComplete, setIsComplete] = useState(false); + const [isShuffled, setIsShuffled] = useState(false); + + useEffect(() => { + const initialPairs = question.matchingPairs.map((pair) => ({ + left: pair.LEFT_PAIR, + right: '', + color: colors[question.matchingPairs.indexOf(pair) % colors.length], + })); + + if (savedAnswer) { + const arrSavedAnswer = stringToArray(savedAnswer); + const updatedPairs = initialPairs.map((pair, index) => ({ + ...pair, + right: arrSavedAnswer[index], + })); + setPairs(updatedPairs); + } else { + setPairs(initialPairs); + } + + if (!isShuffled) { + setIsShuffled(true); + setRightOptions(shuffleArray(question.matchingPairs.map((pair) => pair.RIGHT_PAIR))); + } + }, [question, savedAnswer]); + + const handleLeftClick = (index) => { + setSelectedLeft(index); + const status = pairs.findIndex(item => item.right === rightOptions[selectedRight]); + if (selectedRight !== null) { + makePair(index, selectedRight, status); + } + }; + + const handleRightClick = (index) => { + setSelectedRight(index); + const status = pairs.findIndex(item => item.right === rightOptions[index]); + if (selectedLeft !== null) { + makePair(selectedLeft, index, status); + } + }; + + const makePair = (leftIndex, rightIndex, changePair) => { + const newPairs = [...pairs]; + if (changePair > -1) { + setIsComplete(false); + pairs[changePair].right = ''; + } + + const selectedRightValue = rightOptions[rightIndex]; + newPairs[leftIndex].right = selectedRightValue; + setPairs(newPairs); + // console.log(newPairs); + + setSelectedLeft(null); + setSelectedRight(null); + + const allPairsMatched = newPairs.every((pair) => pair.right !== ''); + if (allPairsMatched && !isComplete) { + setIsComplete(true); + + const rightAnswers = newPairs.map((pair) => pair.right); + console.log(rightAnswers); + onAnswer(index, arrayToString(rightAnswers)); + } + }; + + return ( +
+

{question.TITLE}

+

{question.QUESTION}

+ {question.VIDEO && ( + + )} + {question.IMAGE && Question} + {question.AUDIO && } + +
+ {/* Bagian kiri */} +
+ {pairs.map((pair, index) => ( +
handleLeftClick(index)} + style={{ + padding: '10px', + margin: '10px', + cursor: 'pointer', + backgroundColor: selectedLeft === index ? '#ccc' : pair.color, + color: 'white', + borderRadius: '5px', + }} + > + {pair.left} +
+ ))} +
+ + {/* Bagian kanan */} +
+ {rightOptions.map((right, index) => ( +
handleRightClick(index)} + style={{ + padding: '10px', + margin: '10px', + cursor: 'pointer', + backgroundColor: + pairs.find((pair) => pair.right === right)?.color || + (selectedRight === index ? '#ccc' : '#f0f0f0'), + color: 'white', + borderRadius: '5px', + }} + > + {right} +
+ ))} +
+
+
+ ); +}; + +export default MatchingPairsQuestion; diff --git a/src/roles/user/exerciseTest/views/MultipleChoiceQuestion.jsx b/src/roles/user/exerciseTest/views/MultipleChoiceQuestion.jsx new file mode 100644 index 0000000..2ba614e --- /dev/null +++ b/src/roles/user/exerciseTest/views/MultipleChoiceQuestion.jsx @@ -0,0 +1,53 @@ +import React from 'react'; + +const MultipleChoiceQuestion = ({ question, onAnswer, savedAnswer, index }) => { + const options = question.multipleChoices[0]; + + const handleChange = (e) => { + onAnswer(index, e.target.value); + }; + + function getOptionLetter(option) { + const match = option.match(/OPTION_(\w)/); + return match ? match[1] : null; + } + + return ( +
+

{question.TITLE}

+

{question.QUESTION}

+ {question.VIDEO && ( + + )} + {question.IMAGE && Question} + {question.AUDIO && } +
+ {['OPTION_A', 'OPTION_B', 'OPTION_C', 'OPTION_D', 'OPTION_E'].map( + (optionKey) => { + if (options[optionKey]) { + return ( +
+ +
+ ); + } + return null; + } + )} +
+
+ ); +}; + +export default MultipleChoiceQuestion; diff --git a/src/roles/user/exerciseTest/views/TrueFalseQuestion.jsx b/src/roles/user/exerciseTest/views/TrueFalseQuestion.jsx new file mode 100644 index 0000000..4bba64b --- /dev/null +++ b/src/roles/user/exerciseTest/views/TrueFalseQuestion.jsx @@ -0,0 +1,45 @@ +import React from 'react'; + +const TrueFalseQuestion = ({ question, onAnswer, savedAnswer, index }) => { + const handleChange = (e) => { + onAnswer(index, e.target.value); + }; + + return ( +
+

{question.TITLE}

+

{question.QUESTION}

+ {question.VIDEO && ( + + )} + {question.IMAGE && Question} + {question.AUDIO && } +
+ + +
+
+ ); +}; + +export default TrueFalseQuestion; diff --git a/src/roles/user/history/hooks/useHirtories.jsx b/src/roles/user/history/hooks/useHirtories.jsx new file mode 100644 index 0000000..fcd050c --- /dev/null +++ b/src/roles/user/history/hooks/useHirtories.jsx @@ -0,0 +1,130 @@ +import { useEffect, useState } from 'react'; +import serviceHistory from '../services/historyService'; +import { useSlugContext } from '../../../../utils/SlugContext'; + +const useHistories = () => { + const [historyData, setHistoryData] = useState([]); + const [loading, setLoading] = useState(true); + const [loadingTopic, setLoadingTopic] = useState(true); + const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState('all'); + const { sectionSlugMap } = useSlugContext(); + const sectionData = Object.keys(sectionSlugMap); + + const [topicsBySection, setTopicsBySection] = useState({}); + const [selectedTopic, setSelectedTopic] = useState(null); + + const findKeyById = (id) => { + for (const key in sectionSlugMap) { + if (sectionSlugMap[key] === id) { + return key; + } + } + return null; + }; + + useEffect(() => { + const fetchTopics = async () => { + setLoadingTopic(true); + try { + const topics = await serviceHistory.fetchTopic(); + const groupedTopics = topics.payload.reduce((acc, topic) => { + const section = findKeyById(topic.ID_SECTION); + if (!acc[section]) acc[section] = []; + acc[section].push(topic); + return acc; + }, {}); + setTopicsBySection(groupedTopics); + } catch (error) { + console.log('Failed to fetch topics', error); + } finally { + setLoadingTopic(false); + } + }; + + fetchTopics(); + }, []); + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + setError(null); + try { + let data; + if (selectedTopic) { + data = await serviceHistory.fetchHistoryByTopic(selectedTopic); + } else if (activeTab === 'all') { + data = await serviceHistory.fetchAllHistory(); + } else { + data = await serviceHistory.fetchHistoryBySection(sectionSlugMap[activeTab]); + } + setHistoryData(data.payload); + } catch (error) { + console.log(error); + setError('Failed to fetch data'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [activeTab, selectedTopic]); + + function formatLocalDate(isoDate) { + const date = new Date(isoDate); + + const options = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + timeZone: 'Asia/Jakarta' + }; + + return new Intl.DateTimeFormat('id-ID', options).format(date) + .replace(/\//g, '-') + .replace(/\./g, ':') + ' WIB'; + } + + function compareLevels(currentLevel, nextLevel) { + function extractNumber(level) { + if (level !== null) { + const match = level.match(/\d+/); + return match ? parseInt(match[0]) : 0; + }else{ + console.error('some parameter is null'); + return null; + } + } + + const current = extractNumber(currentLevel); + const next = extractNumber(nextLevel); + + if (current === next) { + return 'stay'; + } else if (current > next) { + return 'down'; + } else { + return 'jump'; + } + } + + return { + historyData, + sectionData, + loading, + error, + activeTab, + topicsBySection, + selectedTopic, + setActiveTab, + formatLocalDate, + compareLevels, + setSelectedTopic, + }; +}; + +export default useHistories; diff --git a/src/roles/user/history/services/historyService.jsx b/src/roles/user/history/services/historyService.jsx new file mode 100644 index 0000000..01c8585 --- /dev/null +++ b/src/roles/user/history/services/historyService.jsx @@ -0,0 +1,54 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchTopic = async () => { + try { + const response = await axiosInstance.get(`/topic`); + return response.data; + } catch (error) { + throw error; + } +}; + +const fetchAllHistory = async () => { + try { + const response = await axiosInstance.get(`/learningHistory`); + const historyData = response.data.payload.history; + const validHistory = historyData.filter(history => history.STUDENT_FINISH !== null); + + return {message: response.data.message, payload: validHistory, status: response.data.statusCode,}; + + } catch (error) { + throw error; + } +}; + +const fetchHistoryBySection = async (section) => { + try { + const response = await axiosInstance.get(`/learningHistory/section/${section}`); + const historyData = response.data.payload; + const validHistory = historyData.filter(history => history.STUDENT_FINISH !== null); + + return {message: response.data.message, payload: validHistory, status: response.data.statusCode,}; + } catch (error) { + return {payload: []}; + } +}; + +const fetchHistoryByTopic = async (topic) => { + try { + const response = await axiosInstance.get(`/learningHistory/topic/${topic}`); + const historyData = response.data.payload; + const validHistory = historyData.filter(history => history.STUDENT_FINISH !== null); + + return {message: response.data.message, payload: validHistory, status: response.data.statusCode,}; + } catch (error) { + return {payload: []}; + } + }; + +export default { + fetchTopic, + fetchAllHistory, + fetchHistoryBySection, + fetchHistoryByTopic +}; \ No newline at end of file diff --git a/src/roles/user/history/views/ExerciseHistory.jsx b/src/roles/user/history/views/ExerciseHistory.jsx new file mode 100644 index 0000000..36f6348 --- /dev/null +++ b/src/roles/user/history/views/ExerciseHistory.jsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { Container, Row, Col, Card, Button, Dropdown, Breadcrumb, Tab, Nav } from 'react-bootstrap'; +import useHistories from '../hooks/useHirtories'; +import { Link } from 'react-router-dom'; +import newBie from '../../../../assets/images/illustration/emptyJourney.png'; +import Skeleton from 'react-loading-skeleton'; + +const ExerciseHistory = () => { + const { + historyData, + sectionData, + loading, + error, + activeTab, + topicsBySection, + selectedTopic, + setActiveTab, + formatLocalDate, + compareLevels, + setSelectedTopic + } = useHistories(); + + if (error) return

{error}

; + + const topics = activeTab !== 'all' ? topicsBySection[activeTab] || []: []; + + return ( +
+

Your Exercise History!

+

Track your progress with a personalized overview of all your workouts.

+ + { setActiveTab(k); setSelectedTopic(''); }}> + + + {/*

Your Exercise History!

+

Track your progress with a personalized overview of all your workouts.

*/} + + +
+
+ {topics.length > 0 && ( + setSelectedTopic(topicId)}> + + {selectedTopic ? topics.find(t => t.ID_TOPIC === selectedTopic)?.NAME_TOPIC : 'Select a Topic'} + + + {topics.map((topic, index) => ( + + {topic.NAME_TOPIC} + + ))} + + + )} +
+ + + {loading?( + + +
+ + +
+ +
+
+ + +
+ +
+
+ + ) : + historyData.length > 0 ? ( + historyData.map((history, index) => ( + + + + +

Exercise

+

Submission: {formatLocalDate(history.STUDENT_FINISH)}

+
+ + {history.SECTION_NAME} + {history.TOPIC_NAME} + {history.CURRENT_LEVEL} + +
+

+ + {history.SCORE}/100 +

+ {compareLevels(history.CURRENT_LEVEL, history.NEXT_LEVEL) === 'jump' ?( +
+ + Jump to Level {history.NEXT_LEVEL} +
+ + ) : ( + compareLevels(history.CURRENT_LEVEL, history.NEXT_LEVEL) === 'down' ?( +
+ + Go Down to Level {history.NEXT_LEVEL} +
+ ) : ( +
+ + Stay in Level {history.NEXT_LEVEL} +
+ ) + )} +
+
+
+ + )) + ) : ( + +

Still new?

+

Begin your journey!

+ + + + ) + } +
+
+
+ ); +}; + +export default ExerciseHistory; diff --git a/src/roles/user/learning/hooks/useSections.jsx b/src/roles/user/learning/hooks/useSections.jsx new file mode 100644 index 0000000..4871ad7 --- /dev/null +++ b/src/roles/user/learning/hooks/useSections.jsx @@ -0,0 +1,31 @@ +import { useState, useEffect } from 'react'; +import learningService from '../services/learningService'; +import { API_URL } from '../../../../utils/Constant'; + +const useSections = () => { + const [sections, setSections] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const thumbPath = `${API_URL}/uploads/section/`; + + useEffect(() => { + const fetchData = async () => { + try { + const dataAPI = await learningService.fetchSections(); + const data = dataAPI.payload; + + setSections(data); + } catch (error) { + setError(error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + return { sections, loading, error, thumbPath}; +}; + +export default useSections; diff --git a/src/roles/user/learning/services/learningService.jsx b/src/roles/user/learning/services/learningService.jsx new file mode 100644 index 0000000..3c0e72f --- /dev/null +++ b/src/roles/user/learning/services/learningService.jsx @@ -0,0 +1,18 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchSections = async () => { + try { + const response = await axiosInstance.get("/section"); + return response.data; + } catch (error) { + console.error("Error fetching sections data", error); + throw error; + } +}; + +export default { + fetchSections +}; + + + diff --git a/src/roles/user/learning/views/Learning.jsx b/src/roles/user/learning/views/Learning.jsx new file mode 100644 index 0000000..ea947fc --- /dev/null +++ b/src/roles/user/learning/views/Learning.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Container, Row, Col, Card, Button } from 'react-bootstrap'; +import { Link } from 'react-router-dom'; +import useSections from '../hooks/useSections'; +import { slugify } from '../../../../utils/Constant'; + +import Skeleton from 'react-loading-skeleton'; + +const Learning = () => { + const { sections, loading, error, thumbPath} = useSections(); + + if (error) { + return
Error: {error.message}
; + } + + return ( +
+
+
+

Choose what you want to learn!

+

Develop your English skills by studying the topics in each section below.

+
+
+ + {loading ? ( +
+ + +
+ ) : ( + + {Array.isArray(sections) && sections.map(section => ( + + + + + {section.NAME_SECTION} + + {section.DESCRIPTION_SECTION} + + + + + + ))} + + )} +
+ ); +}; + +export default Learning; diff --git a/src/roles/user/level/hooks/useLevels.jsx b/src/roles/user/level/hooks/useLevels.jsx new file mode 100644 index 0000000..766ebce --- /dev/null +++ b/src/roles/user/level/hooks/useLevels.jsx @@ -0,0 +1,76 @@ +import { useEffect, useState } from 'react'; +import levelService from '../services/levelService'; +import { useSlugContext } from '../../../../utils/SlugContext'; + +function extractLevelNumber(levelName) { + if (!levelName) { + return null; + } + const number = levelName.match(/\d+/); + return number ? parseInt(number[0], 10) : 0; +} + +const useLevels = (section, topic) => { + const [levels, setLevels] = useState([]); + const [lastCompletedLevel, setLastCompletedLevel] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const { topicSlugMap } = useSlugContext(); + + const savedLevel = JSON.parse(localStorage.getItem('savedLevels')); + const savedScore = JSON.parse(localStorage.getItem('savedScores')); + + const levelDesc = ['PRETEST: Introduction to Topic', 'Beginer', 'Advanced Concepts', 'Practice Exercises', 'Case Studies', 'Vacational Test', 'Final Test']; + + const lastLevel = [{ + ID_STUDENT_LEARNING : "6b81668b-42e2-4254-af44-74063ac61ece", + ID_LEVEL : "0f89df44-edf8-40f6-a81b-1bc77628d798", + NAME_LEVEL : "Level 4", + FINISHED_AT : "2024-09-12T04:23:44.000Z" + }]; + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + try { + const response = await levelService.fetchLevels(topicSlugMap[topic]); + setLevels(response.data.levels); + + // const next = await levelService.fetchNextLearning(topicSlugMap[topic]); + // const data = await levelService.fetchLevels(next); + // if (data.length > 0) { + // setLevels(data.levels); + // setLastCompletedLevel(data.lastCompletedLevel); + // }else{ + // const datas = await levelService.levelnol(topicSlugMap[topic]); + // setLevels(datas.levels); + // setLastCompletedLevel(datas.lastCompletedLevel); + // } + + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [section, topic]); + + const isLevelUnlocked = (levelId) => { + if (levelId !== 0) { + if (!lastCompletedLevel){ + return false; + }else{ + const maxUnlock = extractLevelNumber(lastCompletedLevel.NAME_LEVEL); + return levelId <= maxUnlock; + } + }else{ + return true; + } + + }; + return { levels, lastCompletedLevel, loading, error, isLevelUnlocked, levelDesc}; +}; + +export default useLevels; \ No newline at end of file diff --git a/src/roles/user/level/services/levelService.jsx b/src/roles/user/level/services/levelService.jsx new file mode 100644 index 0000000..b0142f8 --- /dev/null +++ b/src/roles/user/level/services/levelService.jsx @@ -0,0 +1,75 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const sortLevels = (levels) => { + return levels.sort((a, b) => { + const getLevelValue = (level) => { + if (level === 'Pretest' || level === 'PRETEST') return 0; + const match = level.match(/level (\d+)/); + return match ? parseInt(match[1], 10) : Infinity; + }; + + return getLevelValue(a.NAME_LEVEL) - getLevelValue(b.NAME_LEVEL); + }); +}; + +const fetchLevels = async (topicId) => { + try { + const response = await axiosInstance.get(`/level/topic/${topicId}`); + return response.data; + } catch (error) { + return []; + } +}; + +// const fetchNextLearning = async (topicId) => { +// try { +// const response = await axiosInstance.get(`/level/topic/${topicId}`); +// return response.data.data.lastCompletedLevel.NEXT_LEARNING; +// } catch (error) { +// return []; +// } +// }; + +// const fetchLevels = async (next) => { +// try { +// const response = await axiosInstance.get(`/previous/level/${next}`); +// // return response.data; + +// const { currentLevel, previousLevels } = response.data.data; +// const combinedLevels = [currentLevel, ...previousLevels]; +// const sortedLevels = sortLevels(combinedLevels); + +// for (let i = sortedLevels.length; i < 7; i++) { +// sortedLevels.push({ID_LEVEL: `L00${i}`, IS_PRETEST: 0, SCORE: null}); +// } + +// return {levels: sortedLevels, lastCompletedLevel: response.data.data.currentLevel} + +// } catch (error) { +// return []; +// } +// }; + +// const levelnol =async (next) =>{ +// try { +// const response = await axiosInstance.get(`/level/topic/${next}`); +// const levelData = response.data.data.levels; +// const currentLevel = levelData.filter(level => level.NAME_LEVEL === 'Pretest'); + +// for (let i = currentLevel.length; i < 7; i++) { +// currentLevel.push({ID_LEVEL: `L00${i}`, IS_PRETEST: 0, SCORE: null}); +// } + +// return {levels: currentLevel, lastCompletedLevel: currentLevel}; + +// } catch (error) { +// return error; +// } +// } + +export default { + fetchLevels, + // fetchNextLearning, + // levelnol, + // test +}; diff --git a/src/roles/user/level/views/Level.jsx b/src/roles/user/level/views/Level.jsx new file mode 100644 index 0000000..9e3d518 --- /dev/null +++ b/src/roles/user/level/views/Level.jsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { Container, Row, Col, Button, Breadcrumb } from 'react-bootstrap'; +import { Link, useParams, Navigate } from 'react-router-dom'; +import check from '../../../../assets/images/illustration/checkIcon.png'; +// import { useValidation } from '../../utils/ValidationContext'; +import useLevels from '../hooks/useLevels'; +import { unSlugify } from '../../../../utils/Constant'; + +import Skeleton from 'react-loading-skeleton'; + +const Level = () => { + const { section, topic } = useParams(); + // const { isValidSection, isValidTopic } = useValidation(); + + // if (!isValidSection(section)) { + // return ; + // } else { + // if (!isValidTopic(topic)) { + // return ; + // } + // } + + const { levels, lastCompletedLevel, loading, error, isLevelUnlocked, levelDesc } = useLevels(section, topic); + + if (loading) { + return ( +
+ + + +
+ ); + } + + if (error) { + return
Error: {error.message}
; + } + + return ( + + + + + + Learning + {unSlugify(section)} + {unSlugify(topic)} + + + + + {levels.length === 0 ? ( +

Materi Belum Tersedia

+ ) : ( + + {levels.map((level, index) => ( + +
+ {(!isLevelUnlocked(index) && level.SCORE > 40) && } +
+ {level.IS_PRETEST === 0 ?( +
+

LEVEL

+ {index} +
+ ) : ( +
+

PRETEST

+
+ )} +
Score {level.SCORE === null ? 0 : level.SCORE}/100
+
+

+ {levelDesc[index]} +

+ {!isLevelUnlocked(index) ? ( +
+ Not Allowed +
+ ) : ( + level.SCORE < 65 ?( + + ) : ( +
+ Finished +
+ ) + )} +
+ + ))} +
+ )} +
+ ); +}; + +export default Level; \ No newline at end of file diff --git a/src/roles/user/material/hooks/useMaterials.jsx b/src/roles/user/material/hooks/useMaterials.jsx new file mode 100644 index 0000000..ac8c466 --- /dev/null +++ b/src/roles/user/material/hooks/useMaterials.jsx @@ -0,0 +1,29 @@ +import { useState, useEffect } from 'react'; +import materialService from '../services/materialService'; +import { useSlugContext } from '../../../../utils/SlugContext'; + +const useMaterials = (section, topic, level) => { + const [materials, setMaterial] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const { topicSlugMap } = useSlugContext(); + + useEffect(() => { + const fetchData = async () => { + try { + const data = await materialService.fetchMaterials(topicSlugMap[topic], level); + setMaterial(data[0]); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [section, topic, level]); + + return { materials, loading, error }; +}; + +export default useMaterials; diff --git a/src/roles/user/material/services/materialService.jsx b/src/roles/user/material/services/materialService.jsx new file mode 100644 index 0000000..ae56229 --- /dev/null +++ b/src/roles/user/material/services/materialService.jsx @@ -0,0 +1,15 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchMaterials = async (topicId, levelName) => { + try { + const response = await axiosInstance.get(`/level/topic/${topicId}`); + const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName); + return filteredData; + } catch (error) { + return []; + } +}; + +export default { + fetchMaterials, +}; diff --git a/src/roles/user/material/views/Material.jsx b/src/roles/user/material/views/Material.jsx new file mode 100644 index 0000000..27a44fb --- /dev/null +++ b/src/roles/user/material/views/Material.jsx @@ -0,0 +1,96 @@ +import React, { useEffect, useState } from 'react'; +import { Container, Row, Col, Card, Button, Breadcrumb } from 'react-bootstrap'; +import { Link, useParams } from 'react-router-dom'; +import { useGoBack } from '../../../../utils/NavigationBack'; +import useMaterial from '../hooks/useMaterials'; + +import MediaViewer from './components/MediaViewer'; + +import { API_URL } from '../../../../utils/Constant'; + +import { unSlugify } from '../../../../utils/Constant'; +import Skeleton from 'react-loading-skeleton'; + +const Material = () => { + const { section, topic, level } = useParams(); + const goBack = useGoBack(); + + const { materials, loading, error } = useMaterial(section, topic, unSlugify(level)); + const [localLoader, setLoader]= useState(true); + + const [mediaUrls, setMedia] = useState([]); + const mediaPath = `${API_URL}/uploads/level`; + + useEffect(() => { + if (!loading) { + if (materials.IMAGE) setMedia([`${mediaPath}/image/${materials.IMAGE}`]); + if (materials.AUDIO) setMedia([`${mediaPath}/audio/${materials.AUDIO}`]); + if (materials.VIDEO) setMedia([materials.VIDEO]); + setLoader(false); + } + }, [loading]) + + if (error) { + return
Error: {error.message}
; + } + + return ( + + + + + + Learning + {unSlugify(section)} + {unSlugify(topic)} + {unSlugify(level)} + + + + + + {localLoader ? ( +
+ + +
+ ) : ( + + + + {/*
+ +
*/} + + {/* {mediaUrls.length > 0 && } */} + {/*

+ {materials.CONTENT} +

*/} + +
+ + {/*
+ +
+

+ So while the distant hum and rustle might paint a picture of deep contemplation, remember that this narrative is a masterclass in the art of textual non-substance. The rhythm of time and the interplay of light and shadow are just clever ways to make you think there’s more to this than meets the eye. In reality, it's the literary equivalent of a “Coming Soon” sign—full of promise, yet delightfully devoid of any actual meaning. +

+
+ +
*/} + + + + )} + + ); +}; + +export default Material; diff --git a/src/roles/user/material/views/components/MediaViewer.jsx b/src/roles/user/material/views/components/MediaViewer.jsx new file mode 100644 index 0000000..c7fb089 --- /dev/null +++ b/src/roles/user/material/views/components/MediaViewer.jsx @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from 'react'; +import Skeleton from 'react-loading-skeleton'; + +const MediaViewer = ({ mediaUrls }) => { + const [loadedMedia, setLoadedMedia] = useState([]); + const [loading, setLoading] = useState(true); + + function isMediaUrl(url) { + const urls = url[0]; + const audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac']; + const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp']; + + const lowercasedUrl = urls.toLowerCase(); + const isAudio = audioExtensions.some(extension => lowercasedUrl.endsWith(extension)); + const isImage = imageExtensions.some(extension => lowercasedUrl.endsWith(extension)); + + return isAudio || isImage; + } + + const preloadMedia = (urls) => { + return Promise.all( + urls.map((url) => { + return new Promise((resolve, reject) => { + const mediaType = url.split('.').pop(); + + if (['jpg', 'jpeg', 'png', 'gif'].includes(mediaType)) { + const img = new Image(); + img.src = url; + img.onload = () => resolve({ type: 'image', url }); + img.onerror = () => reject(`Failed to load image: ${url}`); + } else if (['mp3', 'wav'].includes(mediaType)) { + const audio = new Audio(); + audio.src = url; + audio.onloadedmetadata = () => resolve({ type: 'audio', url }); + audio.onerror = () => reject(`Failed to load audio: ${url}`); + } else { + resolve({ type: 'unknown', url }); + } + }); + }) + ); + }; + + const renderVideo = (url) => { + // Cek apakah ini link YouTube + if (url.includes('youtube.com') || url.includes('youtu.be')) { + const youtubeId = url.includes('youtube.com') + ? new URLSearchParams(new URL(url).search).get('v') // Ambil ID dari link YouTube + : url.split('/').pop(); // Ambil ID dari youtu.be link + + return ( + + ); + } else if (url.includes('drive.google.com')) { + const driveId = url.includes('file/d/') ? url.split('file/d/')[1].split('/')[0] : ''; + return ( + + ); + } else { + return ( + + ); + } + }; + + useEffect(() => { + if (mediaUrls && mediaUrls.length > 0 && isMediaUrl(mediaUrls)) { + preloadMedia(mediaUrls) + .then((loaded) => { + setLoadedMedia(loaded); + setLoading(false); + }) + .catch((error) => { + console.error(error); + setLoading(false); + }); + } else { + setLoading(false); + } + }, [mediaUrls]); + + if (loading) { + return ( + + ); + } + + return ( +
+ {isMediaUrl(mediaUrls) ? ( + loadedMedia.map((media, index) => { + if (media.type === 'image') { + return {`Media; + } else if (media.type === 'audio') { + return ; + } else { + return
Unknown media type
; + } + }) + ) : ( + renderVideo(mediaUrls[0]) // Panggil fungsi renderVideo dengan URL yang benar + )} +
+ ); +}; + +export default MediaViewer; diff --git a/src/roles/user/report/hooks/useReports.jsx b/src/roles/user/report/hooks/useReports.jsx new file mode 100644 index 0000000..1a51b3a --- /dev/null +++ b/src/roles/user/report/hooks/useReports.jsx @@ -0,0 +1,37 @@ +// hooks/useReport.js +import { useState } from 'react'; +import { submitReport } from '../services/ReportService'; + +const useReports = () => { + const [report, setReport] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [successReport, setSuccess] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setError(null); + setSuccess(false); + try { + await submitReport(report); + setSuccess(true); + setReport(''); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return { + report, + setReport, + loading, + error, + successReport, + handleSubmit, + }; +}; + +export default useReports; diff --git a/src/roles/user/report/services/ReportService.jsx b/src/roles/user/report/services/ReportService.jsx new file mode 100644 index 0000000..1dafdf8 --- /dev/null +++ b/src/roles/user/report/services/ReportService.jsx @@ -0,0 +1,12 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +export const submitReport = async (reportData) => { + try { + const response = await axiosInstance.post(`/report`, { + REPORTS: reportData, + }); + return response.data; + } catch (error) { + throw new Error(error.response ? error.response.data.message : 'Failed to submit report'); + } +}; diff --git a/src/roles/user/report/views/Report.jsx b/src/roles/user/report/views/Report.jsx new file mode 100644 index 0000000..de2e4f6 --- /dev/null +++ b/src/roles/user/report/views/Report.jsx @@ -0,0 +1,51 @@ +import React from "react"; +import useReports from "../hooks/useReports"; +import { Modal, Form, Button, Spinner } from "react-bootstrap"; +import success from "../../../../assets/images/illustration/report.png"; + +const UserNavbar = ({onClose}) => { + + const { report, setReport, loading, error, successReport, handleSubmit } = useReports(); + + return ( + <> + {/* */} + {successReport?( + +

Issue report received!

+ +

Thank you for letting us know. We’ll investigate the issue and work on resolving it promptly.

+ +
+ ):( + <> + + Report an Issue + + +
+ + In order to improve our service, kindly share your experience. + setReport(e.target.value)} + required + /> + + +
+
+ + ) + } + {/*
*/} + + ); +}; + +export default UserNavbar; diff --git a/src/roles/user/setting/hooks/useSettings.jsx b/src/roles/user/setting/hooks/useSettings.jsx new file mode 100644 index 0000000..c9235f4 --- /dev/null +++ b/src/roles/user/setting/hooks/useSettings.jsx @@ -0,0 +1,163 @@ +import { useState, useEffect } from 'react'; +import settingService from '../services/SettingService'; +import { API_URL } from '../../../../utils/Constant'; + +const useSettings = () => { + const [profile, setProfile] = useState(null); + const [selectedImage, setSelectedImage] = useState(null); + const [idUser, setIdUser] = useState(null); + const [loading, setLoading] = useState(true); + const [loadingUpdate, setLoadingUpdate] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + const thumbPath = `${API_URL}/uploads/avatar/`; + + useEffect(() => { + const fetchData = async () => { + try { + const data = await settingService.fetchProfile(); + setProfile(data.payload); + setIdUser(data.payload.ID) + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setProfile((prevProfile) => ({ + ...prevProfile, + [name]: value, + })); + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file && file.size <= 5 * 1024 * 1024) { + setSelectedImage(file); + } else { + setError('File size must be less than 5 MB'); + } + }; + + const handleSubmitProfile = async (e) => { + e.preventDefault(); + setLoadingUpdate(true); + setError(null); + setSuccess(false); + + const formData = new FormData(); + formData.append('NAME_USERS', profile.NAME_USERS); + formData.append('EMAIL', profile.EMAIL); + formData.append('NISN', profile.NISN); + + if (selectedImage) { + formData.append('PICTURE', selectedImage); + } + + try { + await settingService.updateProfile(idUser, formData); + setSuccess(true); + } catch (err) { + setError('Failed to update profile'); + } finally { + setLoadingUpdate(false); + } + }; + + + + //Change Pass + const [formData, setFormData] = useState({ + oldPassword: '', + newPassword: '', + confirmPassword: '', + }); + + const [loadingPass, setLoadingPass] = useState(false); + const [errorPass, setErrorPass] = useState(null); + const [successPass, setSuccessPass] = useState(false); + + const handleInputPassChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleChangePass = async (e) => { + e.preventDefault(); + setLoadingPass(true); + setErrorPass(null); + setSuccessPass(false); + + const passwordData = { + OLD_PASSWORD: formData.oldPassword, + PASSWORD: formData.newPassword, + CONFIRM_PASSWORD: formData.confirmPassword, + }; + + // Validasi bahwa password baru dan confirm password cocok + if (formData.newPassword !== formData.confirmPassword) { + setErrorPass("New Password and Confirm Password don't match"); + setLoadingPass(false); + return; + } + + try { + // Panggil API untuk update password + await settingService.updatePassword(idUser, passwordData); + setSuccessPass(true); // Tampilkan pesan sukses jika berhasil + } catch (err) { + setErrorPass('Failed to update password: ' + err.message); + } finally { + setLoadingPass(false); // Reset loading setelah selesai + } + }; + + const resetForm = () => { + setFormData({ + oldPassword: '', + newPassword: '', + confirmPassword: '', + }); + setSuccess(false); + }; + + const resetStatus = () => { + setLoadingUpdate(false); + setError(null); + setSuccess(false); + setLoadingPass(false); + setErrorPass(null); + setSuccessPass(false); + } + + return { + profile, + selectedImage, + loading, + loadingUpdate, + error, + success, + handleInputChange, + handleImageChange, + handleSubmitProfile, + thumbPath, + + // Change Pass + formData, + handleInputPassChange, + handleChangePass, + loadingPass, + errorPass, + successPass, + resetForm, + resetStatus + }; + +}; + +export default useSettings; diff --git a/src/roles/user/setting/services/SettingService.jsx b/src/roles/user/setting/services/SettingService.jsx new file mode 100644 index 0000000..fb395d3 --- /dev/null +++ b/src/roles/user/setting/services/SettingService.jsx @@ -0,0 +1,55 @@ +import axios from 'axios'; +import { API_URL } from '../../../../utils/Constant'; + +const config = { + headers: { + Authorization: localStorage.getItem('token') + } +}; + +const fetchProfile = async () => { + try { + const response = await axios.get(`${API_URL}/getMe`, config); + return response.data; + } catch (error) { + throw error; + } +}; + +const updateProfile = async (id, formData) => { + + const cfg = { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: localStorage.getItem('token') + }, + }; + + try { + const response = await axios.put(`${API_URL}/user/update/${id}`, formData, cfg); + return response.data; + } catch (error) { + throw error; + } +}; + +const updatePassword = async (userId, passwordData) => { + const cfg = { + headers: { + Authorization: localStorage.getItem('token'), + 'Content-Type': 'application/json', + }, + }; + try { + const response = await axios.put(`${API_URL}/user/update/password/${userId}`, passwordData, cfg); + return response.data; + } catch (error) { + throw error; + } +}; + +export default { + fetchProfile, + updateProfile, + updatePassword +}; diff --git a/src/roles/user/setting/views/Setting.jsx b/src/roles/user/setting/views/Setting.jsx new file mode 100644 index 0000000..659c683 --- /dev/null +++ b/src/roles/user/setting/views/Setting.jsx @@ -0,0 +1,255 @@ +import React, { useState } from 'react'; +import { Container, Row, Col, Button, Form, Tab, Nav, Alert, Modal, Spinner } from 'react-bootstrap'; +import avatar from '../../../../assets/images/default-avatar.jpg'; +import ilustration from '../../../../assets/images/illustration/changePass.png'; +import successModal from '../../../../assets/images/illustration/successModal.png'; +import errorModal from '../../../../assets/images/illustration/IllustrationForgot.png'; +import useSettings from '../hooks/useSettings'; + +import Skeleton from 'react-loading-skeleton'; + +const Setting = () => { + const { + profile, + selectedImage, + loading, + loadingUpdate, + error, + success, + handleInputChange, + handleImageChange, + handleSubmitProfile, + formData, + handleInputPassChange, + handleChangePass, + loadingPass, + errorPass, + successPass, + resetForm, + resetStatus, + thumbPath, + } = useSettings(); + + const [showModal, setShowModal] = useState(false); + + const handleCloseModal = () => { + setShowModal(false); + resetForm(); + }; + + const handleFormProfile = async (e) => { + resetStatus(); + setShowModal(true); + await handleSubmitProfile(e); + }; + + const handleFormPass = async (e) => { + resetStatus(); + setShowModal(true); + await handleChangePass(e); + }; + + // if (loading) { + // return <>; + // } + + return ( + + + + +

Your Exercise History!

+

Track your progress with a personalized overview of all your workouts.

+ + +
+ {loading?( +
+ + +
+ ) : ( + + + + +
+ + NISN* + + + + + Class* + + + + + Full Name* + + + + + Email Address* + + + + +
+ + + +
+

Avatar

+ avatar +
+ +
+ Ensure the file size is no larger than 5 MB +
+ +
+ +
+ + + +
+ + Old Password* + + + + New Password* + + + + Confirm New Password* + + + +
+ + +
+ +
+ +
+
+
+ )} +
+ + + +

+ {error && "ERROR"} + {success && 'Looking Sharp!'} + {errorPass && "ERROR"} + {successPass && 'Security Boosted!'} +

+ + { + loadingUpdate || loadingPass ?( +
+ + + + + + +
+ ):( + imgModal + ) + } + +

+ {error && error} + {success && 'Your profile has been successfully updated.'} + {errorPass && errorPass} + {successPass && "Your password change was successful. You're good to go!"} +

+ + +
+
+ +
+ ); +}; + +export default Setting; \ No newline at end of file diff --git a/src/roles/user/topic/hooks/useTopics.jsx b/src/roles/user/topic/hooks/useTopics.jsx new file mode 100644 index 0000000..1fea719 --- /dev/null +++ b/src/roles/user/topic/hooks/useTopics.jsx @@ -0,0 +1,57 @@ +import { useState, useEffect } from 'react'; +import topicService from '../services/topicService'; +import { API_URL } from '../../../../utils/Constant'; +import { useSlugContext } from '../../../../utils/SlugContext'; + +const useTopics = (sectionId) => { + const [topics, setTopics] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [sections, setSection] = useState(null); + const thumbPath = `${API_URL}/uploads/section/`; + const { sectionSlugMap } = useSlugContext(); + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + try { + const data = await topicService.fetchTopicsBySectionId(sectionSlugMap[sectionId]); + if (Array.isArray(data.payload)) { + setTopics(data.payload); + } else { + setTopics([]); + } + } catch (error) { + setError(error); + } finally { + try { + const sectionData = await topicService.fetchSectionById(sectionSlugMap[sectionId]); + setSection(sectionData.payload); + } catch (err) { + setError('Failed to fetch section'); + } finally { + setLoading(false); + } + } + }; + + // const fetchSection = async () => { + // setLoading(true); + // try { + // const sectionData = await topicService.fetchSectionById(sectionId); + // setSection(sectionData.payload); + // } catch (err) { + // setError('Failed to fetch section'); + // } finally { + // setLoading(false); + // } + // }; + + fetchData(); + // fetchSection(); + }, [sectionId]); + + return { topics, sections, loading, error, thumbPath}; +}; + +export default useTopics; \ No newline at end of file diff --git a/src/roles/user/topic/services/topicService.jsx b/src/roles/user/topic/services/topicService.jsx new file mode 100644 index 0000000..7e9806f --- /dev/null +++ b/src/roles/user/topic/services/topicService.jsx @@ -0,0 +1,24 @@ +import axiosInstance from '../../../../utils/axiosInstance'; + +const fetchTopicsBySectionId = async (sectionId) => { + try { + const response = await axiosInstance.get(`/topic/section/${sectionId}`); + return response.data; + } catch (error) { + return []; + } +}; + +const fetchSectionById = async (sectionId) => { + try { + const response = await axiosInstance.get(`/section/${sectionId}`); + return response.data; + } catch (error) { + throw new Error('Failed to fetch section'); + } +}; + +export default { + fetchTopicsBySectionId, + fetchSectionById, +}; diff --git a/src/roles/user/topic/views/Topic.jsx b/src/roles/user/topic/views/Topic.jsx new file mode 100644 index 0000000..49bb3a1 --- /dev/null +++ b/src/roles/user/topic/views/Topic.jsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { Link, useParams, Navigate } from 'react-router-dom'; +import { Container, Row, Col, Tab, Nav, Button } from 'react-bootstrap'; +import topicImage from '../../../../assets/images/illustration/listening.png'; +import topicIlustration from '../../../../assets/images/illustration/topic.png'; + +import useTopic from '../hooks/useTopics'; +import useSections from '../../learning/hooks/useSections'; + +import { useSlugContext } from '../../../../utils/SlugContext'; +import { slugify, unSlugify } from '../../../../utils/Constant'; + +import Skeleton from 'react-loading-skeleton'; + +const Topic = () => { + const { section } = useParams(); + const { topics, sections, loading, error, thumbPath} = useTopic(section); + // const { sections, loadingSec, errorSec } = useSections(); + + // const currentSection = sections.find(sec => sec.name === section); + // const filteredTopics = topics.filter(topic => topic.sectionId === section); + + + if (loading) { + return ( +
+
+ +
+
+ + +
+
+ ); + } + + if (error) { + return
Error: {error.message}
; + } + + return ( +
+ {topics.length === 0 ? ( +

Belum ada topic

+ ) : ( + + + + + + + + {/*
+
+ + Learning / + {currentSection?.name} +
+

+ {currentSection?.description} +

+
*/} +
+
+ + Learning / + {sections.NAME_SECTION} +
+

+ {sections.DESCRIPTION_SECTION} +

+
+ {topics.map((topic, index) => ( + +
+ +
+

Topic {index + 1}: {topic.NAME_TOPIC}

+

+ {topic.DESCRIPTION_TOPIC} +

+ +
+
+
+ ))} +
+ +
+
+ )} +
+ ); +}; + +export default Topic; + diff --git a/src/utils/Constant.jsx b/src/utils/Constant.jsx new file mode 100644 index 0000000..b31c660 --- /dev/null +++ b/src/utils/Constant.jsx @@ -0,0 +1,30 @@ +// export const API_URL = 'https://8x7r3mdp-3001.asse.devtunnels.ms'; +export const API_URL = 'http://localhost:3001'; +// export const API_URL = 'https://0f4c-114-6-25-184.ngrok-free.app'; + + +export const slugify = (text) => { + if (!text) { + return ''; + } + + return text + .toString() + .toLowerCase() + .trim() + .replace(/\s+/g, '-') + .replace(/[^\w\-]+/g, '') + .replace(/\-\-+/g, '-'); +}; + +export const unSlugify = (text) => { + if (!text) { + return ''; + } + + return text + .toString() + .toLowerCase() + .replace(/-/g, ' ') + .replace(/\b\w/g, (char) => char.toUpperCase()); +}; \ No newline at end of file diff --git a/src/utils/NavigationBack.jsx b/src/utils/NavigationBack.jsx new file mode 100644 index 0000000..e5e540e --- /dev/null +++ b/src/utils/NavigationBack.jsx @@ -0,0 +1,26 @@ +import { useNavigate, useLocation } from 'react-router-dom'; + +export const useGoBack = () => { + const navigate = useNavigate(); + const location = useLocation(); + + const goBack = () => { + // navigate(-1); + + + const pathParts = location.pathname.split('/').filter(part => part !== ''); + const lastSegment = pathParts[pathParts.length - 1]; + let newPath = ""; + if (lastSegment === "material" || lastSegment === "exercise" || lastSegment === "result") { + pathParts.pop(); + pathParts.pop(); + } else { + pathParts.pop(); + } + newPath = `/${pathParts.join('/')}`; + + navigate(newPath); + }; + + return goBack; +}; diff --git a/src/utils/ProtectedRoute.jsx b/src/utils/ProtectedRoute.jsx new file mode 100644 index 0000000..300335d --- /dev/null +++ b/src/utils/ProtectedRoute.jsx @@ -0,0 +1,30 @@ +import { Navigate, Outlet } from 'react-router-dom'; +import useAuth from '../roles/guest/auth/hooks/useAuth'; + +const ProtectedRoute = ({ role }) => { + const { user, getUserFromToken } = useAuth(); + // console.log(user); + if (!user) { + getUserFromToken(); + } + + if (!user) { + return ; + } + + if (user.ROLE !== role) { + let path = ""; + if (user.role == 'student') { + path = '/learning'; + }else if (user.role == 'teacher') { + path = '/teacher'; + }else if (user.role == 'admin') { + path = '/admin'; + } + return ; + } + + return ; +}; + +export default ProtectedRoute; diff --git a/src/utils/SlugContext.jsx b/src/utils/SlugContext.jsx new file mode 100644 index 0000000..0038747 --- /dev/null +++ b/src/utils/SlugContext.jsx @@ -0,0 +1,77 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import axios from 'axios'; +import { API_URL, slugify } from './Constant'; +import axiosInstance from './axiosInstance'; +import loader from "../assets/images/loading.gif" + +// Buat context +const SlugContext = createContext(); +const config = { + headers: { + Authorization: localStorage.getItem('token') + } +}; + +export const SlugProvider = ({ children, logout }) => { + const [sectionSlugMap, setSectionSlugMap] = useState({}); + const [topicSlugMap, setTopicSlugMap] = useState({}); + const [loading, setLoading] = useState(true); + + const fetchData = async () => { + try { + const sectionMapping = {}; + // const sectionResponse = await axios.get(`${API_URL}/section`, config); + const sectionResponse = await axiosInstance.get(`/section`); + sectionResponse.data.payload.forEach((section) => { + const slug = slugify(section.NAME_SECTION); + sectionMapping[slug] = section.ID_SECTION; + }); + setSectionSlugMap(sectionMapping); + } catch (error) { + if (error.status === 403 || error.status === 401) { + logout(); + }else{ + console.error('Error fetching section', error); + } + } finally{ + try{ + const topicMapping = {}; + // const topicResponse = await axios.get(`${API_URL}/topic`, config); + const topicResponse = await axiosInstance.get(`/topic`); + topicResponse.data.payload.forEach((topic) => { + const slug = slugify(topic.NAME_TOPIC); + topicMapping[slug] = topic.ID_TOPIC; + }); + setTopicSlugMap(topicMapping); + } catch (error) { + if (error.status === 403 || error.status === 401) { + logout(); + }else{ + console.error('Error fetching section', error); + setLoading(false); + } + } finally{ + setLoading(false); + } + } + }; + + useEffect(() => { + fetchData(); + }, [logout]); + + if (loading) { + return (

Loading...

); + } + + return ( + + {children} + + ); + +}; + +export const useSlugContext = () => { + return useContext(SlugContext); +}; \ No newline at end of file diff --git a/src/utils/axiosInstance.jsx b/src/utils/axiosInstance.jsx new file mode 100644 index 0000000..ecb3faf --- /dev/null +++ b/src/utils/axiosInstance.jsx @@ -0,0 +1,58 @@ +import axios from 'axios'; +import { API_URL } from './Constant'; + +const axiosInstance = axios.create({ + baseURL: API_URL, +}); + +axiosInstance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = token; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +const refreshAccessToken = async () => { + try { + const response = await axios.post(`${API_URL}/refreshToken`, {}, { withCredentials: true }); + const token = response.data.payload.TOKEN; + + localStorage.setItem('token', token); + console.log('token refreshed'); + return token; + } catch (error) { + console.error('Failed to refresh token', error); + localStorage.removeItem('token'); + window.location.href = '/login'; + throw error; + } +}; + +axiosInstance.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + if (error.response.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + try { + const newAccessToken = await refreshAccessToken(); + originalRequest.headers.Authorization = newAccessToken; + return axiosInstance(originalRequest); + } catch (err) { + return Promise.reject(err); + } + }else if (error.response.status === undefined) { + window.location.href = '/login'; + } + + return Promise.reject(error); + } +); + +export default axiosInstance; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..5a33944 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +})