diff --git a/.env b/.env
new file mode 100644
index 0000000..312c136
--- /dev/null
+++ b/.env
@@ -0,0 +1,6 @@
+VITE_CTP_PROJECT_KEY=art
+VITE_CTP_CLIENT_SECRET=tCcZh5XwKU1grwM-dpEcRnvynmgwxasp
+VITE_CTP_CLIENT_ID=j_yucI-j56zVM3IHDqo7Cfxi
+VITE_CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com
+VITE_CTP_API_URL=https://api.europe-west1.gcp.commercetools.com
+VITE_CTP_SCOPES=manage_my_payments:art view_standalone_prices:art manage_my_orders:art manage_customers:art manage_my_profile:art view_payments:art view_messages:art introspect_oauth_tokens:art create_anonymous_token:art view_quote_requests:art manage_my_quotes:art view_quotes:art manage_my_quote_requests:art view_published_products:art view_discount_codes:art manage_my_shopping_lists:art manage_orders:art view_categories:art view_shopping_lists:art manage_my_business_units:art manage_order_edits:art view_cart_discounts:art view_attribute_groups:art view_project_settings:art view_staged_quotes:art view_product_selections:art view_products:art
\ No newline at end of file
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..e78075d
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,46 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ 'plugin:prettier/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ plugins: [
+ 'react-refresh',
+ 'prettier',
+ ],
+ rules: {
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ "prettier/prettier": [
+ "error",
+ {
+ "endOfLine": "lf"
+ }
+ ],
+ "indent": ["error", 2, { "SwitchCase": 1 }],
+ "no-console": "warn",
+ "prefer-const": "error",
+ "quotes": ["error", "single"],
+ "jsx-quotes": ["error", "prefer-double"],
+ "max-len": ["error", {
+ "code": 120,
+ "ignoreUrls": true,
+ "ignoreRegExpLiterals": true,
+ 'ignoreStrings': true
+ }],
+ "comma-dangle": ["error", "always-multiline"],
+ "semi": ["error", "always"],
+ "no-var": "error",
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": ["error"],
+ "@typescript-eslint/no-explicit-any": "error",
+ "@typescript-eslint/explicit-function-return-type": "error",
+ },
+}
diff --git a/.github/workflows/deploy-template.yaml b/.github/workflows/deploy-template.yaml
new file mode 100644
index 0000000..919b697
--- /dev/null
+++ b/.github/workflows/deploy-template.yaml
@@ -0,0 +1,24 @@
+on:
+ workflow_call:
+ inputs:
+ deploy-dir:
+ required: true
+ type: string
+
+jobs:
+ deploy:
+ runs-on: self-hosted
+ steps:
+ - uses: actions/download-artifact@v3
+ with:
+ name: build.tar.gz
+ path: ./
+ - name: Deploy
+ run: |
+ set -xe
+ echo "Deploy app"
+ tar -xzf build.tar.gz
+ rm -rf ${{ inputs.deploy-dir }} || true
+ mkdir -pv ${{ inputs.deploy-dir }}
+ mv dist ${{ inputs.deploy-dir }}/
+ rm build.tar.gz
\ No newline at end of file
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
new file mode 100644
index 0000000..2e762f1
--- /dev/null
+++ b/.github/workflows/deploy.yaml
@@ -0,0 +1,34 @@
+name: Build
+
+on:
+ push:
+ branches: [ 'sprint_2/login-registration-main', 'sprint_3/catalog-product-profile', 'sprint_4/basket-about_us' ]
+ pull_request:
+ branches: [ 'sprint_2/login-registration-main', 'sprint_3/catalog-product-profile', 'sprint_4/basket-about_us' ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: Build
+ run: |
+ echo "Build app"
+ npm i
+ npm run build
+ tar -czf build.tar.gz ./dist
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: build.tar.gz
+ path: build.tar.gz
+ retention-days: 7
+ deploy_v1:
+ needs: build
+ if: github.event_name != 'pull_request'
+ uses: "./.github/workflows/deploy-template.yaml"
+ with:
+ deploy-dir: "/var/www/ecommerse/v1"
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..cd173dc
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,18 @@
+name: Tests
+
+on:
+ pull_request:
+ branches: [ 'sprint_2/login-registration-main', 'sprint_3/catalog-product-profile', 'sprint_4/basket-about_us' ]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: Tests
+ run: |
+ npm i
+ npm run test
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..d24fdfc
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx lint-staged
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..8ddac9a
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "tabWidth": 2,
+ "printWidth": 120,
+ "singleQuote": true,
+ "bracketSameLine": false,
+ "trailingComma": "all",
+ "bracketSpacing": true,
+ "end_of_line": "lf"
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..73377cb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,102 @@
+# Online Store
+
+**SCOOP** is an online shopping app that provides an interactive and seamless digital experience.
+
+Users can browse through a vast range of products, view detailed descriptions, add their favorite items to the basket, and proceed to checkout. It includes features such as login and registration, product search, product categorization, sorting, and other.
+
+It's fully responsive, ensuring it looks great on various devices with a minimum resolution of 390px.
+
+**Key pages:**
+- Main page
+- Login and Registration
+- Catalog
+- Product page
+- User Profile
+- Basket
+- About
+
+The application is powered by CommerceTools, a leading provider of commerce solutions for B2C and B2B enterprises. CommerceTools offers a cloud-native, microservices-based commerce platform that enables brands to create unique and engaging digital commerce experiences.
+
+# **📚 Technology Stack**
+- `TypeScript` `React` `Redux Toolkit` `React Router`
+- `Vite`
+- `ESLint` `Prettier` `Husky`
+- `JEST` `Testing Library`
+- `Sass` `Classnames`
+- `SDK CommerceTools`
+
+- `CommerceTools`
+
+Used CommerceTools as a **public API** to intergrate its functionality with the current project, and get access to various features and functionalities that CommerceTools offers, such as retrieving product information, processing orders, managing inventory, and other.
+
+# ⚒️ Scripts
+
+```jsx
+npm run build // build the project,
+ // compile the source code and generate production-ready output files
+
+npm run preview // preview the project
+
+npm run dev // starts a dev server for further development
+
+npm run lint // run a code linter, which checks the code for
+ // potential errors, coding style violations, and other issues
+
+npm lint:fix // automatically fix some of the issues
+ // reported by the linter (ex: formatting problems)
+
+npm run prettier // run the Prettier code formatter to ensure
+ // consistent code formatting throughout the project
+
+npm run prepare // automatically executed before the package is published.
+ // It prepares the project for distribution (transpile, clean, or compress files)
+
+npm run test // run automated tests for the project
+ // to ensure its functionality and detect any issues
+```
+
+# 🧑💻 Set up and run the project locally
+
+1. Open the folder in your IDE where you'll be working on this project.
+2. Open the Terminal (Important: Make sure it's in the same folder).
+3. Start here:
+
+```tsx
+// Clone the project
+git clone https://github.com/ElenaYrm/eCommerce-App.git
+
+// Switch to develop branch
+git checkout develop
+
+// Install the dependencies
+npm install (or npm i)
+```
+
+4. To preview:
+
+```tsx
+// Run the application
+npm run dev (or npm run preview)
+```
+
+5. To contribute:
+
+```tsx
+// Create a new branch from develop (required)
+git checkout -b feat/issueName-description
+```
+
+6. Commit changes
+7. Pull-request to develop → Use [template](https://github.com/ElenaYrm/eCommerce-App/blob/main/.github/pull_request_template.md)
+8. You are awesome!
+
+# 👯♂️ Team
+
+[German](https://github.com/germangrib)
+[Elena](https://github.com/ElenaYrm)
+[Nastia](https://github.com/HereEast)
+
+# ⬇️ Important
+
+1. Please, **do not merge changes** without any of the team’s assignment.
+2. All the visual materials and data you see on this website are meant **for learning purposes only**. We're not running a commercial project here, it's all about education and exploration! However, if you ever feel that any content on this website infringes on your rights or if you have any concerns, please don't hesitate to reach out to any of the team members above 🙂
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..e83d758
--- /dev/null
+++ b/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Scoop Art
+
+
+
+
+
+
+
+
diff --git a/jest-setup.ts b/jest-setup.ts
new file mode 100644
index 0000000..7b0828b
--- /dev/null
+++ b/jest-setup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom';
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..5c2f6b0
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,9648 @@
+{
+ "name": "ecommerce",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ecommerce",
+ "version": "0.0.0",
+ "dependencies": {
+ "@commercetools/api-request-builder": "^6.0.0",
+ "@commercetools/platform-sdk": "^5.0.0",
+ "@commercetools/sdk-client-v2": "^2.2.0",
+ "@reduxjs/toolkit": "^1.9.5",
+ "classnames": "^2.3.2",
+ "formik": "^2.4.3",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-infinite-scroll-component": "^6.1.0",
+ "react-redux": "^8.1.2",
+ "react-router-dom": "^6.15.0",
+ "swiper": "^10.2.0",
+ "vite-plugin-svgr": "^3.2.0"
+ },
+ "devDependencies": {
+ "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
+ "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
+ "@testing-library/jest-dom": "^5.17.0",
+ "@testing-library/react": "^14.0.0",
+ "@testing-library/user-event": "^14.4.3",
+ "@types/jest": "^29.5.3",
+ "@types/react": "^18.2.21",
+ "@types/react-dom": "^18.2.7",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitejs/plugin-react": "^4.0.3",
+ "buffer": "^6.0.3",
+ "eslint": "^8.45.0",
+ "eslint-config-prettier": "^8.9.0",
+ "eslint-plugin-prettier": "^5.0.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "formik": "^2.4.3",
+ "husky": "^8.0.3",
+ "identity-obj-proxy": "^3.0.0",
+ "jest": "^29.6.2",
+ "jest-environment-jsdom": "^29.6.2",
+ "jest-svg-transformer": "^1.0.0",
+ "lint-staged": "^13.2.3",
+ "prettier": "^3.0.0",
+ "rollup-plugin-polyfill-node": "^0.12.0",
+ "sass": "^1.64.2",
+ "stream-browserify": "^3.0.0",
+ "ts-jest": "^29.1.1",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5",
+ "vite-plugin-checker": "^0.6.1"
+ }
+ },
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
+ "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
+ "dev": true
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+ "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+ "dependencies": {
+ "@babel/highlight": "^7.22.13",
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/compat-data": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz",
+ "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.22.11",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz",
+ "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.22.10",
+ "@babel/generator": "^7.22.10",
+ "@babel/helper-compilation-targets": "^7.22.10",
+ "@babel/helper-module-transforms": "^7.22.9",
+ "@babel/helpers": "^7.22.11",
+ "@babel/parser": "^7.22.11",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.11",
+ "@babel/types": "^7.22.11",
+ "convert-source-map": "^1.7.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/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
+ "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
+ "dependencies": {
+ "@babel/types": "^7.22.10",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.22.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz",
+ "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==",
+ "dependencies": {
+ "@babel/compat-data": "^7.22.9",
+ "@babel/helper-validator-option": "^7.22.5",
+ "browserslist": "^4.21.9",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
+ "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
+ "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
+ "dependencies": {
+ "@babel/template": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
+ "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz",
+ "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==",
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-validator-identifier": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+ "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+ "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+ "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
+ "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
+ "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.22.11",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz",
+ "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==",
+ "dependencies": {
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.11",
+ "@babel/types": "^7.22.11"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+ "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/parser": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz",
+ "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
+ "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz",
+ "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz",
+ "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz",
+ "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.22.11",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
+ "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
+ "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
+ "dependencies": {
+ "@babel/code-frame": "^7.22.5",
+ "@babel/parser": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.22.11",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz",
+ "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==",
+ "dependencies": {
+ "@babel/code-frame": "^7.22.10",
+ "@babel/generator": "^7.22.10",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.22.11",
+ "@babel/types": "^7.22.11",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.22.11",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
+ "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
+ "node_modules/@commercetools/api-request-builder": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@commercetools/api-request-builder/-/api-request-builder-6.0.0.tgz",
+ "integrity": "sha512-4QhfpWnW161XaRs/2o9h4ZVr21R31vYNHmpKbDh2Dp+lFR9IqdwiafP0fqfLzI+Xg6vTrA2s5coyxkh0qj7Gng==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@commercetools/platform-sdk": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@commercetools/platform-sdk/-/platform-sdk-5.0.0.tgz",
+ "integrity": "sha512-JUiZjPngxjoD2flAxcGfAwCsn4I1k9SKcZzfmphSjYetnuGBeH7RXQj8B45NeLn579vs9/WUc729UeQG/FjVvw==",
+ "dependencies": {
+ "@commercetools/sdk-client-v2": "^2.1.6",
+ "@commercetools/sdk-middleware-auth": "^7.0.0",
+ "@commercetools/sdk-middleware-http": "^7.0.0",
+ "@commercetools/sdk-middleware-logger": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@commercetools/sdk-client-v2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@commercetools/sdk-client-v2/-/sdk-client-v2-2.2.0.tgz",
+ "integrity": "sha512-/j/hINnuh3gdBeMrHUvuSZo+8nkckCHK3Nu6m3/ZkUqVkamRheU5ve/DlALvYpjs2XS2BdWTs1+Wsyyz1+x6iQ==",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "node-fetch": "^2.6.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@commercetools/sdk-middleware-auth": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@commercetools/sdk-middleware-auth/-/sdk-middleware-auth-7.0.1.tgz",
+ "integrity": "sha512-XLRm+o3Yd0mkVoyzsOA98PUu0U0ajQdBHMhZ8N2XMOtL4OY8zsgT8ap5JneXV8zWZNiwIYYAYoUDwBlLZh2lAQ==",
+ "dependencies": {
+ "node-fetch": "^2.6.7"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@commercetools/sdk-middleware-http": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/@commercetools/sdk-middleware-http/-/sdk-middleware-http-7.0.4.tgz",
+ "integrity": "sha512-YpBDTA1NqjfwqPxrll6FzDc0A7hLSRwd9OLGMlPcvUG2Je1ks8Pe34pLdPqz7jgdURwfFXRBmXxPhezDzMbnZA==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@commercetools/sdk-middleware-logger": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@commercetools/sdk-middleware-logger/-/sdk-middleware-logger-3.0.0.tgz",
+ "integrity": "sha512-DhMXAA2yIch/AaGxy7st85Z1HFmeLtHWGkr9z5rX4xKjan4PHGB/IE5saAR+SNGHhs6+1Lp8vZEHDo3tFqVLmg==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@esbuild-plugins/node-globals-polyfill": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz",
+ "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==",
+ "dev": true,
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/@esbuild-plugins/node-modules-polyfill": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz",
+ "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0",
+ "rollup-plugin-node-polyfills": "^0.2.1"
+ },
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "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.8.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz",
+ "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
+ "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
+ "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.21.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
+ "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz",
+ "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
+ "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.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": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.4.tgz",
+ "integrity": "sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/console/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/@jest/core": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.4.tgz",
+ "integrity": "sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.6.4",
+ "@jest/reporters": "^29.6.4",
+ "@jest/test-result": "^29.6.4",
+ "@jest/transform": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.6.3",
+ "jest-config": "^29.6.4",
+ "jest-haste-map": "^29.6.4",
+ "jest-message-util": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.6.4",
+ "jest-resolve-dependencies": "^29.6.4",
+ "jest-runner": "^29.6.4",
+ "jest-runtime": "^29.6.4",
+ "jest-snapshot": "^29.6.4",
+ "jest-util": "^29.6.3",
+ "jest-validate": "^29.6.3",
+ "jest-watcher": "^29.6.4",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.6.3",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/core/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/@jest/core/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@jest/core/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.4.tgz",
+ "integrity": "sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/fake-timers": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.4.tgz",
+ "integrity": "sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.6.4",
+ "jest-snapshot": "^29.6.4"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz",
+ "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.4.tgz",
+ "integrity": "sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.6.3",
+ "jest-mock": "^29.6.3",
+ "jest-util": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.4.tgz",
+ "integrity": "sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.6.4",
+ "@jest/expect": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.4.tgz",
+ "integrity": "sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g==",
+ "dev": true,
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.6.4",
+ "@jest/test-result": "^29.6.4",
+ "@jest/transform": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "jest-worker": "^29.6.4",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/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/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.4.tgz",
+ "integrity": "sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz",
+ "integrity": "sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.6.4",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.6.4",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.4.tgz",
+ "integrity": "sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.6.4",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform/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/@jest/transform/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/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types/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/@jridgewell/gen-mapping": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+ "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
+ "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.19",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
+ "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "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/@pkgr/utils": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
+ "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "fast-glob": "^3.3.0",
+ "is-glob": "^4.0.3",
+ "open": "^9.1.0",
+ "picocolors": "^1.0.0",
+ "tslib": "^2.6.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "1.9.5",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
+ "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
+ "dependencies": {
+ "immer": "^9.0.21",
+ "redux": "^4.2.1",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.8"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.0.2"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz",
+ "integrity": "sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rollup/plugin-inject": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.3.tgz",
+ "integrity": "sha512-411QlbL+z2yXpRWFXSmw/teQRMkXcAAC8aYTemc15gwJRpvEVDQwoe+N/HTFD8RFG8+88Bme9DK2V9CVm7hJdA==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.27.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-inject/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/@rollup/plugin-inject/node_modules/magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz",
+ "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
+ "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-7.0.0.tgz",
+ "integrity": "sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-7.0.0.tgz",
+ "integrity": "sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-7.0.0.tgz",
+ "integrity": "sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-7.0.0.tgz",
+ "integrity": "sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-7.0.0.tgz",
+ "integrity": "sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-7.0.0.tgz",
+ "integrity": "sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-7.0.0.tgz",
+ "integrity": "sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-svg-component": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-7.0.0.tgz",
+ "integrity": "sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-preset": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-7.0.0.tgz",
+ "integrity": "sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==",
+ "dependencies": {
+ "@svgr/babel-plugin-add-jsx-attribute": "^7.0.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "^7.0.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "^7.0.0",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "^7.0.0",
+ "@svgr/babel-plugin-svg-dynamic-title": "^7.0.0",
+ "@svgr/babel-plugin-svg-em-dimensions": "^7.0.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "^7.0.0",
+ "@svgr/babel-plugin-transform-svg-component": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/core": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-7.0.0.tgz",
+ "integrity": "sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "^7.0.0",
+ "camelcase": "^6.2.0",
+ "cosmiconfig": "^8.1.3"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/core/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-7.0.0.tgz",
+ "integrity": "sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==",
+ "dependencies": {
+ "@babel/types": "^7.21.3",
+ "entities": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/plugin-jsx": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-7.0.0.tgz",
+ "integrity": "sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "^7.0.0",
+ "@svgr/hast-util-to-babel-ast": "^7.0.0",
+ "svg-parser": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz",
+ "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.1.3",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+ "dev": true,
+ "dependencies": {
+ "deep-equal": "^2.0.5"
+ }
+ },
+ "node_modules/@testing-library/dom/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/@testing-library/jest-dom": {
+ "version": "5.17.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
+ "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==",
+ "dev": true,
+ "dependencies": {
+ "@adobe/css-tools": "^4.0.1",
+ "@babel/runtime": "^7.9.2",
+ "@types/testing-library__jest-dom": "^5.9.1",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.5.6",
+ "lodash": "^4.17.15",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz",
+ "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^9.0.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.4.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz",
+ "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
+ "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==",
+ "dev": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz",
+ "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz",
+ "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz",
+ "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz",
+ "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
+ },
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
+ "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+ "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+ "dev": true
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
+ "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "29.5.4",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.4.tgz",
+ "integrity": "sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.0.0",
+ "pretty-format": "^29.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@types/jest/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/@types/jsdom": {
+ "version": "20.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+ "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.12",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
+ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.5.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz",
+ "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==",
+ "dev": true
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.2.21",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
+ "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.2.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
+ "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.3",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.1",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz",
+ "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==",
+ "dev": true
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
+ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
+ "dev": true
+ },
+ "node_modules/@types/testing-library__jest-dom": {
+ "version": "5.14.9",
+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
+ "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==",
+ "dev": true,
+ "dependencies": {
+ "@types/jest": "*"
+ }
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
+ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
+ "dev": true
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.24",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
+ "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.0",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
+ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz",
+ "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.5.1",
+ "@typescript-eslint/scope-manager": "6.5.0",
+ "@typescript-eslint/type-utils": "6.5.0",
+ "@typescript-eslint/utils": "6.5.0",
+ "@typescript-eslint/visitor-keys": "6.5.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz",
+ "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "6.5.0",
+ "@typescript-eslint/types": "6.5.0",
+ "@typescript-eslint/typescript-estree": "6.5.0",
+ "@typescript-eslint/visitor-keys": "6.5.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz",
+ "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.5.0",
+ "@typescript-eslint/visitor-keys": "6.5.0"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz",
+ "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.5.0",
+ "@typescript-eslint/utils": "6.5.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz",
+ "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==",
+ "dev": true,
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz",
+ "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.5.0",
+ "@typescript-eslint/visitor-keys": "6.5.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz",
+ "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "6.5.0",
+ "@typescript-eslint/types": "6.5.0",
+ "@typescript-eslint/typescript-estree": "6.5.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz",
+ "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.5.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz",
+ "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.22.9",
+ "@babel/plugin-transform-react-jsx-self": "^7.22.5",
+ "@babel/plugin-transform-react-jsx-source": "^7.22.5",
+ "react-refresh": "^0.14.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0"
+ }
+ },
+ "node_modules/abab": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+ "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
+ "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/acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.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-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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": "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/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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=="
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/babel-jest": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.4.tgz",
+ "integrity": "sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/transform": "^29.6.4",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-jest/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/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/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/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz",
+ "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.8.3",
+ "@babel/plugin-syntax-import-meta": "^7.8.3",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.8.3",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-top-level-await": "^7.8.3"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "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/big-integer": {
+ "version": "1.6.51",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
+ "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bplist-parser": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
+ "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
+ "dev": true,
+ "dependencies": {
+ "big-integer": "^1.6.44"
+ },
+ "engines": {
+ "node": ">= 5.10.0"
+ }
+ },
+ "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/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.10",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
+ "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
+ "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.30001517",
+ "electron-to-chromium": "^1.4.477",
+ "node-releases": "^2.0.13",
+ "update-browserslist-db": "^1.0.11"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "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": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
+ "node_modules/bundle-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
+ "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
+ "dev": true,
+ "dependencies": {
+ "run-applescript": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "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/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001524",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz",
+ "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==",
+ "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": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
+ "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
+ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==",
+ "dev": true
+ },
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
+ "node_modules/cli-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+ "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "dev": true,
+ "dependencies": {
+ "restore-cursor": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
+ "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
+ "dev": true,
+ "dependencies": {
+ "slice-ansi": "^5.0.0",
+ "string-width": "^5.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "dev": true
+ },
+ "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/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/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
+ "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "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": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
+ "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
+ "dependencies": {
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ }
+ },
+ "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/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
+ "node_modules/cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+ "dev": true
+ },
+ "node_modules/cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "dev": true,
+ "dependencies": {
+ "cssom": "~0.3.6"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cssstyle/node_modules/cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+ "dev": true
+ },
+ "node_modules/csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ },
+ "node_modules/data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "dev": true,
+ "dependencies": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+ "dev": true
+ },
+ "node_modules/dedent": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
+ "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
+ "dev": true,
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-equal": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz",
+ "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.2",
+ "es-get-iterator": "^1.1.3",
+ "get-intrinsic": "^1.2.1",
+ "is-arguments": "^1.1.1",
+ "is-array-buffer": "^3.0.2",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.0",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.9"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/default-browser": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
+ "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
+ "dev": true,
+ "dependencies": {
+ "bundle-name": "^3.0.0",
+ "default-browser-id": "^3.0.0",
+ "execa": "^7.1.1",
+ "titleize": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
+ "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
+ "dev": true,
+ "dependencies": {
+ "bplist-parser": "^0.2.0",
+ "untildify": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser/node_modules/execa": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
+ "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.1",
+ "human-signals": "^4.3.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^3.0.7",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/default-browser/node_modules/human-signals": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+ "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/default-browser/node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser/node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser/node_modules/npm-run-path": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+ "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser/node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser/node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "dependencies": {
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true
+ },
+ "node_modules/domexception": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+ "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+ "dev": true,
+ "dependencies": {
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.504",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.504.tgz",
+ "integrity": "sha512-cSMwIAd8yUh54VwitVRVvHK66QqHWE39C3DRj8SWiXitEpVSY3wNPD9y1pxQtLIi4w3UdzF9klLsmuPshz09DQ=="
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-get-iterator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "is-arguments": "^1.1.1",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.7",
+ "isarray": "^2.0.5",
+ "stop-iteration-iterator": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz",
+ "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.2",
+ "@eslint/js": "8.48.0",
+ "@humanwhocodes/config-array": "^0.11.10",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "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-config-prettier": {
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
+ "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz",
+ "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.8.5"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
+ "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
+ "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.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz",
+ "integrity": "sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=7"
+ }
+ },
+ "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/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/globals": {
+ "version": "13.21.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
+ "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/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/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/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "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/estree-walker": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
+ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
+ "dev": true
+ },
+ "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/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "dev": true
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz",
+ "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/expect-utils": "^29.6.4",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.6.4",
+ "jest-message-util": "^29.6.3",
+ "jest-util": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.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-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "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/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz",
+ "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.7",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": 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==",
+ "dev": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formik": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.3.tgz",
+ "integrity": "sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://opencollective.com/formik"
+ }
+ ],
+ "dependencies": {
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
+ "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fs-extra/node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "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==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+ "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "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/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "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/harmony-reflect": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
+ "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "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": "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/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "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.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/husky": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
+ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
+ "dev": true,
+ "bin": {
+ "husky": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/identity-obj-proxy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
+ "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==",
+ "dev": true,
+ "dependencies": {
+ "harmony-reflect": "^1.4.6"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "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/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immer": {
+ "version": "9.0.21",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
+ "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
+ "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
+ "dev": true
+ },
+ "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/import-local": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
+ "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
+ "dev": true,
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "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/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "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==",
+ "dev": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+ "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-array-buffer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "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=="
+ },
+ "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-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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.13.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
+ "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "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-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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-fullwidth-code-point": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
+ "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "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-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
+ "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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.12",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
+ "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
+ "dev": true,
+ "dependencies": {
+ "which-typed-array": "^1.1.11"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
+ "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
+ "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-wsl/node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/istanbul-lib-coverage": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
+ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz",
+ "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz",
+ "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==",
+ "dev": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz",
+ "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.6.4"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz",
+ "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.6.3",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.4.tgz",
+ "integrity": "sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.6.4",
+ "@jest/expect": "^29.6.4",
+ "@jest/test-result": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.6.3",
+ "jest-matcher-utils": "^29.6.4",
+ "jest-message-util": "^29.6.3",
+ "jest-runtime": "^29.6.4",
+ "jest-snapshot": "^29.6.4",
+ "jest-util": "^29.6.3",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.6.3",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus/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/jest-circus/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-circus/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-cli": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.4.tgz",
+ "integrity": "sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.6.4",
+ "@jest/test-result": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.6.4",
+ "jest-util": "^29.6.3",
+ "jest-validate": "^29.6.3",
+ "prompts": "^2.0.1",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-cli/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/jest-config": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.4.tgz",
+ "integrity": "sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.6.4",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.6.4",
+ "jest-environment-node": "^29.6.4",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.6.4",
+ "jest-runner": "^29.6.4",
+ "jest-util": "^29.6.3",
+ "jest-validate": "^29.6.3",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.6.3",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config/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/jest-config/node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jest-config/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-config/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-diff": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz",
+ "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-diff/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/jest-diff/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-diff/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz",
+ "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==",
+ "dev": true,
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz",
+ "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "pretty-format": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each/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/jest-each/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-each/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-environment-jsdom": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.4.tgz",
+ "integrity": "sha512-K6wfgUJ16DoMs02JYFid9lOsqfpoVtyJxpRlnTxUHzvZWBnnh2VNGRB9EC1Cro96TQdq5TtSjb3qUjNaJP9IyA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.6.4",
+ "@jest/fake-timers": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/jsdom": "^20.0.0",
+ "@types/node": "*",
+ "jest-mock": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "jsdom": "^20.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.4.tgz",
+ "integrity": "sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.6.4",
+ "@jest/fake-timers": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.6.3",
+ "jest-util": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.4.tgz",
+ "integrity": "sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "jest-worker": "^29.6.4",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz",
+ "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz",
+ "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.6.4",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/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/jest-matcher-utils/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz",
+ "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.6.3",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util/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/jest-message-util/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-mock": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz",
+ "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.4.tgz",
+ "integrity": "sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.6.4",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.6.3",
+ "jest-validate": "^29.6.3",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz",
+ "integrity": "sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA==",
+ "dev": true,
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.6.4"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve/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/jest-runner": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.4.tgz",
+ "integrity": "sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.6.4",
+ "@jest/environment": "^29.6.4",
+ "@jest/test-result": "^29.6.4",
+ "@jest/transform": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.6.3",
+ "jest-environment-node": "^29.6.4",
+ "jest-haste-map": "^29.6.4",
+ "jest-leak-detector": "^29.6.3",
+ "jest-message-util": "^29.6.3",
+ "jest-resolve": "^29.6.4",
+ "jest-runtime": "^29.6.4",
+ "jest-util": "^29.6.3",
+ "jest-watcher": "^29.6.4",
+ "jest-worker": "^29.6.4",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner/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/jest-runtime": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.4.tgz",
+ "integrity": "sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.6.4",
+ "@jest/fake-timers": "^29.6.4",
+ "@jest/globals": "^29.6.4",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.6.4",
+ "@jest/transform": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.6.4",
+ "jest-message-util": "^29.6.3",
+ "jest-mock": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.6.4",
+ "jest-snapshot": "^29.6.4",
+ "jest-util": "^29.6.3",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime/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/jest-snapshot": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.4.tgz",
+ "integrity": "sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.6.4",
+ "@jest/transform": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.6.4",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.6.4",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.6.4",
+ "jest-message-util": "^29.6.3",
+ "jest-util": "^29.6.3",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.6.3",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/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/jest-snapshot/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-svg-transformer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/jest-svg-transformer/-/jest-svg-transformer-1.0.0.tgz",
+ "integrity": "sha512-+kD21VthJFHIbI3DZRz+jo4sBOSR1qWEMXhVC28owRMqC5nA+zEiJrHOlj+EqQIztYMouRc1dIjE8SJfFPJUXA==",
+ "dev": true,
+ "peerDependencies": {
+ "jest": ">22",
+ "react": ">=16"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz",
+ "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-util/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/jest-validate": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz",
+ "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-validate/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/jest-validate/node_modules/pretty-format": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz",
+ "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-validate/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.4.tgz",
+ "integrity": "sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.6.4",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.6.3",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-watcher/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/jest-worker": {
+ "version": "29.6.4",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.4.tgz",
+ "integrity": "sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.6.3",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=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==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "dev": true,
+ "dependencies": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "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=="
+ },
+ "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==",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonfile/node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz",
+ "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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=="
+ },
+ "node_modules/lint-staged": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz",
+ "integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "5.3.0",
+ "commander": "11.0.0",
+ "debug": "4.3.4",
+ "execa": "7.2.0",
+ "lilconfig": "2.1.0",
+ "listr2": "6.6.1",
+ "micromatch": "4.0.5",
+ "pidtree": "0.6.0",
+ "string-argv": "0.3.2",
+ "yaml": "2.3.1"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": "^16.14.0 || >=18.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "dev": true,
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/execa": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
+ "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.1",
+ "human-signals": "^4.3.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^3.0.7",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/human-signals": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+ "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/lint-staged/node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/npm-run-path": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+ "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz",
+ "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==",
+ "dev": true,
+ "dependencies": {
+ "cli-truncate": "^3.1.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^5.0.1",
+ "rfdc": "^1.3.0",
+ "wrap-ansi": "^8.1.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "peerDependencies": {
+ "enquirer": ">= 2.3.0 < 3"
+ },
+ "peerDependenciesMeta": {
+ "enquirer": {
+ "optional": true
+ }
+ }
+ },
+ "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": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "dev": true
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true
+ },
+ "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/lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==",
+ "dev": true
+ },
+ "node_modules/log-update": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz",
+ "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==",
+ "dev": true,
+ "dependencies": {
+ "ansi-escapes": "^5.0.0",
+ "cli-cursor": "^4.0.0",
+ "slice-ansi": "^5.0.0",
+ "strip-ansi": "^7.0.1",
+ "wrap-ansi": "^8.0.1"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-escapes": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz",
+ "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/type-fest": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/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==",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "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-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-fetch/node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/node-fetch/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/node-fetch/node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
+ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
+ "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
+ "dev": true
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-is": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
+ "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "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/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+ "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+ "dev": true,
+ "dependencies": {
+ "default-browser": "^4.0.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "dev": true,
+ "dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0"
+ },
+ "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/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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==",
+ "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/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "dev": true,
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true
+ },
+ "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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pidtree": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
+ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+ "dev": true,
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
+ "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
+ "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.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "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/prettier": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
+ "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/psl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
+ "dev": true
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz",
+ "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ]
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
+ "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.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==",
+ "dev": true
+ },
+ "node_modules/react-infinite-scroll-component": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
+ "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
+ "dependencies": {
+ "throttle-debounce": "^2.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0"
+ }
+ },
+ "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-redux": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
+ "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8 || ^17.0 || ^18.0",
+ "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0",
+ "react-native": ">=0.59",
+ "redux": "^4 || ^5.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
+ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz",
+ "integrity": "sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==",
+ "dependencies": {
+ "@remix-run/router": "1.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz",
+ "integrity": "sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==",
+ "dependencies": {
+ "@remix-run/router": "1.8.0",
+ "react-router": "6.15.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "node_modules/redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
+ "peerDependencies": {
+ "redux": "^4"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
+ "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
+ "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "functions-have-names": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
+ "node_modules/reselect": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
+ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz",
+ "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==",
+ "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-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/resolve.exports": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
+ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+ "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "dev": true,
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/rfdc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
+ "dev": true
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
+ "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup-plugin-inject": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
+ "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==",
+ "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.",
+ "dev": true,
+ "dependencies": {
+ "estree-walker": "^0.6.1",
+ "magic-string": "^0.25.3",
+ "rollup-pluginutils": "^2.8.1"
+ }
+ },
+ "node_modules/rollup-plugin-node-polyfills": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz",
+ "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==",
+ "dev": true,
+ "dependencies": {
+ "rollup-plugin-inject": "^3.0.0"
+ }
+ },
+ "node_modules/rollup-plugin-polyfill-node": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-polyfill-node/-/rollup-plugin-polyfill-node-0.12.0.tgz",
+ "integrity": "sha512-PWEVfDxLEKt8JX1nZ0NkUAgXpkZMTb85rO/Ru9AQ69wYW8VUCfDgP4CGRXXWYni5wDF0vIeR1UoF3Jmw/Lt3Ug==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/plugin-inject": "^5.0.1"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0"
+ }
+ },
+ "node_modules/rollup-pluginutils": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
+ "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
+ "dev": true,
+ "dependencies": {
+ "estree-walker": "^0.6.1"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
+ "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "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/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/sass": {
+ "version": "1.66.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz",
+ "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.0.0",
+ "is-fullwidth-code-point": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+ "dev": true
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
+ "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
+ "dev": true,
+ "dependencies": {
+ "internal-slot": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/stream-browserify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
+ "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "~2.0.4",
+ "readable-stream": "^3.5.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "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-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "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/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/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==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svg-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
+ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
+ },
+ "node_modules/swiper": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.2.0.tgz",
+ "integrity": "sha512-nktQsOtBInJjr3f5DicxC8eHYGcLXDVIGPSon0QoXRaO6NjKnATCbQ8SZsD3dN1Ph1RH4EhVPwSYCcuDRFWHGQ==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/swiperjs"
+ },
+ {
+ "type": "open_collective",
+ "url": "http://opencollective.com/swiper"
+ }
+ ],
+ "engines": {
+ "node": ">= 4.7.0"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
+ "node_modules/synckit": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
+ "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+ "dev": true,
+ "dependencies": {
+ "@pkgr/utils": "^2.3.1",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/throttle-debounce": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
+ "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
+ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
+ "dev": true
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
+ "dev": true
+ },
+ "node_modules/titleize": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
+ "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
+ "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/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+ "dev": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz",
+ "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.13.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.1.1",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
+ "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==",
+ "dev": true,
+ "dependencies": {
+ "bs-logger": "0.x",
+ "fast-json-stable-stringify": "2.x",
+ "jest-util": "^29.0.0",
+ "json5": "^2.2.3",
+ "lodash.memoize": "4.x",
+ "make-error": "1.x",
+ "semver": "^7.5.3",
+ "yargs-parser": "^21.0.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/types": "^29.0.0",
+ "babel-jest": "^29.0.0",
+ "jest": "^29.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "dev": true
+ },
+ "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-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
+ "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
+ "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.1",
+ "picocolors": "^1.0.0"
+ },
+ "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/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
+ "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "4.4.9",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
+ "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.18.10",
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-checker": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.2.tgz",
+ "integrity": "sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "ansi-escapes": "^4.3.0",
+ "chalk": "^4.1.1",
+ "chokidar": "^3.5.1",
+ "commander": "^8.0.0",
+ "fast-glob": "^3.2.7",
+ "fs-extra": "^11.1.0",
+ "lodash.debounce": "^4.0.8",
+ "lodash.pick": "^4.4.0",
+ "npm-run-path": "^4.0.1",
+ "semver": "^7.5.0",
+ "strip-ansi": "^6.0.0",
+ "tiny-invariant": "^1.1.0",
+ "vscode-languageclient": "^7.0.0",
+ "vscode-languageserver": "^7.0.0",
+ "vscode-languageserver-textdocument": "^1.0.1",
+ "vscode-uri": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "peerDependencies": {
+ "eslint": ">=7",
+ "meow": "^9.0.0",
+ "optionator": "^0.9.1",
+ "stylelint": ">=13",
+ "typescript": "*",
+ "vite": ">=2.0.0",
+ "vls": "*",
+ "vti": "*",
+ "vue-tsc": ">=1.3.9"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ },
+ "meow": {
+ "optional": true
+ },
+ "optionator": {
+ "optional": true
+ },
+ "stylelint": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ },
+ "vls": {
+ "optional": true
+ },
+ "vti": {
+ "optional": true
+ },
+ "vue-tsc": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-checker/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/vite-plugin-checker/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/vite-plugin-svgr": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-3.2.0.tgz",
+ "integrity": "sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.2",
+ "@svgr/core": "^7.0.0",
+ "@svgr/plugin-jsx": "^7.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^2.6.0 || 3 || 4"
+ }
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
+ "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.0.0 || >=10.0.0"
+ }
+ },
+ "node_modules/vscode-languageclient": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz",
+ "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==",
+ "dev": true,
+ "dependencies": {
+ "minimatch": "^3.0.4",
+ "semver": "^7.3.4",
+ "vscode-languageserver-protocol": "3.16.0"
+ },
+ "engines": {
+ "vscode": "^1.52.0"
+ }
+ },
+ "node_modules/vscode-languageserver": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz",
+ "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==",
+ "dev": true,
+ "dependencies": {
+ "vscode-languageserver-protocol": "3.16.0"
+ },
+ "bin": {
+ "installServerIntoExtension": "bin/installServerIntoExtension"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.16.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
+ "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
+ "dev": true,
+ "dependencies": {
+ "vscode-jsonrpc": "6.0.0",
+ "vscode-languageserver-types": "3.16.0"
+ }
+ },
+ "node_modules/vscode-languageserver-textdocument": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz",
+ "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==",
+ "dev": true
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.16.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
+ "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==",
+ "dev": true
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz",
+ "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==",
+ "dev": true
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "dev": true,
+ "dependencies": {
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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-collection": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
+ "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.1",
+ "is-set": "^2.0.1",
+ "is-weakmap": "^2.0.1",
+ "is-weakset": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
+ "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "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/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ },
+ "node_modules/yaml": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
+ "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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..b3a2575
--- /dev/null
+++ b/package.json
@@ -0,0 +1,82 @@
+{
+ "name": "ecommerce",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "lint:fix": "eslint --fix .",
+ "prettier": "prettier --write",
+ "prepare": "husky install",
+ "test": "jest"
+ },
+ "lint-staged": {
+ "*.{ts,tsx,js}": [
+ "prettier --write",
+ "eslint"
+ ]
+ },
+ "dependencies": {
+ "@commercetools/api-request-builder": "^6.0.0",
+ "@commercetools/platform-sdk": "^5.0.0",
+ "@commercetools/sdk-client-v2": "^2.2.0",
+ "@reduxjs/toolkit": "^1.9.5",
+ "classnames": "^2.3.2",
+ "formik": "^2.4.3",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-infinite-scroll-component": "^6.1.0",
+ "react-redux": "^8.1.2",
+ "react-router-dom": "^6.15.0",
+ "swiper": "^10.2.0",
+ "vite-plugin-svgr": "^3.2.0"
+ },
+ "devDependencies": {
+ "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
+ "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
+ "@testing-library/jest-dom": "^5.17.0",
+ "@testing-library/react": "^14.0.0",
+ "@testing-library/user-event": "^14.4.3",
+ "@types/jest": "^29.5.3",
+ "@types/react": "^18.2.21",
+ "@types/react-dom": "^18.2.7",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitejs/plugin-react": "^4.0.3",
+ "buffer": "^6.0.3",
+ "eslint": "^8.45.0",
+ "eslint-config-prettier": "^8.9.0",
+ "eslint-plugin-prettier": "^5.0.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "formik": "^2.4.3",
+ "husky": "^8.0.3",
+ "identity-obj-proxy": "^3.0.0",
+ "jest": "^29.6.2",
+ "jest-environment-jsdom": "^29.6.2",
+ "jest-svg-transformer": "^1.0.0",
+ "lint-staged": "^13.2.3",
+ "prettier": "^3.0.0",
+ "rollup-plugin-polyfill-node": "^0.12.0",
+ "sass": "^1.64.2",
+ "stream-browserify": "^3.0.0",
+ "ts-jest": "^29.1.1",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5",
+ "vite-plugin-checker": "^0.6.1"
+ },
+ "jest": {
+ "preset": "ts-jest",
+ "testEnvironment": "jsdom",
+ "setupFilesAfterEnv": [
+ "/jest-setup.ts"
+ ],
+ "moduleNameMapper": {
+ "\\.(css|less|scss)$": "identity-obj-proxy",
+ "\\.svg$": "jest-svg-transformer"
+ }
+ }
+}
diff --git a/public/fonts/Inter-Bold.ttf b/public/fonts/Inter-Bold.ttf
new file mode 100644
index 0000000..8e82c70
Binary files /dev/null and b/public/fonts/Inter-Bold.ttf differ
diff --git a/public/fonts/Inter-Bold.woff b/public/fonts/Inter-Bold.woff
new file mode 100644
index 0000000..d775cbb
Binary files /dev/null and b/public/fonts/Inter-Bold.woff differ
diff --git a/public/fonts/Inter-Bold.woff2 b/public/fonts/Inter-Bold.woff2
new file mode 100644
index 0000000..2846f29
Binary files /dev/null and b/public/fonts/Inter-Bold.woff2 differ
diff --git a/public/fonts/Inter-Medium.ttf b/public/fonts/Inter-Medium.ttf
new file mode 100644
index 0000000..b53fb1c
Binary files /dev/null and b/public/fonts/Inter-Medium.ttf differ
diff --git a/public/fonts/Inter-Medium.woff b/public/fonts/Inter-Medium.woff
new file mode 100644
index 0000000..d546843
Binary files /dev/null and b/public/fonts/Inter-Medium.woff differ
diff --git a/public/fonts/Inter-Medium.woff2 b/public/fonts/Inter-Medium.woff2
new file mode 100644
index 0000000..f92498a
Binary files /dev/null and b/public/fonts/Inter-Medium.woff2 differ
diff --git a/public/fonts/Inter-Regular.ttf b/public/fonts/Inter-Regular.ttf
new file mode 100644
index 0000000..8d4eebf
Binary files /dev/null and b/public/fonts/Inter-Regular.ttf differ
diff --git a/public/fonts/Inter-Regular.woff b/public/fonts/Inter-Regular.woff
new file mode 100644
index 0000000..62d3a61
Binary files /dev/null and b/public/fonts/Inter-Regular.woff differ
diff --git a/public/fonts/Inter-Regular.woff2 b/public/fonts/Inter-Regular.woff2
new file mode 100644
index 0000000..6c2b689
Binary files /dev/null and b/public/fonts/Inter-Regular.woff2 differ
diff --git a/public/fonts/Inter-SemiBold.otf b/public/fonts/Inter-SemiBold.otf
new file mode 100644
index 0000000..daf4c44
Binary files /dev/null and b/public/fonts/Inter-SemiBold.otf differ
diff --git a/public/fonts/Inter-SemiBold.woff b/public/fonts/Inter-SemiBold.woff
new file mode 100644
index 0000000..a815f43
Binary files /dev/null and b/public/fonts/Inter-SemiBold.woff differ
diff --git a/public/fonts/Inter-SemiBold.woff2 b/public/fonts/Inter-SemiBold.woff2
new file mode 100644
index 0000000..611e90c
Binary files /dev/null and b/public/fonts/Inter-SemiBold.woff2 differ
diff --git a/public/images/bg-404.jpg b/public/images/bg-404.jpg
new file mode 100644
index 0000000..9f0cd4a
Binary files /dev/null and b/public/images/bg-404.jpg differ
diff --git a/public/images/bg-form-2.jpg b/public/images/bg-form-2.jpg
new file mode 100644
index 0000000..fbe6fbb
Binary files /dev/null and b/public/images/bg-form-2.jpg differ
diff --git a/public/images/bg-form.jpg b/public/images/bg-form.jpg
new file mode 100644
index 0000000..7ffec98
Binary files /dev/null and b/public/images/bg-form.jpg differ
diff --git a/public/images/bg-home.jpg b/public/images/bg-home.jpg
new file mode 100644
index 0000000..8a17b68
Binary files /dev/null and b/public/images/bg-home.jpg differ
diff --git a/public/images/bg-profile.jpg b/public/images/bg-profile.jpg
new file mode 100644
index 0000000..a7c4956
Binary files /dev/null and b/public/images/bg-profile.jpg differ
diff --git a/public/images/circles.jpg b/public/images/circles.jpg
new file mode 100644
index 0000000..78c4b5c
Binary files /dev/null and b/public/images/circles.jpg differ
diff --git a/public/images/favic.png b/public/images/favic.png
new file mode 100644
index 0000000..e7f6b69
Binary files /dev/null and b/public/images/favic.png differ
diff --git a/public/images/ph-default.jpg b/public/images/ph-default.jpg
new file mode 100644
index 0000000..9e0e81b
Binary files /dev/null and b/public/images/ph-default.jpg differ
diff --git a/public/images/ph-elena.jpg b/public/images/ph-elena.jpg
new file mode 100644
index 0000000..edac693
Binary files /dev/null and b/public/images/ph-elena.jpg differ
diff --git a/public/images/ph-german.jpg b/public/images/ph-german.jpg
new file mode 100644
index 0000000..b06f9bf
Binary files /dev/null and b/public/images/ph-german.jpg differ
diff --git a/public/images/ph-nastia-2.jpg b/public/images/ph-nastia-2.jpg
new file mode 100644
index 0000000..4acf9bd
Binary files /dev/null and b/public/images/ph-nastia-2.jpg differ
diff --git a/public/images/ph-nastia.jpg b/public/images/ph-nastia.jpg
new file mode 100644
index 0000000..bc9efab
Binary files /dev/null and b/public/images/ph-nastia.jpg differ
diff --git a/public/images/profile-ava.jpg b/public/images/profile-ava.jpg
new file mode 100644
index 0000000..d886ee4
Binary files /dev/null and b/public/images/profile-ava.jpg differ
diff --git a/src/__mocks__/AboutTabsMock.ts b/src/__mocks__/AboutTabsMock.ts
new file mode 100644
index 0000000..e9c95aa
--- /dev/null
+++ b/src/__mocks__/AboutTabsMock.ts
@@ -0,0 +1,12 @@
+import { IAboutDataTabs } from '../types/interfaces';
+
+export const studentAboutUsMock: IAboutDataTabs = {
+ profilePicture: '#',
+ stName: 'Test Name',
+ role: 'testRole',
+ github: 'testGitHub',
+ body: {
+ textAbout: 'This is Test Name',
+ recommendations: ['Contribution 1', 'Contribution 2'],
+ },
+};
diff --git a/src/__mocks__/store/authSliceMock.ts b/src/__mocks__/store/authSliceMock.ts
new file mode 100644
index 0000000..199b4a3
--- /dev/null
+++ b/src/__mocks__/store/authSliceMock.ts
@@ -0,0 +1,17 @@
+import { IAuthSlice } from '../../store/auth/types';
+
+export const initialAuthSliceMock: IAuthSlice = {
+ isAuthorized: false,
+ isNewUser: false,
+ status: 'initial',
+ error: '',
+ userId: '',
+};
+
+export const resultAuthSliceMock: IAuthSlice = {
+ isAuthorized: false,
+ isNewUser: true,
+ status: 'error',
+ error: 'Some error',
+ userId: '',
+};
diff --git a/src/__mocks__/store/cartSliceMock.ts b/src/__mocks__/store/cartSliceMock.ts
new file mode 100644
index 0000000..253430c
--- /dev/null
+++ b/src/__mocks__/store/cartSliceMock.ts
@@ -0,0 +1,41 @@
+import { ICartSlice, IItemCart } from '../../store/cart/types';
+
+export const initialCartSliceMock: ICartSlice = {
+ status: 'initial',
+ error: '',
+ basket: {
+ id: '',
+ version: 0,
+ lineItems: [],
+ totalPrice: 0,
+ totalQuantity: 0,
+ codes: [],
+ },
+ discounts: [],
+};
+
+const lineItemMock: IItemCart = {
+ itemId: 'id',
+ productId: 'prodId',
+ name: 'name',
+ artist: 'artist',
+ image: 'image',
+ quantity: 2,
+ price: 100,
+ discountedPrice: 90,
+ totalPrice: 180,
+};
+
+export const resultCartSliceMock: ICartSlice = {
+ status: 'error',
+ error: 'some error',
+ basket: {
+ id: 'is',
+ version: 1,
+ lineItems: [lineItemMock],
+ totalPrice: 180,
+ totalQuantity: 2,
+ codes: [],
+ },
+ discounts: [],
+};
diff --git a/src/__mocks__/store/catalogSliceMock.ts b/src/__mocks__/store/catalogSliceMock.ts
new file mode 100644
index 0000000..35e54f8
--- /dev/null
+++ b/src/__mocks__/store/catalogSliceMock.ts
@@ -0,0 +1,33 @@
+import { ICatalogSlice } from '../../store/catalog/types';
+import { IProduct } from '../../types/interfaces.ts';
+
+export const initialCatalogSliceMock: ICatalogSlice = {
+ status: 'initial',
+ error: '',
+ productList: [],
+ categories: [],
+ totalProducts: 0,
+};
+
+const productMock: IProduct = {
+ productId: 'id',
+ artist: 'some',
+ title: 'mock',
+ year: '2000',
+ description: 'some',
+ dimensions: '120 * 120',
+ medium: 'oil',
+ size: 'small',
+ images: [],
+ price: 100,
+ discountPrice: 90,
+ color: 'black',
+};
+
+export const resultCatalogSliceMock: ICatalogSlice = {
+ status: 'success',
+ error: '',
+ productList: [productMock],
+ categories: [],
+ totalProducts: 1,
+};
diff --git a/src/__mocks__/store/index.ts b/src/__mocks__/store/index.ts
new file mode 100644
index 0000000..01f97ac
--- /dev/null
+++ b/src/__mocks__/store/index.ts
@@ -0,0 +1,6 @@
+export * from './authSliceMock.ts';
+export * from './cartSliceMock.ts';
+export * from './catalogSliceMock.ts';
+export * from './productSliceMock.ts';
+export * from './userSliceMock.ts';
+export * from './storeMock.ts';
diff --git a/src/__mocks__/store/productSliceMock.ts b/src/__mocks__/store/productSliceMock.ts
new file mode 100644
index 0000000..c523026
--- /dev/null
+++ b/src/__mocks__/store/productSliceMock.ts
@@ -0,0 +1,23 @@
+import { IProductSlice } from '../../store/product/types';
+import { IProduct } from '../../types/interfaces';
+
+export const initialProductMock: IProduct = {
+ productId: '',
+ artist: '',
+ title: '',
+ year: '',
+ description: '',
+ dimensions: '',
+ medium: '',
+ size: '',
+ images: [],
+ price: 0,
+ discountPrice: 0,
+ color: '',
+};
+
+export const initialProductSliceMock: IProductSlice = {
+ product: initialProductMock,
+ status: 'initial',
+ error: '',
+};
diff --git a/src/__mocks__/store/storeMock.ts b/src/__mocks__/store/storeMock.ts
new file mode 100644
index 0000000..637eef0
--- /dev/null
+++ b/src/__mocks__/store/storeMock.ts
@@ -0,0 +1,14 @@
+import { RootState } from '../../store/store';
+import { initialAuthSliceMock } from './authSliceMock';
+import { initialProductSliceMock } from './productSliceMock';
+import { initialCatalogSliceMock } from './catalogSliceMock';
+import { initialUserSliceMock } from './userSliceMock';
+import { initialCartSliceMock } from './cartSliceMock';
+
+export const storeMock: RootState = {
+ auth: initialAuthSliceMock,
+ product: initialProductSliceMock,
+ catalog: initialCatalogSliceMock,
+ user: initialUserSliceMock,
+ cart: initialCartSliceMock,
+};
diff --git a/src/__mocks__/store/userSliceMock.ts b/src/__mocks__/store/userSliceMock.ts
new file mode 100644
index 0000000..02ee7fc
--- /dev/null
+++ b/src/__mocks__/store/userSliceMock.ts
@@ -0,0 +1,37 @@
+import { IUser } from '../../types/interfaces';
+import { IUserSlice } from '../../store/user/types';
+
+export const initialUserMock: IUser = {
+ id: '',
+ version: 0,
+ email: '',
+ firstName: '',
+ lastName: '',
+ password: '',
+ date: '',
+ month: '',
+ year: '',
+ defaultShippingAddressId: '',
+ defaultBillingAddressId: '',
+ shippingAddressIds: [],
+ billingAddressIds: [],
+ addresses: [],
+};
+
+export const initialUserSliceMock: IUserSlice = {
+ user: initialUserMock,
+ status: 'initial',
+ error: '',
+ isSuccess: false,
+ editStatus: 'initial',
+ editError: '',
+};
+
+export const resultUserSliceMock: IUserSlice = {
+ user: initialUserMock,
+ status: 'initial',
+ error: '',
+ isSuccess: false,
+ editStatus: 'initial',
+ editError: '',
+};
diff --git a/src/__mocks__/utils/categoriesMockData.ts b/src/__mocks__/utils/categoriesMockData.ts
new file mode 100644
index 0000000..3c8624e
--- /dev/null
+++ b/src/__mocks__/utils/categoriesMockData.ts
@@ -0,0 +1,66 @@
+import { Category } from '@commercetools/platform-sdk';
+import { ICategoryFilterItem } from '../../components/Filters/types';
+
+export const categoriesMockData: Category[] = [
+ {
+ id: '86247e4a-9981-462b-9799-8e5505bf077b',
+ version: 2,
+ createdAt: '2023-08-29T05:37:33.572Z',
+ lastModifiedAt: '2023-08-29T05:40:31.529Z',
+ key: 'paint',
+ name: {
+ 'en-US': 'Paintings',
+ },
+ slug: {
+ 'en-US': 'paintings',
+ },
+ description: {
+ 'en-US': 'Explore an extensive selection of original paintings for sale or available to rent',
+ },
+ ancestors: [],
+ orderHint: '0.1',
+ assets: [],
+ },
+ {
+ id: 'a148abd4-552a-4ff0-87e4-b67ca25c84e2',
+ version: 5,
+ createdAt: '2023-08-29T05:40:10.841Z',
+ lastModifiedAt: '2023-08-31T11:40:24.651Z',
+ key: 'p-modern',
+ name: {
+ 'en-US': 'Modern',
+ },
+ slug: {
+ 'en-US': 'pop-art',
+ },
+ description: {
+ 'en-US':
+ 'Discover Modern paintings for sale online in Scoop Art’s showcase of some of the best contemporary Modern painters working today.',
+ },
+ ancestors: [
+ {
+ typeId: 'category',
+ id: '86247e4a-9981-462b-9799-8e5505bf077b',
+ },
+ ],
+ parent: {
+ typeId: 'category',
+ id: '86247e4a-9981-462b-9799-8e5505bf077b',
+ },
+ orderHint: '0.2',
+ assets: [],
+ },
+];
+
+export const localCategoriesMock: ICategoryFilterItem[] = [
+ {
+ label: 'Paintings',
+ value: '86247e4a-9981-462b-9799-8e5505bf077b',
+ parent: null,
+ },
+ {
+ label: 'Modern',
+ value: 'a148abd4-552a-4ff0-87e4-b67ca25c84e2',
+ parent: '86247e4a-9981-462b-9799-8e5505bf077b',
+ },
+];
diff --git a/src/__mocks__/utils/extractLocalCartMock.ts b/src/__mocks__/utils/extractLocalCartMock.ts
new file mode 100644
index 0000000..e6677e9
--- /dev/null
+++ b/src/__mocks__/utils/extractLocalCartMock.ts
@@ -0,0 +1,198 @@
+import { Cart } from '@commercetools/platform-sdk';
+import { ICart } from '../../store/cart/types';
+
+export const cartMock: Cart = {
+ id: 'edbf8b7a-aa68-4379-bb1d-9e6ed7d725d2',
+ version: 4,
+ createdAt: '2023-09-15T12:32:24.278Z',
+ lastModifiedAt: '2023-09-15T12:32:24.475Z',
+ lastModifiedBy: {
+ clientId: 'j_yucI-j56zVM3IHDqo7Cfxi',
+ customer: {
+ typeId: 'customer',
+ id: '7a742b9f-8bb9-46cb-867f-03a646a49fbd',
+ },
+ },
+ createdBy: {
+ clientId: 'j_yucI-j56zVM3IHDqo7Cfxi',
+ customer: {
+ typeId: 'customer',
+ id: '7a742b9f-8bb9-46cb-867f-03a646a49fbd',
+ },
+ },
+ customerId: '7a742b9f-8bb9-46cb-867f-03a646a49fbd',
+ lineItems: [
+ {
+ id: '48a0c84f-ce9a-4a0a-aed5-0e10c3fd28d8',
+ productId: 'baf7b9e0-79e2-47f0-bb21-aa6beb7796c5',
+ name: {
+ 'en-US': 'Rest',
+ },
+ productType: {
+ typeId: 'product-type',
+ id: 'a7e4da6b-8b8f-41d5-8d54-61d36425be6a',
+ },
+ productSlug: {
+ 'en-US': 'rest',
+ },
+ variant: {
+ id: 1,
+ sku: 'Pain-Mod-01',
+ prices: [
+ {
+ id: '3f301915-39ac-4109-9aa4-18a730a13be3',
+ value: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 900000,
+ fractionDigits: 2,
+ },
+ country: 'US',
+ },
+ ],
+ images: [
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/bob-nosa-01-1-uolcjeB4.jpg',
+ label: 'bob-nosa-01-1',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/bob-nosa-01-2-roGwMeWz.jpg',
+ label: 'bob-nosa-01-2',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/bob-nosa-01-3-3mZ_n6CE.jpg',
+ label: 'bob-nosa-01-3',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ ],
+ attributes: [
+ {
+ name: 'artist',
+ value: {
+ 'en-US': 'Bob-nosa',
+ },
+ },
+ {
+ name: 'color',
+ value: {
+ 'en-US': 'grey',
+ },
+ },
+ {
+ name: 'size',
+ value: {
+ 'en-US': 'Medium',
+ },
+ },
+ {
+ name: 'year',
+ value: {
+ 'en-US': '2022',
+ },
+ },
+ {
+ name: 'medium',
+ value: {
+ 'en-US': 'Oil on canvas',
+ },
+ },
+ {
+ name: 'dimensions',
+ value: {
+ 'en-US': '180 x 180 cm',
+ },
+ },
+ ],
+ assets: [],
+ },
+ price: {
+ id: '3f301915-39ac-4109-9aa4-18a730a13be3',
+ value: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 900000,
+ fractionDigits: 2,
+ },
+ country: 'US',
+ },
+ quantity: 1,
+ discountedPricePerQuantity: [],
+ perMethodTaxRate: [],
+ addedAt: '2023-09-15T12:32:24.451Z',
+ lastModifiedAt: '2023-09-15T12:32:24.451Z',
+ state: [
+ {
+ quantity: 1,
+ state: {
+ typeId: 'state',
+ id: 'abca45cf-d2f5-496d-8d86-1a620d5407aa',
+ },
+ },
+ ],
+ priceMode: 'Platform',
+ lineItemMode: 'Standard',
+ totalPrice: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 900000,
+ fractionDigits: 2,
+ },
+ taxedPricePortions: [],
+ },
+ ],
+ cartState: 'Active',
+ totalPrice: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 900000,
+ fractionDigits: 2,
+ },
+ country: 'US',
+ shippingMode: 'Single',
+ shipping: [],
+ customLineItems: [],
+ discountCodes: [],
+ directDiscounts: [],
+ inventoryMode: 'None',
+ taxMode: 'Platform',
+ taxRoundingMode: 'HalfEven',
+ taxCalculationMode: 'LineItemLevel',
+ deleteDaysAfterLastModification: 90,
+ refusedGifts: [],
+ origin: 'Customer',
+ itemShippingAddresses: [],
+ totalLineItemQuantity: 1,
+};
+
+export const localCartMock: ICart = {
+ id: 'edbf8b7a-aa68-4379-bb1d-9e6ed7d725d2',
+ version: 4,
+ lineItems: [
+ {
+ itemId: '48a0c84f-ce9a-4a0a-aed5-0e10c3fd28d8',
+ productId: 'baf7b9e0-79e2-47f0-bb21-aa6beb7796c5',
+ name: 'Rest',
+ artist: 'Bob-nosa',
+ image:
+ 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/bob-nosa-01-1-uolcjeB4.jpg',
+ quantity: 1,
+ price: 900000,
+ discountedPrice: 0,
+ totalPrice: 900000,
+ },
+ ],
+ totalPrice: 900000,
+ totalQuantity: 1,
+ codes: [],
+};
diff --git a/src/__mocks__/utils/extractLocalUserMock.ts b/src/__mocks__/utils/extractLocalUserMock.ts
new file mode 100644
index 0000000..23bcc9d
--- /dev/null
+++ b/src/__mocks__/utils/extractLocalUserMock.ts
@@ -0,0 +1,27 @@
+import { Customer } from '@commercetools/platform-sdk';
+
+export const customerObject: Customer = {
+ email: 'test@example.com',
+ password: 'password123',
+ firstName: 'John',
+ lastName: 'Doe',
+ dateOfBirth: '1990-01-01',
+ id: '123',
+ version: 1,
+ addresses: [],
+ createdAt: '2023-01-01',
+ lastModifiedAt: '2023-01-01',
+ isEmailVerified: true,
+ authenticationMode: '',
+};
+
+export const customerWithMissingProperties: Customer = {
+ email: 'test@example.com',
+ id: '123',
+ version: 1,
+ addresses: [],
+ createdAt: '2023-01-01',
+ lastModifiedAt: '2023-01-01',
+ isEmailVerified: true,
+ authenticationMode: '',
+};
diff --git a/src/__mocks__/utils/getMonthIndexMock.ts b/src/__mocks__/utils/getMonthIndexMock.ts
new file mode 100644
index 0000000..a42c64c
--- /dev/null
+++ b/src/__mocks__/utils/getMonthIndexMock.ts
@@ -0,0 +1,16 @@
+export const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+];
+
+export const expectedIndexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
diff --git a/src/__mocks__/utils/getUserAgeMock.ts b/src/__mocks__/utils/getUserAgeMock.ts
new file mode 100644
index 0000000..842bb39
--- /dev/null
+++ b/src/__mocks__/utils/getUserAgeMock.ts
@@ -0,0 +1,5 @@
+export const testCases = [
+ { date: '15', month: 'May', year: '1990', expectedAge: 33 },
+ { date: '1', month: 'September', year: '1995', expectedAge: 28 },
+ { date: '19', month: 'August', year: '2000', expectedAge: 23 },
+];
diff --git a/src/__mocks__/utils/index.ts b/src/__mocks__/utils/index.ts
new file mode 100644
index 0000000..b490d05
--- /dev/null
+++ b/src/__mocks__/utils/index.ts
@@ -0,0 +1,7 @@
+export * from './extractLocalCartMock';
+export * from './categoriesMockData';
+export * from './extractLocalUserMock';
+export * from './getMonthIndexMock';
+export * from './getUserAgeMock';
+export * from './productMockData';
+export * from './splitParagraphsMock';
diff --git a/src/__mocks__/utils/productMockData.ts b/src/__mocks__/utils/productMockData.ts
new file mode 100644
index 0000000..2cd48dc
--- /dev/null
+++ b/src/__mocks__/utils/productMockData.ts
@@ -0,0 +1,293 @@
+import { Product } from '@commercetools/platform-sdk';
+import { IProduct } from '../../types/interfaces.ts';
+
+export const productMockData: Product = {
+ id: 'e755fe7e-855c-4f33-9524-ec25c9583d6c',
+ version: 35,
+ createdAt: '2023-08-31T14:01:28.355Z',
+ lastModifiedAt: '2023-09-01T18:53:05.212Z',
+ productType: {
+ typeId: 'product-type',
+ id: 'a7e4da6b-8b8f-41d5-8d54-61d36425be6a',
+ },
+ masterData: {
+ current: {
+ name: {
+ 'en-US': 'Four Foot Companion (Black)',
+ },
+ description: {
+ 'en-US': 'Some description',
+ },
+ categories: [
+ {
+ typeId: 'category',
+ id: '7c3b2d08-a680-4192-9a13-0ed59f2c5e15',
+ },
+ ],
+ categoryOrderHints: {},
+ slug: {
+ 'en-US': 'four-foot-companion-black',
+ },
+ metaTitle: {
+ 'en-US': '',
+ },
+ metaDescription: {
+ 'en-US': '',
+ },
+ masterVariant: {
+ id: 1,
+ sku: 'Sculp-Pop-02',
+ prices: [
+ {
+ id: 'c8b0f0ad-be68-449c-ac57-cf34b6177a93',
+ value: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 8000000,
+ fractionDigits: 2,
+ },
+ country: 'US',
+ discounted: {
+ value: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 7200000,
+ fractionDigits: 2,
+ },
+ discount: {
+ typeId: 'product-discount',
+ id: 'a03a5274-4eaa-4e10-81d1-02a3cf74e66d',
+ },
+ },
+ },
+ ],
+ images: [
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-3-4JKj0VaT.jpg',
+ label: 'kaws-01-3',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-2-iywrwOAk.jpg',
+ label: 'kaws-01-2',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-1-44-kd6dP.jpg',
+ label: 'kaws-01-1',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-4-PQlIlbhK.jpg',
+ label: 'kaws-01-4',
+ dimensions: {
+ w: 2200,
+ h: 1800,
+ },
+ },
+ ],
+ attributes: [
+ {
+ name: 'artist',
+ value: {
+ 'en-US': 'KAWS',
+ },
+ },
+ {
+ name: 'color',
+ value: {
+ 'en-US': 'black',
+ },
+ },
+ {
+ name: 'size',
+ value: {
+ 'en-US': 'Medium',
+ },
+ },
+ {
+ name: 'year',
+ value: {
+ 'en-US': '2007',
+ },
+ },
+ {
+ name: 'medium',
+ value: {
+ 'en-US': 'Painted Cast Vinyl',
+ },
+ },
+ {
+ name: 'dimensions',
+ value: {
+ 'en-US': '127 × 55.9 × 35.6 cm',
+ },
+ },
+ ],
+ assets: [],
+ },
+ variants: [],
+ searchKeywords: {},
+ },
+ staged: {
+ name: {
+ 'en-US': 'Four Foot Companion (Black)',
+ },
+ description: {
+ 'en-US':
+ 'Brian Donnelly (b. 1974) studied illustration at the School of Visual Arts in New York. Before he achieved success as an artist he worked as a background painter on animated series such as Disney’s 101 Dalmations, and cult shows Daria and Doug. From an early age Donnelly was known for marking buildings in New Jersey and Manhattan with ‘KAWS’, a tag he chose because he liked the way the letters looked together. He soon moved on from this simple tag, however, and developed a unique style that involved adding cartoon-like figures to bus-shelter advertisements. In 1999 KAWS visited Japan after being approached by Bounty Hunter, the cult toy and streetwear brand. He would go on to create his first toy, ‘COMPANION’. Produced in an edition of 500, the toys sold out almost immediately, and COMPANION became a recurring figure in KAWS’s work.',
+ },
+ categories: [
+ {
+ typeId: 'category',
+ id: '7c3b2d08-a680-4192-9a13-0ed59f2c5e15',
+ },
+ ],
+ categoryOrderHints: {},
+ slug: {
+ 'en-US': 'four-foot-companion-black',
+ },
+ metaTitle: {
+ 'en-US': '',
+ },
+ metaDescription: {
+ 'en-US': '',
+ },
+ masterVariant: {
+ id: 1,
+ sku: 'Sculp-Pop-02',
+ prices: [
+ {
+ id: 'c8b0f0ad-be68-449c-ac57-cf34b6177a93',
+ value: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 8000000,
+ fractionDigits: 2,
+ },
+ country: 'US',
+ discounted: {
+ value: {
+ type: 'centPrecision',
+ currencyCode: 'USD',
+ centAmount: 7200000,
+ fractionDigits: 2,
+ },
+ discount: {
+ typeId: 'product-discount',
+ id: 'a03a5274-4eaa-4e10-81d1-02a3cf74e66d',
+ },
+ },
+ },
+ ],
+ images: [
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-3-4JKj0VaT.jpg',
+ label: 'kaws-01-3',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-2-iywrwOAk.jpg',
+ label: 'kaws-01-2',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-1-44-kd6dP.jpg',
+ label: 'kaws-01-1',
+ dimensions: {
+ w: 1800,
+ h: 2200,
+ },
+ },
+ {
+ url: 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-4-PQlIlbhK.jpg',
+ label: 'kaws-01-4',
+ dimensions: {
+ w: 2200,
+ h: 1800,
+ },
+ },
+ ],
+ attributes: [
+ {
+ name: 'artist',
+ value: {
+ 'en-US': 'KAWS',
+ },
+ },
+ {
+ name: 'color',
+ value: {
+ 'en-US': 'black',
+ },
+ },
+ {
+ name: 'size',
+ value: {
+ 'en-US': 'Medium',
+ },
+ },
+ {
+ name: 'year',
+ value: {
+ 'en-US': '2007',
+ },
+ },
+ {
+ name: 'medium',
+ value: {
+ 'en-US': 'Painted Cast Vinyl',
+ },
+ },
+ {
+ name: 'dimensions',
+ value: {
+ 'en-US': '127 × 55.9 × 35.6 cm',
+ },
+ },
+ ],
+ assets: [],
+ },
+ variants: [],
+ searchKeywords: {},
+ },
+ published: true,
+ hasStagedChanges: false,
+ },
+ priceMode: 'Embedded',
+};
+
+export const localProductMock: IProduct = {
+ artist: 'KAWS',
+ title: 'Four Foot Companion (Black)',
+ year: '2007',
+ medium: 'Painted Cast Vinyl',
+ dimensions: '127 × 55.9 × 35.6 cm',
+ color: 'black',
+ size: 'Medium',
+ description: 'Some description',
+ images: [
+ 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-3-4JKj0VaT.jpg',
+ 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-2-iywrwOAk.jpg',
+ 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-1-44-kd6dP.jpg',
+ 'https://f4d041cb597b544d8a51-9f3917450bea295535e6c9414a5b041f.ssl.cf3.rackcdn.com/kaws-01-4-PQlIlbhK.jpg',
+ ],
+ price: 80000,
+ discountPrice: 72000,
+ productId: 'e755fe7e-855c-4f33-9524-ec25c9583d6c',
+};
diff --git a/src/__mocks__/utils/splitParagraphsMock.tsx b/src/__mocks__/utils/splitParagraphsMock.tsx
new file mode 100644
index 0000000..2a556e2
--- /dev/null
+++ b/src/__mocks__/utils/splitParagraphsMock.tsx
@@ -0,0 +1,13 @@
+export const paragraphsStringMock = 'String1.\\n\nString2.\\n\nString3.';
+export const simpleSpan = Some simple string;
+export const spansArray = [
+
+ String1.
+ ,
+
+ String2.
+ ,
+
+ String3.
+ ,
+];
diff --git a/src/__test__/store/auth/selectors/authSelectors.test.ts b/src/__test__/store/auth/selectors/authSelectors.test.ts
new file mode 100644
index 0000000..324450c
--- /dev/null
+++ b/src/__test__/store/auth/selectors/authSelectors.test.ts
@@ -0,0 +1,31 @@
+import {
+ selectAuthError,
+ selectAuthLoadingStatus,
+ selectIsAuthorized,
+ selectIsNewUser,
+} from '../../../../store/auth/selectors';
+import { initialAuthSliceMock, storeMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+
+describe('Test redux auth selectors: ', () => {
+ test('should select isAuthorized user flag from the store', () => {
+ const result = selectIsAuthorized(storeMock);
+ expect(result).toEqual(initialAuthSliceMock.isAuthorized);
+ });
+
+ test('should select loading status from the store', () => {
+ const result = selectAuthLoadingStatus(storeMock);
+ expect(result).toEqual(initialAuthSliceMock.status);
+ });
+
+ test('should select error from the store', () => {
+ const result = selectAuthError(storeMock);
+ expect(result).toEqual(initialAuthSliceMock.error);
+ });
+
+ test('should select isNewUser user flag from the store', () => {
+ const result = selectIsNewUser(storeMock);
+ expect(result).toEqual(initialAuthSliceMock.isNewUser);
+ });
+});
diff --git a/src/__test__/store/auth/slice/authSlice.test.ts b/src/__test__/store/auth/slice/authSlice.test.ts
new file mode 100644
index 0000000..d7f234a
--- /dev/null
+++ b/src/__test__/store/auth/slice/authSlice.test.ts
@@ -0,0 +1,27 @@
+import { Action } from '@reduxjs/toolkit';
+import { authReducer, deleteNotice, resetError } from '../../../../store/auth/slice';
+import { initialAuthState } from '../../../../constant';
+import { resultAuthSliceMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+jest.mock('../../../../services/sdk/auth/methods', () => {});
+
+describe('Test redux auth slice: ', () => {
+ test('should return default state when passed an empty action', () => {
+ const result = authReducer(undefined, { type: '' });
+ expect(result).toEqual(initialAuthState);
+ });
+
+ test('should reset isNewUser to initial value with "deleteNotice" action', () => {
+ const action: Action = { type: deleteNotice.type };
+ const result = authReducer(resultAuthSliceMock, action);
+ expect(result.isNewUser).toBeFalsy();
+ });
+
+ test('should reset error and status to initial values with "resetError" action', () => {
+ const action: Action = { type: resetError.type };
+ const result = authReducer(resultAuthSliceMock, action);
+ expect(result.error).toBe('');
+ expect(result.status).toBe('initial');
+ });
+});
diff --git a/src/__test__/store/cart/selectors/cartSelectors.test.ts b/src/__test__/store/cart/selectors/cartSelectors.test.ts
new file mode 100644
index 0000000..84d5e90
--- /dev/null
+++ b/src/__test__/store/cart/selectors/cartSelectors.test.ts
@@ -0,0 +1,49 @@
+import {
+ selectCart,
+ selectCartData,
+ selectCartError,
+ selectCartLoadingInfo,
+ selectCartLoadingStatus,
+ selectDiscountsCodes,
+} from '../../../../store/cart/selectors';
+import { initialCartSliceMock, storeMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+
+describe('Test redux cart selectors: ', () => {
+ test('should select basket from the store', () => {
+ const result = selectCart(storeMock);
+ expect(result).toEqual(initialCartSliceMock.basket);
+ });
+
+ test('should select cart loading status from the store', () => {
+ const result = selectCartLoadingStatus(storeMock);
+ expect(result).toEqual(initialCartSliceMock.status);
+ });
+
+ test('should select cart error from the store', () => {
+ const result = selectCartError(storeMock);
+ expect(result).toEqual(initialCartSliceMock.error);
+ });
+
+ test('should select cart discounts from the store', () => {
+ const result = selectDiscountsCodes(storeMock);
+ expect(result).toEqual(initialCartSliceMock.discounts);
+ });
+
+ test('should select cart loading status and error from the store', () => {
+ const result = selectCartLoadingInfo(storeMock);
+ expect(result).toEqual({
+ status: initialCartSliceMock.status,
+ error: initialCartSliceMock.error,
+ });
+ });
+
+ test('should select basket and discounts from the store', () => {
+ const result = selectCartData(storeMock);
+ expect(result).toEqual({
+ basket: initialCartSliceMock.basket,
+ discounts: initialCartSliceMock.discounts,
+ });
+ });
+});
diff --git a/src/__test__/store/cart/slice/cartSlice.test.ts b/src/__test__/store/cart/slice/cartSlice.test.ts
new file mode 100644
index 0000000..c939f8f
--- /dev/null
+++ b/src/__test__/store/cart/slice/cartSlice.test.ts
@@ -0,0 +1,27 @@
+import { Action } from '@reduxjs/toolkit';
+import { initialCartSlice } from '../../../../constant';
+import { cartReducer, resetCart, resetCartError } from '../../../../store/cart/slice';
+import { resultCartSliceMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+jest.mock('../../../../services/sdk/cart/methods', () => {});
+
+describe('Test redux cart slice: ', () => {
+ test('should return default state when passed an empty action', () => {
+ const result = cartReducer(undefined, { type: '' });
+ expect(result).toEqual(initialCartSlice);
+ });
+
+ test('should reset basket to initial values with "resetCart" action', () => {
+ const action: Action = { type: resetCart.type };
+ const result = cartReducer(resultCartSliceMock, action);
+ expect(result.basket).toEqual(initialCartSlice.basket);
+ });
+
+ test('should reset basket to initial values with "resetCartError" action', () => {
+ const action: Action = { type: resetCartError.type };
+ const result = cartReducer(resultCartSliceMock, action);
+ expect(result.status).toEqual(initialCartSlice.status);
+ expect(result.error).toEqual(initialCartSlice.error);
+ });
+});
diff --git a/src/__test__/store/catalog/selectors/catalogSelectors.test.ts b/src/__test__/store/catalog/selectors/catalogSelectors.test.ts
new file mode 100644
index 0000000..69aa941
--- /dev/null
+++ b/src/__test__/store/catalog/selectors/catalogSelectors.test.ts
@@ -0,0 +1,31 @@
+import {
+ selectCategories,
+ selectCatalogLoadingStatus,
+ selectCatalogError,
+ selectProductlist,
+} from '../../../../store/catalog/selectors';
+import { initialCatalogSliceMock, storeMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+
+describe('Test redux catalog selectors: ', () => {
+ test('should select categories array from the store', () => {
+ const result = selectCategories(storeMock);
+ expect(result).toEqual(initialCatalogSliceMock.categories);
+ });
+
+ test('should select categories loading status from the store', () => {
+ const result = selectCatalogLoadingStatus(storeMock);
+ expect(result).toEqual(initialCatalogSliceMock.status);
+ });
+
+ test('should select error from the store', () => {
+ const result = selectCatalogError(storeMock);
+ expect(result).toEqual(initialCatalogSliceMock.error);
+ });
+
+ test('should select product list array from the store', () => {
+ const result = selectProductlist(storeMock);
+ expect(result).toEqual(initialCatalogSliceMock.productList);
+ });
+});
diff --git a/src/__test__/store/catalog/slice/catalogSlice.test.ts b/src/__test__/store/catalog/slice/catalogSlice.test.ts
new file mode 100644
index 0000000..6a4947e
--- /dev/null
+++ b/src/__test__/store/catalog/slice/catalogSlice.test.ts
@@ -0,0 +1,21 @@
+import { Action } from '@reduxjs/toolkit';
+import { catalogReducer, resetProductList } from '../../../../store/catalog/slice';
+import { initialProductListSlice } from '../../../../constant';
+import { resultCatalogSliceMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+jest.mock('../../../../services/sdk/catalog/methods', () => {});
+
+describe('Test redux catalog slice: ', () => {
+ test('should return default state when passed an empty action', () => {
+ const result = catalogReducer(undefined, { type: '' });
+ expect(result).toEqual(initialProductListSlice);
+ });
+
+ test('should reset product list to initial values with "resetProductList" action', () => {
+ const action: Action = { type: resetProductList.type };
+ const result = catalogReducer(resultCatalogSliceMock, action);
+ expect(result.totalProducts).toEqual(0);
+ expect(result.productList).toEqual([]);
+ });
+});
diff --git a/src/__test__/store/product/slice/productSlice.test.ts b/src/__test__/store/product/slice/productSlice.test.ts
new file mode 100644
index 0000000..b2787d4
--- /dev/null
+++ b/src/__test__/store/product/slice/productSlice.test.ts
@@ -0,0 +1,12 @@
+import { productReducer } from '../../../../store/product/slice';
+import { initialProductSlice } from '../../../../constant';
+
+jest.mock('../../../../constant/metaData', () => {});
+jest.mock('../../../../services/sdk/product/methods', () => {});
+
+describe('Test redux product slice: ', () => {
+ test('should return default state when passed an empty action', () => {
+ const result = productReducer(undefined, { type: '' });
+ expect(result).toEqual(initialProductSlice);
+ });
+});
diff --git a/src/__test__/store/user/selectors/userSelectors.test.ts b/src/__test__/store/user/selectors/userSelectors.test.ts
new file mode 100644
index 0000000..72294cc
--- /dev/null
+++ b/src/__test__/store/user/selectors/userSelectors.test.ts
@@ -0,0 +1,37 @@
+import {
+ selectUserLoadingStatus,
+ selectUserError,
+ selectIsSuccess,
+ selectEditError,
+ selectEditStatus,
+} from '../../../../store/user/selectors';
+import { initialUserSliceMock, storeMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+
+describe('Test redux user selectors: ', () => {
+ test('should select user loading status from the store', () => {
+ const result = selectUserLoadingStatus(storeMock);
+ expect(result).toEqual(initialUserSliceMock.status);
+ });
+
+ test('should select user error from the store', () => {
+ const result = selectUserError(storeMock);
+ expect(result).toEqual(initialUserSliceMock.error);
+ });
+
+ test('should select isSuccess from the store', () => {
+ const result = selectIsSuccess(storeMock);
+ expect(result).toEqual(initialUserSliceMock.isSuccess);
+ });
+
+ test('should select editError list array from the store', () => {
+ const result = selectEditError(storeMock);
+ expect(result).toEqual(initialUserSliceMock.editError);
+ });
+
+ test('should select editStatus list array from the store', () => {
+ const result = selectEditStatus(storeMock);
+ expect(result).toEqual(initialUserSliceMock.editStatus);
+ });
+});
diff --git a/src/__test__/store/user/slice/userSlice.test.ts b/src/__test__/store/user/slice/userSlice.test.ts
new file mode 100644
index 0000000..8e3dd13
--- /dev/null
+++ b/src/__test__/store/user/slice/userSlice.test.ts
@@ -0,0 +1,28 @@
+import { Action } from '@reduxjs/toolkit';
+import { userReducer, deleteSuccessState, resetEditError } from '../../../../store/user/slice';
+import { initialUserState } from '../../../../constant';
+import { resultUserSliceMock } from '../../../../__mocks__/store';
+
+jest.mock('../../../../constant/metaData', () => {});
+jest.mock('../../../../services/sdk/auth/methods', () => {});
+jest.mock('../../../../services/sdk/customer/methods', () => {});
+
+describe('Test redux user slice: ', () => {
+ test('should return default state when passed an empty action', () => {
+ const result = userReducer(undefined, { type: '' });
+ expect(result).toEqual(initialUserState);
+ });
+
+ test('should set success status to initial value with "deleteSuccessState" action', () => {
+ const action: Action = { type: deleteSuccessState.type };
+ const result = userReducer(resultUserSliceMock, action);
+ expect(result.isSuccess).toBeFalsy();
+ });
+
+ test('should reset error and status to initial values with "resetEditError" action', () => {
+ const action: Action = { type: resetEditError.type };
+ const result = userReducer(resultUserSliceMock, action);
+ expect(result.error).toBe('');
+ expect(result.status).toBe('initial');
+ });
+});
diff --git a/src/__test__/utils/checkError.test.ts b/src/__test__/utils/checkError.test.ts
new file mode 100644
index 0000000..0ce7406
--- /dev/null
+++ b/src/__test__/utils/checkError.test.ts
@@ -0,0 +1,17 @@
+import { checkError } from '../../utils';
+
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../constant', () => 'en-US');
+
+describe('Test checkError function', () => {
+ test('should return error message when error is an instance of Error', () => {
+ const error = new Error('Test error');
+ const result = checkError(error);
+ expect(result).toBe(error.message);
+ });
+
+ test('should return "Something was wrong" when error is not an instance of Error', () => {
+ const result = checkError('Not an error inst');
+ expect(result).toBe('Something was wrong');
+ });
+});
diff --git a/src/__test__/utils/extractLocalCart.test.ts b/src/__test__/utils/extractLocalCart.test.ts
new file mode 100644
index 0000000..c480113
--- /dev/null
+++ b/src/__test__/utils/extractLocalCart.test.ts
@@ -0,0 +1,15 @@
+import { extractLocalCart } from '../../utils/extractLocalCart';
+import { cartMock, localCartMock } from '../../__mocks__/utils';
+
+jest.mock('../../constant', () => {
+ return {
+ LANG_CODE: 'en-US',
+ };
+});
+
+describe('Test extractLocalCart function: ', () => {
+ test('should return the local cart object with only necessary fields', () => {
+ const result = extractLocalCart(cartMock);
+ expect(result).toEqual(localCartMock);
+ });
+});
diff --git a/src/__test__/utils/extractLocalUser.test.ts b/src/__test__/utils/extractLocalUser.test.ts
new file mode 100644
index 0000000..15a438a
--- /dev/null
+++ b/src/__test__/utils/extractLocalUser.test.ts
@@ -0,0 +1,49 @@
+import { customerObject, customerWithMissingProperties } from '../../__mocks__/utils';
+import { extractLocalUser } from '../../utils';
+
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../constant', () => 'en-US');
+
+describe('Test extractLocalUser function', () => {
+ test('should extract user properties from a customer object', () => {
+ const result = extractLocalUser(customerObject);
+
+ expect(result).toEqual({
+ id: '123',
+ version: 1,
+ email: 'test@example.com',
+ firstName: 'John',
+ lastName: 'Doe',
+ password: 'password123',
+ date: '01',
+ month: '01',
+ year: '1990',
+ defaultShippingAddressId: '',
+ defaultBillingAddressId: '',
+ shippingAddressIds: [],
+ billingAddressIds: [],
+ addresses: [],
+ });
+ });
+
+ test('should set default values for missing properties', () => {
+ const result = extractLocalUser(customerWithMissingProperties);
+
+ expect(result).toEqual({
+ id: '123',
+ version: 1,
+ email: 'test@example.com',
+ firstName: '',
+ lastName: '',
+ password: '',
+ date: '',
+ month: '',
+ year: '',
+ defaultShippingAddressId: '',
+ defaultBillingAddressId: '',
+ shippingAddressIds: [],
+ billingAddressIds: [],
+ addresses: [],
+ });
+ });
+});
diff --git a/src/__test__/utils/formatPrice.test.ts b/src/__test__/utils/formatPrice.test.ts
new file mode 100644
index 0000000..622f2e9
--- /dev/null
+++ b/src/__test__/utils/formatPrice.test.ts
@@ -0,0 +1,10 @@
+import { formatPrice } from '../../utils';
+
+jest.mock('../../constant', () => {});
+
+describe('Test formatPrice function', () => {
+ test('should return formatted price', () => {
+ const result = formatPrice(20000);
+ expect(result).toBe('$20,000');
+ });
+});
diff --git a/src/__test__/utils/getCategoryList.test.ts b/src/__test__/utils/getCategoryList.test.ts
new file mode 100644
index 0000000..b726c09
--- /dev/null
+++ b/src/__test__/utils/getCategoryList.test.ts
@@ -0,0 +1,15 @@
+import { getCategoryList } from '../../utils';
+import { categoriesMockData, localCategoriesMock } from '../../__mocks__/utils';
+
+jest.mock('../../constant', () => {
+ return {
+ LANG_CODE: 'en-US',
+ };
+});
+
+describe('Test getCategoryList function: ', () => {
+ test('should return the formatted list of categories', () => {
+ const result = getCategoryList(categoriesMockData);
+ expect(result).toEqual(localCategoriesMock);
+ });
+});
diff --git a/src/__test__/utils/getCurrentData.test.ts b/src/__test__/utils/getCurrentData.test.ts
new file mode 100644
index 0000000..e094b0c
--- /dev/null
+++ b/src/__test__/utils/getCurrentData.test.ts
@@ -0,0 +1,20 @@
+import { getCurrentDate } from '../../utils';
+
+jest.mock('../../constant', () => {});
+
+describe('Test getCurrentDate function', () => {
+ test('should return the current date in the correct format', () => {
+ const currentDate = new Date();
+ const daysOfWeek: string[] = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+ const month: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ const expectedDayShort = daysOfWeek[currentDate.getDay()];
+ const expectedMonth = month[currentDate.getMonth()];
+ const expectedDate = currentDate.getDate();
+ const expectedFormattedDate = `${expectedDayShort}, ${expectedMonth} ${
+ String(expectedDate).length > 1 ? expectedDate : '0' + expectedDate
+ }, ${currentDate.getFullYear()}`;
+ const result = getCurrentDate();
+
+ expect(result).toBe(expectedFormattedDate);
+ });
+});
diff --git a/src/__test__/utils/getMonthIndex.test.ts b/src/__test__/utils/getMonthIndex.test.ts
new file mode 100644
index 0000000..56ced6d
--- /dev/null
+++ b/src/__test__/utils/getMonthIndex.test.ts
@@ -0,0 +1,17 @@
+import { expectedIndexes, months } from '../../__mocks__/utils';
+import { getMonthIndex } from '../../utils';
+
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../constant', () => 'en-US');
+
+describe('Test getMonthIndex function', () => {
+ test('should return the correct month index for each month', () => {
+ for (let i = 0; i < months.length; i++) {
+ const month = months[i];
+ const expectedIndex = expectedIndexes[i] + 1;
+ const result = getMonthIndex(month);
+
+ expect(result).toBe(expectedIndex);
+ }
+ });
+});
diff --git a/src/__test__/utils/getUserAge.test.ts b/src/__test__/utils/getUserAge.test.ts
new file mode 100644
index 0000000..2251cf4
--- /dev/null
+++ b/src/__test__/utils/getUserAge.test.ts
@@ -0,0 +1,11 @@
+import { testCases } from '../../__mocks__/utils';
+import { getUserAge } from '../../utils/getUserAge';
+
+describe('Test getUserAge function', () => {
+ test('should calculate the correct user age', () => {
+ for (const testCase of testCases) {
+ const result = getUserAge(new Date('2023-09-15'), testCase.date, testCase.month, testCase.year);
+ expect(result).toBe(testCase.expectedAge);
+ }
+ });
+});
diff --git a/src/__test__/utils/parseProductData.test.ts b/src/__test__/utils/parseProductData.test.ts
new file mode 100644
index 0000000..be6dd5e
--- /dev/null
+++ b/src/__test__/utils/parseProductData.test.ts
@@ -0,0 +1,15 @@
+import { parseProductData } from '../../utils';
+import { localProductMock, productMockData } from '../../__mocks__/utils';
+
+jest.mock('../../constant', () => {
+ return {
+ LANG_CODE: 'en-US',
+ };
+});
+
+describe('Test parseProductData function: ', () => {
+ test('should return formatted product data', () => {
+ const result = parseProductData(productMockData);
+ expect(result).toEqual(localProductMock);
+ });
+});
diff --git a/src/__test__/utils/splitToParagraphs.test.ts b/src/__test__/utils/splitToParagraphs.test.ts
new file mode 100644
index 0000000..35d4b2b
--- /dev/null
+++ b/src/__test__/utils/splitToParagraphs.test.ts
@@ -0,0 +1,16 @@
+import { splitToParagraphs } from '../../utils';
+import { paragraphsStringMock, simpleSpan, spansArray } from '../../__mocks__/utils';
+
+jest.mock('../../constant', () => {});
+
+describe('Test splitToParagraphs function: ', () => {
+ test('should return the string', () => {
+ const result = splitToParagraphs('Some simple string');
+ expect(result).toEqual(simpleSpan);
+ });
+
+ test('should split test to spans', () => {
+ const result = splitToParagraphs(paragraphsStringMock, 'testClass');
+ expect(result).toStrictEqual(spansArray);
+ });
+});
diff --git a/src/__test__/utils/validation/city.test.ts b/src/__test__/utils/validation/city.test.ts
new file mode 100644
index 0000000..9d56cc5
--- /dev/null
+++ b/src/__test__/utils/validation/city.test.ts
@@ -0,0 +1,24 @@
+import { cityValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test City ${validFunc}`, () => {
+ it('Should return empty string for a valid city', () => {
+ const validCity = 'MyCity';
+ const result = cityValidate(validCity);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty city`, () => {
+ const emptyCity = '';
+ const result = cityValidate(emptyCity);
+ expect(result).toBe(errorMsg.city.empty);
+ });
+ it(`${shouldReturnError} a city name containing non-alphabetic characters`, () => {
+ const invalidCity = 'Los Angeles 123';
+ const result = cityValidate(invalidCity);
+ expect(result).toBe(errorMsg.city.invalid);
+ });
+});
diff --git a/src/__test__/utils/validation/dateMY.test.ts b/src/__test__/utils/validation/dateMY.test.ts
new file mode 100644
index 0000000..9393f54
--- /dev/null
+++ b/src/__test__/utils/validation/dateMY.test.ts
@@ -0,0 +1,25 @@
+import { dateMYValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Date month year ${validFunc}`, () => {
+ it('should return empty string for a valid date format and age', () => {
+ const validDate = '20may1993';
+ const result = dateMYValidate(validDate);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an invalid date format`, () => {
+ const invalidDate = '20';
+ const result = dateMYValidate(invalidDate);
+ expect(result).toBe(errorMsg.date.empty);
+ });
+
+ it(`${shouldReturnError} a user under the minimum age`, () => {
+ const youngDate = '20may2022';
+ const result = dateMYValidate(youngDate);
+ expect(result).toBe(errorMsg.date.invalid);
+ });
+});
diff --git a/src/__test__/utils/validation/email.test.ts b/src/__test__/utils/validation/email.test.ts
new file mode 100644
index 0000000..56c2ce5
--- /dev/null
+++ b/src/__test__/utils/validation/email.test.ts
@@ -0,0 +1,25 @@
+import { emailValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Email ${validFunc}`, () => {
+ it('Should return empty string for a valid email', () => {
+ const validEmail = 'test@example.com';
+ const result = emailValidate(validEmail);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty email`, () => {
+ const emptyEmail = '';
+ const result = emailValidate(emptyEmail);
+ expect(result).toBe(errorMsg.email.empty);
+ });
+
+ it(`${shouldReturnError} an invalid email`, () => {
+ const invalidEmail = 'invalid-email';
+ const result = emailValidate(invalidEmail);
+ expect(result).toBe(errorMsg.email.invalid);
+ });
+});
diff --git a/src/__test__/utils/validation/lastName.test.ts b/src/__test__/utils/validation/lastName.test.ts
new file mode 100644
index 0000000..f431ce2
--- /dev/null
+++ b/src/__test__/utils/validation/lastName.test.ts
@@ -0,0 +1,25 @@
+import { lastNameValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Last name ${validFunc}`, () => {
+ it('Should return empty string for a valid Last name', () => {
+ const validLastName = 'g';
+ const result = lastNameValidate(validLastName);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty Last name`, () => {
+ const emptyLastName = '';
+ const result = lastNameValidate(emptyLastName);
+ expect(result).toBe(errorMsg.lastName.empty);
+ });
+
+ it(`${shouldReturnError} an invalid Last name`, () => {
+ const invalidLastName = 'G1';
+ const result = lastNameValidate(invalidLastName);
+ expect(result).toBe(errorMsg.lastName.invalid);
+ });
+});
diff --git a/src/__test__/utils/validation/name.test.ts b/src/__test__/utils/validation/name.test.ts
new file mode 100644
index 0000000..e364f7c
--- /dev/null
+++ b/src/__test__/utils/validation/name.test.ts
@@ -0,0 +1,25 @@
+import { nameValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Name ${validFunc}`, () => {
+ it('Should return empty string for a valid Name', () => {
+ const validName = 'g';
+ const result = nameValidate(validName);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty Name`, () => {
+ const emptyName = '';
+ const result = nameValidate(emptyName);
+ expect(result).toBe(errorMsg.firstName.empty);
+ });
+
+ it(`${shouldReturnError} an invalid Name`, () => {
+ const invalidName = 'G1';
+ const result = nameValidate(invalidName);
+ expect(result).toBe(errorMsg.firstName.invalid);
+ });
+});
diff --git a/src/__test__/utils/validation/password.test.ts b/src/__test__/utils/validation/password.test.ts
new file mode 100644
index 0000000..c0ee0ca
--- /dev/null
+++ b/src/__test__/utils/validation/password.test.ts
@@ -0,0 +1,49 @@
+import { passwordValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Password ${validFunc}`, () => {
+ it('should return empty string for a valid password', () => {
+ const validPassword = 'P@ssw0rd';
+ const result = passwordValidate(validPassword, false);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty password`, () => {
+ const emptyPassword = '';
+ const result = passwordValidate(emptyPassword, false);
+ expect(result).toBe(errorMsg.password.empty);
+ });
+
+ it(`${shouldReturnError} a password with length less than 8`, () => {
+ const shortPassword = 'Short1';
+ const result = passwordValidate(shortPassword, false);
+ expect(result).toBe(errorMsg.password.invalid);
+ });
+
+ it(`${shouldReturnError} a password without an uppercase letter`, () => {
+ const lowercasePassword = 'lowercase1!';
+ const result = passwordValidate(lowercasePassword, false);
+ expect(result).toBe(errorMsg.password.invalid);
+ });
+
+ it(`${shouldReturnError} a password without a lowercase letter`, () => {
+ const uppercasePassword = 'UPPERCASE1!';
+ const result = passwordValidate(uppercasePassword, false);
+ expect(result).toBe(errorMsg.password.invalid);
+ });
+
+ it(`${shouldReturnError} a password without a digit`, () => {
+ const noDigitPassword = 'NoDigit$';
+ const result = passwordValidate(noDigitPassword, false);
+ expect(result).toBe(errorMsg.password.invalid);
+ });
+
+ it(`${shouldReturnError} a password without a special character`, () => {
+ const noSpecialCharacterPassword = 'NoSpecial123';
+ const result = passwordValidate(noSpecialCharacterPassword, false);
+ expect(result).toBe(errorMsg.password.invalid);
+ });
+});
diff --git a/src/__test__/utils/validation/street.test.ts b/src/__test__/utils/validation/street.test.ts
new file mode 100644
index 0000000..6f7c71a
--- /dev/null
+++ b/src/__test__/utils/validation/street.test.ts
@@ -0,0 +1,19 @@
+import { streetValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Street ${validFunc}`, () => {
+ it('Should return empty string for a valid street', () => {
+ const validStreet = '1';
+ const result = streetValidate(validStreet);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty street`, () => {
+ const emptyStreet = '';
+ const result = streetValidate(emptyStreet);
+ expect(result).toBe(errorMsg.street.empty);
+ });
+});
diff --git a/src/__test__/utils/validation/zipCode.test.ts b/src/__test__/utils/validation/zipCode.test.ts
new file mode 100644
index 0000000..f6c0fba
--- /dev/null
+++ b/src/__test__/utils/validation/zipCode.test.ts
@@ -0,0 +1,25 @@
+import { zipCodeValidate } from '../../../utils/validation';
+import { errorMsg } from '../../../constant';
+import { shouldReturnError, validFunc } from '../../variables';
+
+jest.mock('../../../constant/metaData', () => {});
+
+describe(`Test Zip Code ${validFunc}`, () => {
+ it('should return an empty string for a valid zip code', () => {
+ const validZipCode = '12345';
+ const result = zipCodeValidate(validZipCode);
+ expect(result).toBe('');
+ });
+
+ it(`${shouldReturnError} an empty value`, () => {
+ const emptyValue = '';
+ const result = zipCodeValidate(emptyValue);
+ expect(result).toBe(errorMsg.zipCode.empty);
+ });
+
+ it(`${shouldReturnError} an invalid zip code`, () => {
+ const invalidZipCode = '1234';
+ const result = zipCodeValidate(invalidZipCode);
+ expect(result).toBe(errorMsg.zipCode.invalid);
+ });
+});
diff --git a/src/__test__/variables.ts b/src/__test__/variables.ts
new file mode 100644
index 0000000..d78d99c
--- /dev/null
+++ b/src/__test__/variables.ts
@@ -0,0 +1,2 @@
+export const validFunc = 'validation function';
+export const shouldReturnError = 'Should return an error message for';
diff --git a/src/assets/icons/arrow-right.svg b/src/assets/icons/arrow-right.svg
new file mode 100644
index 0000000..2cb3e5e
--- /dev/null
+++ b/src/assets/icons/arrow-right.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/circle.svg b/src/assets/icons/circle.svg
new file mode 100644
index 0000000..d85aa8e
--- /dev/null
+++ b/src/assets/icons/circle.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/ic-minus.svg b/src/assets/icons/ic-minus.svg
new file mode 100644
index 0000000..287109c
--- /dev/null
+++ b/src/assets/icons/ic-minus.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/ic-plus.svg b/src/assets/icons/ic-plus.svg
new file mode 100644
index 0000000..6a0d95a
--- /dev/null
+++ b/src/assets/icons/ic-plus.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/icon-arrow.svg b/src/assets/icons/icon-arrow.svg
new file mode 100644
index 0000000..e2f8033
--- /dev/null
+++ b/src/assets/icons/icon-arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/icon-cart.svg b/src/assets/icons/icon-cart.svg
new file mode 100644
index 0000000..2a49644
--- /dev/null
+++ b/src/assets/icons/icon-cart.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/logo.svg b/src/assets/icons/logo.svg
new file mode 100644
index 0000000..b7cef9d
--- /dev/null
+++ b/src/assets/icons/logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/assets/icons/react.svg b/src/assets/icons/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/src/assets/icons/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/rs_logo.svg b/src/assets/icons/rs_logo.svg
new file mode 100644
index 0000000..915b701
--- /dev/null
+++ b/src/assets/icons/rs_logo.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/components/AboutUs/AboutTabs/AboutTabs.test.tsx b/src/components/AboutUs/AboutTabs/AboutTabs.test.tsx
new file mode 100644
index 0000000..9b1639a
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/AboutTabs.test.tsx
@@ -0,0 +1,12 @@
+import { render } from '@testing-library/react';
+import AboutTabs from './AboutTabs';
+import { studentDataTabs } from '../../../constant/aboutus';
+
+describe('Test AboutTabs component', () => {
+ test('Should render AboutTabs component', () => {
+ const { getByText } = render();
+
+ const aboutTabsElement = getByText(studentDataTabs[0].stName);
+ expect(aboutTabsElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/AboutUs/AboutTabs/AboutTabs.tsx b/src/components/AboutUs/AboutTabs/AboutTabs.tsx
new file mode 100644
index 0000000..c083fd6
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/AboutTabs.tsx
@@ -0,0 +1,30 @@
+import styles from './aboutTabs.module.scss';
+
+import { ReactElement, useState } from 'react';
+import { studentDataTabs } from '../../../constant/aboutus';
+import { Description } from './Description';
+import { Tab } from './Tab';
+
+function AboutTabs(): ReactElement {
+ const [activeTab, setActiveTab] = useState(0);
+
+ const handleTabClick = function (index: number): void {
+ setActiveTab(index);
+ };
+
+ return (
+
+
+ {studentDataTabs.map((student, ind) => {
+ const isActive = activeTab === ind;
+ return (
+ handleTabClick(ind)} />
+ );
+ })}
+
+
+
+ );
+}
+
+export default AboutTabs;
diff --git a/src/components/AboutUs/AboutTabs/Description/Description.test.tsx b/src/components/AboutUs/AboutTabs/Description/Description.test.tsx
new file mode 100644
index 0000000..d0671e7
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Description/Description.test.tsx
@@ -0,0 +1,27 @@
+import { render } from '@testing-library/react';
+import Description from './Description';
+import { studentAboutUsMock } from '../../../../__mocks__/AboutTabsMock';
+
+describe('Test Description component', () => {
+ test('Should render component with student information', () => {
+ const { getByText } = render();
+
+ expect(getByText('About Test')).toBeInTheDocument();
+ expect(getByText('This is Test Name')).toBeInTheDocument();
+ // eslint-disable-next-line prettier/prettier
+ expect(getByText('Test\'s Contribution')).toBeInTheDocument();
+ expect(getByText('Contribution 1')).toBeInTheDocument();
+ expect(getByText('Contribution 2')).toBeInTheDocument();
+ });
+
+ test('Should render mobile component with student information', () => {
+ const { getByText } = render();
+
+ expect(getByText('About Test')).toBeInTheDocument();
+ expect(getByText('This is Test Name')).toBeInTheDocument();
+ // eslint-disable-next-line prettier/prettier
+ expect(getByText('Test\'s Contribution')).toBeInTheDocument();
+ expect(getByText('Contribution 1')).toBeInTheDocument();
+ expect(getByText('Contribution 2')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/AboutUs/AboutTabs/Description/Description.tsx b/src/components/AboutUs/AboutTabs/Description/Description.tsx
new file mode 100644
index 0000000..820a8a9
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Description/Description.tsx
@@ -0,0 +1,37 @@
+import styles from './description.module.scss';
+import { IAboutDataTabs } from '../../../../types/interfaces';
+import { ReactElement } from 'react';
+import classNames from 'classnames';
+
+interface IDescription {
+ student: IAboutDataTabs;
+ isMobile: boolean;
+}
+
+function Description({ student, isMobile }: IDescription): ReactElement {
+ const myName = student.stName.split(' ')[0];
+ const textAbout = student.body.textAbout;
+
+ return (
+
+
About {myName}
+
{textAbout}
+
+
+ {myName}'s Contribution
+
+
+ {student.body.recommendations.map((recc, indx) => {
+ return (
+ -
+ {recc}
+
+ );
+ })}
+
+
+
+ );
+}
+
+export default Description;
diff --git a/src/components/AboutUs/AboutTabs/Description/description.module.scss b/src/components/AboutUs/AboutTabs/Description/description.module.scss
new file mode 100644
index 0000000..f990f98
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Description/description.module.scss
@@ -0,0 +1,128 @@
+@import '../../../../styles/common';
+
+.root {
+ max-width: 80%;
+ margin-bottom: 6rem;
+ font-size: 4rem;
+ line-height: 1.1;
+
+ @media (max-width: 960px) {
+ max-width: 100%;
+ }
+
+ @media (max-width: 650px) {
+ display: none;
+ }
+
+ &__title {
+ font-size: 4rem;
+ margin-bottom: 2rem;
+ }
+
+ &__about {
+ margin-bottom: 6rem;
+ }
+
+ &__contribution {
+ width: calc(66% + 2rem);
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ margin-left: auto;
+
+ @media (max-width: 960px) {
+ padding-right: 0;
+ margin-left: unset;
+ width: 100%;
+ align-items: end;
+ align-items: flex-start;
+ }
+ }
+
+ &__contributionTitle {
+ margin-bottom: 2rem;
+ font-weight: 600;
+ }
+
+ &__item {
+ position: relative;
+ margin-bottom: 0.5rem;
+ padding-left: 3.5rem;
+
+ &::before {
+ content: '';
+ position: absolute;
+ width: 1.5rem;
+ height: 1.5rem;
+ border-radius: 50%;
+ background-color: $primary;
+ top: 1.5rem;
+ left: 0;
+ }
+ }
+}
+
+.bodyMobile {
+ display: none;
+ font-size: 3rem;
+ line-height: 1.2;
+
+ @media (max-width: 650px) {
+ display: flex;
+ flex-direction: column;
+ height: 0;
+ opacity: 0;
+
+ &.isNotHide {
+ opacity: 1;
+ height: 100%;
+ }
+ }
+
+ @media (max-width: 480px) {
+ font-size: 2.4rem;
+ }
+
+ &__title {
+ margin-bottom: 1rem;
+
+ @media (max-width: 650px) {
+ font-size: 3rem;
+ }
+
+ @media (max-width: 480px) {
+ font-size: 2.4rem;
+ }
+ }
+
+ &__about {
+ margin-bottom: 4rem;
+ }
+
+ &__contribution {
+ margin-bottom: 1rem;
+ font-weight: 600;
+ }
+
+ &__item {
+ position: relative;
+ margin-bottom: 0.5rem;
+ padding-left: 2rem;
+
+ &::before {
+ content: '';
+ position: absolute;
+ width: 1rem;
+ height: 1rem;
+ border-radius: 50%;
+ background-color: $primary;
+ top: 1.8rem;
+ left: 0;
+ transform: translateY(-50%);
+
+ @media (max-width: 480px) {
+ top: 1.5rem;
+ }
+ }
+ }
+}
diff --git a/src/components/AboutUs/AboutTabs/Description/index.ts b/src/components/AboutUs/AboutTabs/Description/index.ts
new file mode 100644
index 0000000..c6c8a1e
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Description/index.ts
@@ -0,0 +1,2 @@
+import Description from './Description';
+export { Description };
diff --git a/src/components/AboutUs/AboutTabs/Tab/Tab.test.tsx b/src/components/AboutUs/AboutTabs/Tab/Tab.test.tsx
new file mode 100644
index 0000000..16fd327
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Tab/Tab.test.tsx
@@ -0,0 +1,17 @@
+import { render, screen } from '@testing-library/react';
+import Tab from './Tab';
+import { studentAboutUsMock } from '../../../../__mocks__/AboutTabsMock';
+
+describe('Test Tab component in About-us', () => {
+ test('Should render Tab component', () => {
+ render( {}} />);
+
+ expect(screen.getByText('Test Name')).toBeInTheDocument();
+ expect(screen.getByText('testRole')).toBeInTheDocument();
+ expect(screen.getByText('GitHub')).toBeInTheDocument();
+
+ const image = screen.getByAltText(/Photo of/i);
+ expect(image).toBeInTheDocument();
+ expect(image).toHaveAttribute('src', '#');
+ });
+});
diff --git a/src/components/AboutUs/AboutTabs/Tab/Tab.tsx b/src/components/AboutUs/AboutTabs/Tab/Tab.tsx
new file mode 100644
index 0000000..accd765
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Tab/Tab.tsx
@@ -0,0 +1,49 @@
+import { ReactElement, SyntheticEvent } from 'react';
+import { IAboutDataTabs } from '../../../../types/interfaces';
+
+import styles from './tab.module.scss';
+import classNames from 'classnames';
+import { Description } from '../Description';
+
+const IMG_DEFAULT = '/images/ph-default.jpg';
+
+interface ITab {
+ isActive: boolean;
+ student: IAboutDataTabs;
+ handleTabClick: () => void;
+}
+
+function Tab({ isActive, student, handleTabClick }: ITab): ReactElement {
+ const stName = student.stName.split(' ')[0];
+
+ return (
+
+
+
+

): void => {
+ const target = e.target;
+ if (target instanceof HTMLImageElement) {
+ target.src = IMG_DEFAULT;
+ }
+ }}
+ />
+
+
{student.stName}
+
{student.role}
+
{`About ${stName}`}
+
+
+
+
+ );
+}
+
+export default Tab;
diff --git a/src/components/AboutUs/AboutTabs/Tab/index.ts b/src/components/AboutUs/AboutTabs/Tab/index.ts
new file mode 100644
index 0000000..bd53ab2
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Tab/index.ts
@@ -0,0 +1,2 @@
+import Tab from './Tab';
+export { Tab };
diff --git a/src/components/AboutUs/AboutTabs/Tab/tab.module.scss b/src/components/AboutUs/AboutTabs/Tab/tab.module.scss
new file mode 100644
index 0000000..fffd39c
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/Tab/tab.module.scss
@@ -0,0 +1,178 @@
+@import '../../../../styles/common';
+
+.item {
+ position: relative;
+ flex-grow: 1;
+ margin-bottom: 4rem;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ @media (min-width: 651px) {
+ &:hover {
+ cursor: pointer;
+
+ .item__imgContainer {
+ box-shadow: $background-shadow;
+ }
+
+ .item__tooltip {
+ opacity: 1;
+ }
+
+ .item__title,
+ .item__role {
+ color: $primary;
+ }
+
+ .item__links_github {
+ color: $primary;
+
+ &::after {
+ border-right: 9px solid $primary;
+ }
+ }
+ }
+ }
+
+ @media (max-width: 650px) {
+ cursor: default;
+
+ padding-bottom: 4rem;
+ border-bottom: 1px solid $dark;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ .item__imgContainer {
+ box-shadow: none;
+ cursor: default;
+ }
+
+ .item__tooltip {
+ opacity: 0;
+ }
+
+ .item__title {
+ font-size: 6rem;
+ }
+ }
+
+ &__tooltip {
+ display: inline;
+ padding: 0.5rem 1.2rem;
+ position: absolute;
+ border-radius: 2rem;
+ background-color: $primary;
+ color: $light;
+ font-size: 1.6rem;
+ margin: 2rem;
+
+ opacity: 0;
+ }
+
+ &__studentInfo {
+ display: flex;
+ flex-direction: column;
+
+ &.isactive {
+ color: $primary;
+
+ .item__tooltip {
+ opacity: 1;
+ }
+
+ @media (max-width: 650px) {
+ color: $dark;
+
+ .item__tooltip {
+ opacity: 0;
+ }
+ }
+ }
+ }
+
+ &__imgContainer {
+ position: relative;
+ overflow: hidden;
+ border-radius: 4rem;
+ width: 100%;
+ height: fit-content;
+ margin-bottom: 2rem;
+ background-color: rgba($primary, 0.1);
+
+ transition: all 0.2s;
+ }
+
+ &__img {
+ display: block;
+ width: auto;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ &__title {
+ width: 10%;
+ margin-bottom: 2rem;
+ font-size: 4rem;
+ line-height: 1;
+ }
+
+ &__role {
+ margin-bottom: 0.5rem;
+ }
+
+ &__links {
+ @media (min-width: 651px) {
+ &.isactive {
+ .item__links_github {
+ color: $primary;
+
+ &::after {
+ border-right: 9px solid $primary;
+ }
+ }
+ }
+ }
+
+ &_github {
+ @include font(semibold, p3, $dark);
+ position: relative;
+ display: inline;
+ width: fit-content;
+ font-size: 2rem;
+ text-decoration: none;
+
+ &:hover {
+ color: $primary;
+ text-decoration: underline;
+ text-decoration-thickness: 1.5px;
+ text-underline-offset: 4px;
+
+ &::after {
+ border-right: 9px solid $primary;
+ }
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 7.5rem;
+ top: 52%;
+ transform: translateY(-50%) rotate(180deg);
+ width: 0;
+ height: 0;
+ border-left: 7px solid transparent;
+ border-top: 7px solid transparent;
+ border-bottom: 7px solid transparent;
+ border-right: 9px solid $dark;
+ }
+ }
+
+ @media (max-width: 650px) {
+ margin-bottom: 6rem;
+ }
+ }
+}
diff --git a/src/components/AboutUs/AboutTabs/aboutTabs.module.scss b/src/components/AboutUs/AboutTabs/aboutTabs.module.scss
new file mode 100644
index 0000000..a8f2efe
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/aboutTabs.module.scss
@@ -0,0 +1,28 @@
+@import '../../../styles/common';
+
+.root {
+ margin-bottom: 2rem;
+ border-bottom: 1px solid $dark;
+
+ @media(max-width: 650px) {
+ margin-bottom: 8rem;
+ border-bottom: none;
+ }
+}
+
+.list {
+ display: flex;
+ max-width: 80%;
+ margin-bottom: 6rem;
+ gap: 2rem;
+ border-bottom: 1px solid $dark;
+
+ @media(max-width: 960px) {
+ max-width: 100%;
+ }
+
+ @media(max-width: 650px) {
+ flex-direction: column;
+ margin-bottom: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/components/AboutUs/AboutTabs/index.ts b/src/components/AboutUs/AboutTabs/index.ts
new file mode 100644
index 0000000..d8d1ec2
--- /dev/null
+++ b/src/components/AboutUs/AboutTabs/index.ts
@@ -0,0 +1,2 @@
+import AboutTabs from './AboutTabs';
+export { AboutTabs };
diff --git a/src/components/AboutUs/ContentBlock/ContentBlock.tsx b/src/components/AboutUs/ContentBlock/ContentBlock.tsx
new file mode 100644
index 0000000..a09e9b3
--- /dev/null
+++ b/src/components/AboutUs/ContentBlock/ContentBlock.tsx
@@ -0,0 +1,35 @@
+import { ReactElement } from 'react';
+
+import styles from './contentBlock.module.scss';
+import classnames from 'classnames';
+
+export interface IContentBlock {
+ title?: string;
+ paragraphs?: string[];
+ result?: string;
+ classBase?: string;
+}
+
+function ContentBlock({ title, paragraphs, result, classBase }: IContentBlock): ReactElement {
+ return (
+ <>
+ {title && (
+ {title}
+ )}
+ {paragraphs &&
+ paragraphs.map((content) => (
+
+ {content}
+
+ ))}
+ {result && (
+
+ Result:
+ {result}
+
+ )}
+ >
+ );
+}
+
+export default ContentBlock;
diff --git a/src/components/AboutUs/ContentBlock/contentBlock.module.scss b/src/components/AboutUs/ContentBlock/contentBlock.module.scss
new file mode 100644
index 0000000..09f8450
--- /dev/null
+++ b/src/components/AboutUs/ContentBlock/contentBlock.module.scss
@@ -0,0 +1,45 @@
+@import '../../../styles/common/';
+
+.root__paragraph,
+.root__subtitle {
+ display: inline-block;
+ font-weight: 300;
+ line-height: 1.2;
+ margin-bottom: 2.5rem;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.root__paragraph {
+ span {
+ font-weight: 400;
+ padding: 0 1rem 0.3rem;
+ background-color: rgba($light, 0.3);
+ border-radius: 1.2rem;
+ margin-right: 1rem;
+
+ @media (max-width: 650px) {
+ padding: 0 0.8rem 0.2rem;
+ border-radius: 1rem;
+ }
+ }
+}
+
+.root__subtitle {
+ padding-top: 6rem;
+ font-weight: 500;
+ text-decoration: underline;
+ text-decoration-thickness: 3px;
+ text-underline-offset: 5px;
+
+ @media (max-width: 650px) {
+ padding-top: 4rem;
+ }
+
+ @media (max-width: 480px) {
+ padding-top: 3rem;
+ margin-bottom: 2rem;
+ }
+}
diff --git a/src/components/AboutUs/ContentBlock/index.ts b/src/components/AboutUs/ContentBlock/index.ts
new file mode 100644
index 0000000..0e3760b
--- /dev/null
+++ b/src/components/AboutUs/ContentBlock/index.ts
@@ -0,0 +1,2 @@
+import ContentBlock from './ContentBlock';
+export { ContentBlock };
diff --git a/src/components/AboutUs/RSLogo/RSLogo.tsx b/src/components/AboutUs/RSLogo/RSLogo.tsx
new file mode 100644
index 0000000..93ac7f6
--- /dev/null
+++ b/src/components/AboutUs/RSLogo/RSLogo.tsx
@@ -0,0 +1,25 @@
+import { ReactElement } from 'react';
+import RS_Logo from '../../../assets/icons/rs_logo.svg';
+
+import styles from './rsLogo.module.scss';
+
+const SCHOOL_LINK = 'https://rs.school';
+
+function RSLogo(): ReactElement {
+ return (
+
+ );
+}
+
+export default RSLogo;
diff --git a/src/components/AboutUs/RSLogo/index.ts b/src/components/AboutUs/RSLogo/index.ts
new file mode 100644
index 0000000..618ab1a
--- /dev/null
+++ b/src/components/AboutUs/RSLogo/index.ts
@@ -0,0 +1,2 @@
+import RSLogo from './RSLogo';
+export { RSLogo };
diff --git a/src/components/AboutUs/RSLogo/rsLogo.module.scss b/src/components/AboutUs/RSLogo/rsLogo.module.scss
new file mode 100644
index 0000000..e2fb886
--- /dev/null
+++ b/src/components/AboutUs/RSLogo/rsLogo.module.scss
@@ -0,0 +1,99 @@
+@import '../../../styles/common';
+
+.root {
+ position: relative;
+ width: 100%;
+ height: 40rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 3rem 0;
+
+ @media (max-width: 540px) {
+ height: 36rem;
+ }
+
+ @media (max-width: 480px) {
+ height: 32rem;
+ }
+
+ @media (max-width: 440px) {
+ height: 30rem;
+ }
+
+ &__link {
+ position: relative;
+ max-width: 60rem;
+ width: 100%;
+ filter: drop-shadow(0px 9px 9px rgba($primary, 0.5));
+ transition: all 0.2s;
+
+ &:hover {
+ filter: drop-shadow(0px 9px 9px rgba($primary, 0));
+ }
+
+ svg {
+ width: 45%;
+ transition: all 0.2s;
+ }
+
+ .link {
+ position: relative;
+ width: 100%;
+ height: 12rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: $primary;
+ color: $light-primary;
+ border-radius: 15rem;
+ text-decoration: none;
+
+ transition: all 0.2s;
+
+ .shape {
+ width: 80%;
+ display: flex;
+ position: absolute;
+ z-index: -10;
+
+ &__item {
+ width: 100%;
+ height: 20rem;
+ background-color: $primary;
+ border-radius: 20rem;
+
+ transition: all 0.2s;
+ }
+ }
+
+ &:hover {
+ background-color: $primary-hover;
+
+ .shape__item {
+ background-color: $primary-hover;
+ }
+
+ svg {
+ width: 44.8%;
+ }
+ }
+
+ @media (max-width: 540px) {
+ height: 10rem;
+
+ .shape__item {
+ height: 16rem;
+ }
+ }
+
+ @media (max-width: 440px) {
+ height: 8rem;
+
+ .shape__item {
+ height: 14rem;
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/AboutUs/TeamProcess/TeamProcess.test.tsx b/src/components/AboutUs/TeamProcess/TeamProcess.test.tsx
new file mode 100644
index 0000000..b928095
--- /dev/null
+++ b/src/components/AboutUs/TeamProcess/TeamProcess.test.tsx
@@ -0,0 +1,17 @@
+import { render } from '@testing-library/react';
+import TeamProcess from './TeamProcess';
+import { teamworkText } from '../../../constant/about-teamwork';
+
+describe('Test TeamProcess component', () => {
+ it('Should correctly displays headings and paragraphs.', () => {
+ const { getByText } = render();
+
+ expect(getByText('Teamwork & Process')).toBeInTheDocument();
+ expect(getByText('Project Kickoff')).toBeInTheDocument();
+
+ expect(getByText(/Hey! We are the team who has been working/i)).toBeInTheDocument();
+ expect(getByText(/Let’s have a look at our workflow/i)).toBeInTheDocument();
+ expect(getByText(/We did this meeting right before Sprint 1 started/i)).toBeInTheDocument();
+ expect(getByText(/We needed more than one call/i)).toBeInTheDocument();
+ });
+});
diff --git a/src/components/AboutUs/TeamProcess/TeamProcess.tsx b/src/components/AboutUs/TeamProcess/TeamProcess.tsx
new file mode 100644
index 0000000..04e2a9f
--- /dev/null
+++ b/src/components/AboutUs/TeamProcess/TeamProcess.tsx
@@ -0,0 +1,23 @@
+import { ReactElement } from 'react';
+import { ContentBlock } from '../ContentBlock';
+import { IContentBlock } from '../ContentBlock/ContentBlock';
+
+import styles from './teamProcess.module.scss';
+
+interface ITeamworkProps {
+ content: IContentBlock[];
+}
+
+function TeamProcess({ content }: ITeamworkProps): ReactElement {
+ return (
+
+
Teamwork & Process
+
+ {content.map((textBlock) => (
+
+ ))}
+
+ );
+}
+
+export default TeamProcess;
diff --git a/src/components/AboutUs/TeamProcess/index.ts b/src/components/AboutUs/TeamProcess/index.ts
new file mode 100644
index 0000000..52027e9
--- /dev/null
+++ b/src/components/AboutUs/TeamProcess/index.ts
@@ -0,0 +1,2 @@
+import TeamProcess from './TeamProcess';
+export { TeamProcess };
diff --git a/src/components/AboutUs/TeamProcess/teamProcess.module.scss b/src/components/AboutUs/TeamProcess/teamProcess.module.scss
new file mode 100644
index 0000000..47c8b53
--- /dev/null
+++ b/src/components/AboutUs/TeamProcess/teamProcess.module.scss
@@ -0,0 +1,35 @@
+@import '../../../styles/common';
+
+.root {
+ padding: 5rem 5rem 7rem;
+ background-color: $primary;
+ color: $light-primary;
+ border-radius: 4rem;
+ font-size: 4rem;
+ line-height: 1.1;
+ display: flex;
+ flex-direction: column;
+
+ &__title {
+ margin-bottom: 6rem;
+ font-weight: 500;
+
+ @media (max-width: 650px) {
+ margin-bottom: 4rem;
+ }
+ }
+
+ @media (max-width: 800px) {
+ padding: 5rem 3rem 5rem;
+ }
+
+ @media (max-width: 650px) {
+ font-size: 3rem;
+ line-height: 1.2;
+ }
+
+ @media (max-width: 480px) {
+ font-size: 2.4rem;
+ line-height: 1.2;
+ }
+}
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
new file mode 100644
index 0000000..4f5a659
--- /dev/null
+++ b/src/components/App/App.tsx
@@ -0,0 +1,6 @@
+import { ReactElement } from 'react';
+import { Router } from '../../router/Router';
+
+export default function App(): ReactElement {
+ return ;
+}
diff --git a/src/components/App/index.ts b/src/components/App/index.ts
new file mode 100644
index 0000000..e0fca9f
--- /dev/null
+++ b/src/components/App/index.ts
@@ -0,0 +1,2 @@
+import App from './App';
+export { App };
diff --git a/src/components/Breadcrumbs/Breadcrumbs.tsx b/src/components/Breadcrumbs/Breadcrumbs.tsx
new file mode 100644
index 0000000..dd396fe
--- /dev/null
+++ b/src/components/Breadcrumbs/Breadcrumbs.tsx
@@ -0,0 +1,59 @@
+import { ReactElement } from 'react';
+import { Link, useSearchParams } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { selectCategories } from '../../store/catalog/selectors';
+import { PATH } from '../../router/constants/paths';
+import { Page } from '../../router/types';
+import { SearchParams } from '../../types/enums';
+import { ICategoryFilterItem } from '../Filters/types';
+
+import styles from './breadcrumbs.module.scss';
+
+function Breadcrumbs(): ReactElement {
+ const categories = useSelector(selectCategories);
+ const [searchParams] = useSearchParams();
+
+ const category = categories.find((item) => item.value === searchParams.get(SearchParams.Category));
+ const isSubcategory = !!category?.parent;
+ let parentCategory: ICategoryFilterItem | undefined = undefined;
+ if (isSubcategory) {
+ parentCategory = categories.find((item) => item.value === category?.parent);
+ }
+
+ return (
+
+
+ -
+
+ Home
+ /
+
+
+ -
+
+ Catalog
+ {category && / }
+
+
+ {category && (
+ -
+
+ {isSubcategory ? parentCategory?.label : category.label}
+ {isSubcategory && / }
+
+
+ )}
+ {isSubcategory && (
+ -
+ {category?.label}
+
+ )}
+
+
+ );
+}
+
+export default Breadcrumbs;
diff --git a/src/components/Breadcrumbs/breadcrumbs.module.scss b/src/components/Breadcrumbs/breadcrumbs.module.scss
new file mode 100644
index 0000000..f52a293
--- /dev/null
+++ b/src/components/Breadcrumbs/breadcrumbs.module.scss
@@ -0,0 +1,32 @@
+@import 'src/styles/common';
+
+.crumbs {
+ width: 100%;
+ text-align: left;
+ margin-bottom: 4rem;
+
+ @media all and (max-width: 768px) {
+ margin-bottom: 2rem;
+ }
+
+ &__list {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
+ &__link {
+ @include font(regular, p3, $dark);
+ text-decoration: none;
+
+ transition: all 0.2s;
+
+ &:hover {
+ opacity: 0.5;
+ }
+ }
+
+ &__separator {
+ white-space: pre-wrap;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Breadcrumbs/index.ts b/src/components/Breadcrumbs/index.ts
new file mode 100644
index 0000000..d5ba605
--- /dev/null
+++ b/src/components/Breadcrumbs/index.ts
@@ -0,0 +1,3 @@
+import Breadcrumbs from './Breadcrumbs';
+
+export { Breadcrumbs };
diff --git a/src/components/Filters/CategoryFilter/CategoryFilter.tsx b/src/components/Filters/CategoryFilter/CategoryFilter.tsx
new file mode 100644
index 0000000..d4e537b
--- /dev/null
+++ b/src/components/Filters/CategoryFilter/CategoryFilter.tsx
@@ -0,0 +1,51 @@
+import React, { ReactElement, useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import classnames from 'classnames';
+import { selectCategories } from '../../../store/catalog/selectors';
+import { useAppDispatch } from '../../../store/store';
+import { categoriesThunk, productListThunk } from '../../../store/catalog/thunks';
+import { SearchParams } from '../../../types/enums';
+import { changeParams, getSearchParams } from '../../../utils';
+import { FilterTypeProps } from '../types';
+import { CategoryItem } from './CategoryItem';
+
+import styles from './categoryFilter.module.scss';
+
+function CategoryFilter({ searchParams, setSearchParams, className }: FilterTypeProps): ReactElement {
+ const categories = useSelector(selectCategories);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (categories.length === 0) {
+ dispatch(categoriesThunk());
+ }
+ }, [categories, dispatch]);
+
+ function handleClick(event: React.MouseEvent): void {
+ changeParams(setSearchParams, event.currentTarget.dataset.id || '', SearchParams.Category);
+ changeParams(setSearchParams, '1', SearchParams.Page);
+ dispatch(productListThunk({ params: getSearchParams(searchParams), list: [] }));
+ }
+
+ return (
+
+
+ {categories.map((item) => (
+ -
+
+
+ ))}
+
+
+ );
+}
+
+export default CategoryFilter;
diff --git a/src/components/Filters/CategoryFilter/CategoryItem/CategoryItem.tsx b/src/components/Filters/CategoryFilter/CategoryItem/CategoryItem.tsx
new file mode 100644
index 0000000..a9fbdc0
--- /dev/null
+++ b/src/components/Filters/CategoryFilter/CategoryItem/CategoryItem.tsx
@@ -0,0 +1,25 @@
+import React, { ReactElement } from 'react';
+import { IFilterItem } from '../../types';
+
+import styles from './categoryItem.module.scss';
+import classnames from 'classnames';
+
+interface CategoryItemProps extends IFilterItem {
+ onClick: (event: React.MouseEvent) => void;
+ isActive: boolean;
+}
+
+function CategoryItem({ label, value, onClick, isActive }: CategoryItemProps): ReactElement {
+ return (
+
+ );
+}
+
+export default CategoryItem;
diff --git a/src/components/Filters/CategoryFilter/CategoryItem/categoryItem.module.scss b/src/components/Filters/CategoryFilter/CategoryItem/categoryItem.module.scss
new file mode 100644
index 0000000..c8605a5
--- /dev/null
+++ b/src/components/Filters/CategoryFilter/CategoryItem/categoryItem.module.scss
@@ -0,0 +1,27 @@
+@import 'src/styles/common';
+
+.item {
+ position: relative;
+ @include font(semibold, h1, $dark);
+ transition: color 0.3s ease-in-out;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 0.2rem;
+ background-color: $primary;
+ opacity: 0;
+ transition: opacity 0.3s ease-in-out;
+ }
+
+ &_active {
+ color: $primary;
+
+ &::after {
+ opacity: 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/Filters/CategoryFilter/CategoryItem/index.ts b/src/components/Filters/CategoryFilter/CategoryItem/index.ts
new file mode 100644
index 0000000..1f0d413
--- /dev/null
+++ b/src/components/Filters/CategoryFilter/CategoryItem/index.ts
@@ -0,0 +1,3 @@
+import CategoryItem from './CategoryItem';
+
+export { CategoryItem };
diff --git a/src/components/Filters/CategoryFilter/CategoryList/CategoryList.tsx b/src/components/Filters/CategoryFilter/CategoryList/CategoryList.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/Filters/CategoryFilter/CategoryList/index.ts b/src/components/Filters/CategoryFilter/CategoryList/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/Filters/CategoryFilter/categoryFilter.module.scss b/src/components/Filters/CategoryFilter/categoryFilter.module.scss
new file mode 100644
index 0000000..4ced176
--- /dev/null
+++ b/src/components/Filters/CategoryFilter/categoryFilter.module.scss
@@ -0,0 +1,51 @@
+@import '../../../styles/common/';
+
+.category {
+ &__list {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ }
+
+
+ &__item {
+ margin-bottom: 0.5rem;
+
+ button {
+ position: relative;
+
+ transition: all 0.1s;
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 2px;
+ background-color: $primary;
+
+ transition: all 0.1s;
+ }
+ }
+
+ &:hover {
+ button {
+ color: $primary;
+
+ &::after {
+ opacity: 1;
+ }
+ }
+ }
+
+ &_subcategory {
+ padding-left: 4rem;
+
+ &:nth-child(3) {
+ margin-bottom: 2rem;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/Filters/CategoryFilter/index.ts b/src/components/Filters/CategoryFilter/index.ts
new file mode 100644
index 0000000..e6f0cdd
--- /dev/null
+++ b/src/components/Filters/CategoryFilter/index.ts
@@ -0,0 +1,3 @@
+import CategoryFilter from './CategoryFilter';
+
+export { CategoryFilter };
diff --git a/src/components/Filters/CheckboxFilter/CheckboxFilter.tsx b/src/components/Filters/CheckboxFilter/CheckboxFilter.tsx
new file mode 100644
index 0000000..155c635
--- /dev/null
+++ b/src/components/Filters/CheckboxFilter/CheckboxFilter.tsx
@@ -0,0 +1,61 @@
+import { ChangeEvent, ReactElement, useState } from 'react';
+import classnames from 'classnames';
+import { changeParams } from '../../../utils';
+import { CheckboxFilterProps } from '../types';
+
+import styles from './checkboxFilter.module.scss';
+
+function CheckboxFilter({
+ searchParams,
+ setSearchParams,
+ className,
+ field,
+ filters,
+ name,
+}: CheckboxFilterProps): ReactElement {
+ const [isOpen, setIsOpen] = useState(true);
+
+ function handleChange(event: ChangeEvent): void {
+ let newValue = event.target.name;
+ const curValue = searchParams.get(field) || '';
+ if (curValue.includes(newValue)) {
+ newValue = curValue
+ .split(',')
+ .filter((item) => item !== newValue)
+ .join(',');
+ changeParams(setSearchParams, newValue, field);
+ } else {
+ newValue = curValue + `${curValue.length > 0 ? ',' : ''}` + newValue;
+ changeParams(setSearchParams, newValue, field);
+ }
+ }
+
+ return (
+
+
setIsOpen(!isOpen)}>
+
{name}
+
+
+ {isOpen && (
+
+ )}
+
+ );
+}
+
+export default CheckboxFilter;
diff --git a/src/components/Filters/CheckboxFilter/checkboxFilter.module.scss b/src/components/Filters/CheckboxFilter/checkboxFilter.module.scss
new file mode 100644
index 0000000..8ea88bc
--- /dev/null
+++ b/src/components/Filters/CheckboxFilter/checkboxFilter.module.scss
@@ -0,0 +1,55 @@
+@import 'src/styles/common';
+
+.filter {
+ &__header {
+ @include accordion-header();
+ }
+
+ &__title {
+ @include font(semibold, p1, $dark);
+
+ &_close {
+ font-weight: bold;
+ }
+ }
+
+ &__icon {
+ @include accordion-icon();
+ background-image: url('../../../assets/icons/arrow-right.svg');
+
+ &_down {
+ transform: rotate(90deg);
+ }
+
+ &_up {
+ transform: rotate(-90deg);
+ }
+ }
+
+ &__form {
+ @include filter();
+ padding: 1rem 0 3rem;
+ }
+
+ &__item {
+ @include check-field();
+
+ span {
+ transition: opacity 0.2s;
+ }
+
+ &:not(:last-child) {
+ margin-bottom: 1.5rem;
+ }
+
+ &:hover {
+ span {
+ opacity: 0.6;
+ }
+ }
+ }
+
+ &__input {
+ @include checkbox();
+ }
+}
\ No newline at end of file
diff --git a/src/components/Filters/CheckboxFilter/index.ts b/src/components/Filters/CheckboxFilter/index.ts
new file mode 100644
index 0000000..3cdf7bc
--- /dev/null
+++ b/src/components/Filters/CheckboxFilter/index.ts
@@ -0,0 +1,3 @@
+import CheckboxFilter from './CheckboxFilter';
+
+export { CheckboxFilter };
diff --git a/src/components/Filters/ColorFilter/ColorFilter.tsx b/src/components/Filters/ColorFilter/ColorFilter.tsx
new file mode 100644
index 0000000..a308fb7
--- /dev/null
+++ b/src/components/Filters/ColorFilter/ColorFilter.tsx
@@ -0,0 +1,66 @@
+import { ChangeEvent, ReactElement, useState } from 'react';
+import classnames from 'classnames';
+import { SearchParams } from '../../../types/enums';
+import { changeParams } from '../../../utils';
+import { colors } from '../../../constant';
+import { FilterTypeProps } from '../types';
+
+import styles from './colorFilter.module.scss';
+
+function ColorFilter({ searchParams, setSearchParams, className }: FilterTypeProps): ReactElement {
+ const [isOpen, setIsOpen] = useState(true);
+
+ function handleChange(event: ChangeEvent): void {
+ let newValue = event.target.name;
+ const curValue = searchParams.get(SearchParams.Color) || '';
+ if (curValue.includes(newValue)) {
+ newValue = curValue
+ .split(',')
+ .filter((item) => item !== newValue)
+ .join(',');
+ changeParams(setSearchParams, newValue, SearchParams.Color);
+ } else {
+ newValue = curValue + `${curValue.length > 0 ? ',' : ''}` + newValue;
+ changeParams(setSearchParams, newValue, SearchParams.Color);
+ }
+ }
+
+ return (
+
+
setIsOpen(!isOpen)}>
+
Color
+
+
+ {isOpen && (
+
+ )}
+
+ );
+}
+
+export default ColorFilter;
diff --git a/src/components/Filters/ColorFilter/colorFilter.module.scss b/src/components/Filters/ColorFilter/colorFilter.module.scss
new file mode 100644
index 0000000..3958aa7
--- /dev/null
+++ b/src/components/Filters/ColorFilter/colorFilter.module.scss
@@ -0,0 +1,88 @@
+@import 'src/styles/common';
+
+.color {
+ &__header {
+ @include accordion-header();
+ }
+
+ &__title {
+ @include font(semibold, p1, $dark);
+
+ &_close {
+ font-weight: bold;
+ }
+ }
+
+ &__icon {
+ @include accordion-icon();
+ background-image: url('../../../assets/icons/arrow-right.svg');
+
+ &_down {
+ transform: rotate(90deg);
+ }
+
+ &_up {
+ transform: rotate(-90deg);
+ }
+ }
+
+ &__form {
+ @include filter();
+ padding: 1rem 0 3rem;
+ }
+
+ &__item {
+ @include check-field();
+ position: relative;
+ width: 100%;
+ padding: 1rem;
+ border: 0.1rem solid $bullet-grey;
+
+ transition: border 0.2s;
+
+ &:not(:last-child) {
+ margin-bottom: 1rem;
+ }
+
+ &:hover {
+ border: 1px solid rgba($dark, 1);
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ right: 1rem;
+ width: 1rem;
+ height: 1rem;
+ background-color: $green;
+ border-radius: 100%;
+ transform: scale(0);
+ opacity: 0;
+ transition: all 0.1s ease-in;
+ }
+
+ &_active {
+ border-color: $dark;
+
+ &::after {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+ }
+
+ &__color {
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ border-radius: 100%;
+ border: 0.1rem solid transparent;
+ }
+
+ &__input {
+ position: absolute;
+ visibility: hidden;
+ opacity: 0;
+ z-index: -1;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Filters/ColorFilter/index.ts b/src/components/Filters/ColorFilter/index.ts
new file mode 100644
index 0000000..118bf03
--- /dev/null
+++ b/src/components/Filters/ColorFilter/index.ts
@@ -0,0 +1,3 @@
+import ColorFilter from './ColorFilter';
+
+export { ColorFilter };
diff --git a/src/components/Filters/Filters.tsx b/src/components/Filters/Filters.tsx
new file mode 100644
index 0000000..3bd9642
--- /dev/null
+++ b/src/components/Filters/Filters.tsx
@@ -0,0 +1,75 @@
+import { ReactElement } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { PriceFilter } from './PriceFilter';
+import { ColorFilter } from './ColorFilter';
+import { CheckboxFilter } from './CheckboxFilter';
+import { productListThunk } from '../../store/catalog/thunks';
+import { useAppDispatch } from '../../store/store';
+import { changeParams, getSearchParams } from '../../utils';
+import { SearchParams } from '../../types/enums';
+import { FiltersProps } from './types';
+import { brands, sizes } from '../../constant';
+
+import styles from './filters.module.scss';
+
+function Filters({ className, onClick }: FiltersProps): ReactElement {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const dispatch = useAppDispatch();
+
+ function showResult(): void {
+ changeParams(setSearchParams, '1', SearchParams.Page);
+ dispatch(productListThunk({ params: getSearchParams(searchParams), list: [] }));
+ onClick();
+ }
+
+ function resetFilters(): void {
+ changeParams(setSearchParams, '', SearchParams.PriceFrom);
+ changeParams(setSearchParams, '', SearchParams.PriceTo);
+ changeParams(setSearchParams, '', SearchParams.Brand);
+ changeParams(setSearchParams, '', SearchParams.Color);
+ changeParams(setSearchParams, '', SearchParams.Size);
+
+ showResult();
+ }
+
+ return (
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Filters;
diff --git a/src/components/Filters/PriceFilter/PriceFilter.tsx b/src/components/Filters/PriceFilter/PriceFilter.tsx
new file mode 100644
index 0000000..6bdc3e8
--- /dev/null
+++ b/src/components/Filters/PriceFilter/PriceFilter.tsx
@@ -0,0 +1,72 @@
+import { ChangeEvent, ReactElement, useEffect, useState } from 'react';
+import classnames from 'classnames';
+import { SearchParams } from '../../../types/enums';
+import { useDebounce } from '../../../hooks';
+import { changeParams } from '../../../utils';
+import { FilterTypeProps } from '../types';
+
+import styles from './priceFilter.module.scss';
+
+function PriceFilter({ searchParams, setSearchParams, className }: FilterTypeProps): ReactElement {
+ const [from, setFrom] = useState(searchParams.get(SearchParams.PriceFrom) || '');
+ const [to, setTo] = useState(searchParams.get(SearchParams.PriceTo) || '');
+ const [isOpen, setIsOpen] = useState(true);
+
+ const debounceChangeParams = useDebounce(changeParams, 400);
+
+ function handleChange(event: ChangeEvent): void {
+ const field = event.target.name;
+ const newValue = event.target.value;
+ if (field === 'from') {
+ setFrom(newValue);
+ debounceChangeParams(setSearchParams, newValue, SearchParams.PriceFrom);
+ } else {
+ setTo(newValue);
+ debounceChangeParams(setSearchParams, newValue, SearchParams.PriceTo);
+ }
+ }
+
+ useEffect(() => {
+ setFrom(searchParams.get(SearchParams.PriceFrom) || '');
+ setTo(searchParams.get(SearchParams.PriceTo) || '');
+ }, [searchParams]);
+
+ return (
+
+
setIsOpen(!isOpen)}>
+
Price
+
+
+ {isOpen && (
+
+ )}
+
+ );
+}
+
+export default PriceFilter;
diff --git a/src/components/Filters/PriceFilter/index.ts b/src/components/Filters/PriceFilter/index.ts
new file mode 100644
index 0000000..f99d398
--- /dev/null
+++ b/src/components/Filters/PriceFilter/index.ts
@@ -0,0 +1,3 @@
+import PriceFilter from './PriceFilter';
+
+export { PriceFilter };
diff --git a/src/components/Filters/PriceFilter/priceFilter.module.scss b/src/components/Filters/PriceFilter/priceFilter.module.scss
new file mode 100644
index 0000000..7da33c9
--- /dev/null
+++ b/src/components/Filters/PriceFilter/priceFilter.module.scss
@@ -0,0 +1,60 @@
+@import 'src/styles/common';
+
+.price {
+ width: 100%;
+
+ &__header {
+ @include accordion-header();
+ }
+
+ &__title {
+ @include font(semibold, p1, $dark);
+
+ &_close {
+ font-weight: bold;
+ }
+ }
+
+ &__icon {
+ @include accordion-icon();
+ background-image: url('../../../assets/icons/arrow-right.svg');
+
+ &_down {
+ transform: rotate(90deg);
+ }
+
+ &_up {
+ transform: rotate(-90deg);
+ }
+ }
+
+ &__form {
+ @include filter();
+ flex-direction: row;
+ padding: 1rem 0 3rem;
+ }
+
+ &__item {
+ border: 0.1rem solid $dark;
+
+ &:last-child {
+ border-left-color: transparent;
+ }
+ }
+
+ &__input {
+ width: 100%;
+ border: none;
+ padding: 1rem;
+ background-color: transparent;
+
+ &::placeholder {
+ @include font(reguler, p1, $placeholder-grey);
+ opacity: 0.7;
+ }
+
+ &:focus {
+ background-color: rgba($primary, 0.1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/Filters/filters.module.scss b/src/components/Filters/filters.module.scss
new file mode 100644
index 0000000..1af9e3b
--- /dev/null
+++ b/src/components/Filters/filters.module.scss
@@ -0,0 +1,49 @@
+@import 'src/styles/common';
+
+.filters {
+ &__list {
+ width: 100%;
+ }
+
+ &__item {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid $dark;
+
+ &:last-child {
+ border-bottom: 1px solid $dark;
+ }
+ }
+
+ &__wrapper {
+ padding-top: 3rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ }
+
+ &__btn {
+ @include font(semibold, h2, $dark);
+ width: 100%;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: $border-radius-btn;
+ background-color: $btn-grey;
+ transition: background-color 0.3s ease-in-out;
+
+ &:focus,
+ &:hover {
+ background-color: $btn-dark-grey;
+ }
+ }
+
+ &__results {
+ @include btn-primary();
+ width: 100%;
+ margin-bottom: 2rem;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Filters/index.ts b/src/components/Filters/index.ts
new file mode 100644
index 0000000..2a727c1
--- /dev/null
+++ b/src/components/Filters/index.ts
@@ -0,0 +1,3 @@
+import Filters from './Filters';
+
+export { Filters };
diff --git a/src/components/Filters/types/index.ts b/src/components/Filters/types/index.ts
new file mode 100644
index 0000000..4fdc34f
--- /dev/null
+++ b/src/components/Filters/types/index.ts
@@ -0,0 +1,32 @@
+import { SetURLSearchParams } from 'react-router-dom';
+import { SearchParams } from '../../../types/enums.ts';
+
+export interface IFilterItem {
+ label: string;
+ value: string;
+}
+
+export interface IColorFilterItem extends IFilterItem {
+ color: string;
+}
+
+export interface ICategoryFilterItem extends IFilterItem {
+ parent: string | null;
+}
+
+export interface FiltersProps {
+ onClick: () => void;
+ className?: string;
+}
+
+export interface FilterTypeProps {
+ searchParams: URLSearchParams;
+ setSearchParams: SetURLSearchParams;
+ className?: string;
+}
+
+export interface CheckboxFilterProps extends FilterTypeProps {
+ field: SearchParams;
+ filters: IFilterItem[];
+ name: string;
+}
diff --git a/src/components/Footer/Footer.test.tsx b/src/components/Footer/Footer.test.tsx
new file mode 100644
index 0000000..d997dc5
--- /dev/null
+++ b/src/components/Footer/Footer.test.tsx
@@ -0,0 +1,22 @@
+import { render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { Footer } from '.';
+
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../constant', () => 'en-US');
+
+describe('Test Footer component', () => {
+ beforeEach(() => {
+ render(
+
+
+ ,
+ );
+ });
+
+ test('Should displays current school link and has the right href', () => {
+ const schoolLinkElement = screen.getByRole('link', { name: /©2023 RS School/i });
+ expect(schoolLinkElement).toBeInTheDocument();
+ expect(schoolLinkElement).toHaveAttribute('href', 'https://rs.school/');
+ });
+});
diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx
new file mode 100644
index 0000000..764302d
--- /dev/null
+++ b/src/components/Footer/Footer.tsx
@@ -0,0 +1,23 @@
+import { ReactElement } from 'react';
+import { Link } from 'react-router-dom';
+import classnames from 'classnames';
+import { getCurrentDate } from '../../utils';
+
+import styles from './footer.module.scss';
+
+const SCHOOL_URL = 'https://rs.school/';
+
+export default function Footer(): ReactElement {
+ const currentDate = getCurrentDate();
+
+ return (
+
+ );
+}
diff --git a/src/components/Footer/footer.module.scss b/src/components/Footer/footer.module.scss
new file mode 100644
index 0000000..cf06e16
--- /dev/null
+++ b/src/components/Footer/footer.module.scss
@@ -0,0 +1,43 @@
+@import 'src/styles/common';
+
+.footer {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: $dark;
+ color: $light;
+
+ &__container {
+ width: 100%;
+ padding: 1rem 2rem;
+ display: flex;
+ justify-content: space-between;
+ gap: 2rem;
+
+ @media all and (max-width: 620px) {
+ padding: 2rem;
+ flex-direction: column-reverse;
+ gap: 0;
+ }
+
+ @media all and (max-width: 480px) {
+ padding: 2rem 1rem;
+ }
+ }
+
+ &__link {
+ @include link();
+ width: fit-content;
+ text-align: right;
+
+ &:hover {
+ color: $light;
+ opacity: 0.5;
+ }
+
+ &::after {
+ display: none;
+ }
+ }
+}
diff --git a/src/components/Footer/index.ts b/src/components/Footer/index.ts
new file mode 100644
index 0000000..ef9e54f
--- /dev/null
+++ b/src/components/Footer/index.ts
@@ -0,0 +1,2 @@
+import Footer from './Footer';
+export { Footer };
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
new file mode 100644
index 0000000..f01a966
--- /dev/null
+++ b/src/components/Header/Header.tsx
@@ -0,0 +1,135 @@
+import { ReactElement, MouseEvent, useState, useEffect } from 'react';
+import { Link, NavLink, useLocation, useNavigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { resetCart } from '../../store/cart/slice';
+import { selectIsAuthorized } from '../../store/auth/selectors';
+import { useAppDispatch } from '../../store/store';
+import { logoutThunk } from '../../store/auth/thunks';
+import { selectCart } from '../../store/cart/selectors';
+import { PATH } from '../../router/constants/paths';
+import { Page } from '../../router/types';
+import { getCartThunk } from '../../store/cart/thunks';
+
+import Logo from '../../assets/icons/logo.svg';
+import CartIcon from '../../assets/icons/icon-cart.svg';
+
+import classnames from 'classnames';
+import styles from './header.module.scss';
+
+export default function Header(): ReactElement {
+ const [menuOpen, setMenuOpen] = useState(false);
+
+ const navigate = useNavigate();
+ const location = useLocation();
+ const isAuthorized = useSelector(selectIsAuthorized);
+
+ const dispatch = useAppDispatch();
+
+ const basket = useSelector(selectCart);
+
+ useEffect(() => {
+ if (!basket.id) {
+ dispatch(getCartThunk());
+ }
+ }, [basket.id, dispatch]);
+
+ const isAuthPage = location.pathname.slice(1) === 'login' || location.pathname.slice(1) === 'register';
+
+ function handleLogout(e: MouseEvent): void {
+ e.preventDefault();
+ dispatch(logoutThunk());
+ dispatch(resetCart());
+ navigate(PATH[Page.Home]);
+ closeMenu();
+ }
+
+ function toggleMenu(e: MouseEvent): void {
+ e.preventDefault();
+ setMenuOpen(!menuOpen);
+ }
+
+ function closeMenu(): void {
+ if (menuOpen) {
+ setMenuOpen(false);
+ }
+ }
+
+ function closeMenuOnResize(): void {
+ if (window.innerWidth > 650 && menuOpen) {
+ setMenuOpen(false);
+ }
+ }
+
+ useEffect(() => {
+ window.addEventListener('resize', closeMenuOnResize);
+
+ return () => {
+ window.removeEventListener('resize', closeMenuOnResize);
+ };
+ });
+
+ return (
+
+ );
+}
diff --git a/src/components/Header/header.module.scss b/src/components/Header/header.module.scss
new file mode 100644
index 0000000..dafd6ef
--- /dev/null
+++ b/src/components/Header/header.module.scss
@@ -0,0 +1,168 @@
+@import '../../styles/common/';
+
+.header {
+ width: 100%;
+ height: 6rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: $light;
+ border-bottom: 1px solid $dark;
+
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+
+ &__container {
+ padding: 0 2rem;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+}
+
+.logo {
+ width: 16rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: $dark;
+
+ &::after {
+ display: none;
+ }
+
+ svg {
+ fill: currentColor;
+ }
+
+ &:hover {
+ color: $primary;
+ }
+}
+
+.header__nav {
+ display: flex;
+ flex-direction: row;
+ gap: 2rem;
+
+ &_button {
+ display: none;
+ }
+
+ @media all and (max-width: 650px) {
+ flex-direction: row-reverse;
+ gap: 1.5rem;
+
+ &_button {
+ display: block;
+ }
+ }
+}
+
+.nav {
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+
+ &__link {
+ @include link();
+
+ &::after {
+ bottom: -0.1rem;
+ opacity: 0;
+
+ transition: all 0.2s ease-in;
+ }
+
+ &:hover {
+ &::after {
+ opacity: 1;
+ }
+ }
+ }
+
+ &__link_active {
+ color: $primary;
+
+ &::after {
+ background-color: $primary;
+ opacity: 1;
+ }
+ }
+
+ &__link_cart {
+ position: relative;
+
+ &::after {
+ bottom: 0.1rem;
+ }
+
+ &:hover {
+ svg > * {
+ fill: $primary;
+ }
+ }
+
+ .icon__count {
+ position: absolute;
+ right: -12px;
+ top: -4px;
+ width: 2rem;
+ height: 2rem;
+ background-color: $primary;
+ border-radius: 50%;
+ color: $light;
+ font-size: 1.2rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ }
+
+ @media all and (max-width: 650px) {
+ display: none;
+ }
+
+ &.open {
+ @media all and (max-width: 650px) {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 0.5rem;
+ padding: 2rem;
+ position: absolute;
+ top: 6rem;
+ left: 0;
+ right: 0;
+ background-color: $light;
+ box-shadow: 0px 5px 8px rgba($primary, 1);
+ z-index: 1;
+ }
+
+ @media all and (max-width: 480px) {
+ top: 5rem;
+ padding: 2rem 1rem;
+ }
+ }
+}
+
+@media all and (max-width: 480px) {
+ .header {
+ height: 5rem;
+
+ &__container {
+ padding: 0 1rem;
+ }
+ }
+
+ .logo {
+ width: 12rem;
+ }
+
+ .nav {
+ gap: 1.5rem;
+ }
+}
diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts
new file mode 100644
index 0000000..6a0251d
--- /dev/null
+++ b/src/components/Header/index.ts
@@ -0,0 +1,2 @@
+import Header from './Header';
+export { Header };
diff --git a/src/components/LoginForm/LoginForm.test.tsx b/src/components/LoginForm/LoginForm.test.tsx
new file mode 100644
index 0000000..0956846
--- /dev/null
+++ b/src/components/LoginForm/LoginForm.test.tsx
@@ -0,0 +1,50 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import * as reduxHooks from 'react-redux';
+import { LoginForm } from './index';
+import { initialCartSlice } from '../../constant';
+
+jest.mock('react-redux');
+jest.mock('../../constant/metaData', () => {});
+jest.mock('../../services/sdk/auth/methods', () => {});
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../services/sdk/catalog/methods', () => {});
+jest.mock('../../services/sdk/customer/methods', () => {});
+jest.mock('../../services/sdk/cart/methods', () => {});
+
+const useDispatchSpy = jest.spyOn(reduxHooks, 'useDispatch');
+const useSelectorSpy = jest.spyOn(reduxHooks, 'useSelector');
+
+describe('Test LoginForm component: ', () => {
+ test('should create LoginForm', () => {
+ useDispatchSpy.mockReturnValue(jest.fn());
+ const component = render();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('should dispatch action on submit', async () => {
+ useSelectorSpy.mockReturnValue(() => initialCartSlice.basket);
+ const mockDispatchFn = jest.fn();
+ useDispatchSpy.mockReturnValue(mockDispatchFn);
+
+ render();
+
+ const submitBtn = screen.getByText('Login ( ^ω^)');
+ expect(submitBtn).toBeInTheDocument();
+ await userEvent.click(submitBtn);
+ expect(mockDispatchFn).not.toHaveBeenCalled();
+
+ const email = screen.getByPlaceholderText('Email');
+ expect(email).toBeInTheDocument();
+ await userEvent.type(email, 'test@test.com');
+ expect(email).toHaveValue('test@test.com');
+
+ const password = screen.getByPlaceholderText('Password');
+ expect(email).toBeInTheDocument();
+ await userEvent.type(password, 'testTest1!');
+ expect(password).toHaveValue('testTest1!');
+
+ await userEvent.click(submitBtn);
+ expect(mockDispatchFn).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/LoginForm/LoginForm.tsx b/src/components/LoginForm/LoginForm.tsx
new file mode 100644
index 0000000..0192ffb
--- /dev/null
+++ b/src/components/LoginForm/LoginForm.tsx
@@ -0,0 +1,69 @@
+import { ReactElement } from 'react';
+import { Formik } from 'formik';
+import { useSelector } from 'react-redux';
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { InputField } from '../shared/InputField';
+import { PasswordField } from '../shared/PasswordField';
+import { Button } from '../shared/Button';
+import { anonLoginThunk, loginThunk } from '../../store/auth/thunks';
+import { selectCart } from '../../store/cart/selectors';
+import { useAppDispatch } from '../../store/store';
+import { initialLoginForm } from '../../constant';
+import { Input } from '../../types/enums';
+import { emailValidate } from '../../utils/validation';
+
+import styles from './loginForm.module.scss';
+
+export interface ILoginForm {
+ email: string;
+ password: string;
+}
+
+function LoginForm(): ReactElement {
+ const cart = useSelector(selectCart);
+ const dispatch = useAppDispatch();
+
+ function handleSubmit(values: ILoginForm): void {
+ const user: UserAuthOptions = {
+ username: values.email.trim(),
+ password: values.password.trim(),
+ };
+
+ if (cart.id) {
+ dispatch(anonLoginThunk(user));
+ } else {
+ dispatch(loginThunk(user));
+ }
+ }
+
+ return (
+
+ {({ values, handleSubmit, errors, touched, setFieldTouched }): ReactElement => (
+
+ )}
+
+ );
+}
+
+export default LoginForm;
diff --git a/src/components/LoginForm/__snapshots__/LoginForm.test.tsx.snap b/src/components/LoginForm/__snapshots__/LoginForm.test.tsx.snap
new file mode 100644
index 0000000..1a9886e
--- /dev/null
+++ b/src/components/LoginForm/__snapshots__/LoginForm.test.tsx.snap
@@ -0,0 +1,178 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test LoginForm component: should create LoginForm 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/components/LoginForm/index.ts b/src/components/LoginForm/index.ts
new file mode 100644
index 0000000..a9b2be4
--- /dev/null
+++ b/src/components/LoginForm/index.ts
@@ -0,0 +1,2 @@
+import LoginForm from './LoginForm';
+export { LoginForm };
diff --git a/src/components/LoginForm/loginForm.module.scss b/src/components/LoginForm/loginForm.module.scss
new file mode 100644
index 0000000..946c738
--- /dev/null
+++ b/src/components/LoginForm/loginForm.module.scss
@@ -0,0 +1,10 @@
+@import 'src/styles/common';
+
+.form {
+ display: flex;
+ flex-direction: column;
+
+ &__password {
+ margin-bottom: 7rem;
+ }
+}
\ No newline at end of file
diff --git a/src/components/ProductsList/ProductCard/ProductCard.tsx b/src/components/ProductsList/ProductCard/ProductCard.tsx
new file mode 100644
index 0000000..c3a9f3e
--- /dev/null
+++ b/src/components/ProductsList/ProductCard/ProductCard.tsx
@@ -0,0 +1,92 @@
+import { memo, ReactElement, MouseEvent, useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import classnames from 'classnames';
+import { PATH } from '../../../router/constants/paths';
+import { Page } from '../../../router/types';
+import { IProduct } from '../../../types/interfaces';
+import { updateCartThunk } from '../../../store/cart/thunks';
+import { selectCart, selectCartLoadingStatus } from '../../../store/cart/selectors';
+import { useAppDispatch } from '../../../store/store';
+import { formatPrice } from '../../../utils';
+import { Button } from '../../shared/Button';
+import { Loader } from '../../shared/Loader';
+
+import styles from './productCard.module.scss';
+
+interface ProductItemProps {
+ item: IProduct;
+ className?: string;
+}
+
+function ProductCard({ item, className }: ProductItemProps): ReactElement {
+ const { productId, title, artist, medium, dimensions, price, discountPrice, images } = item;
+ const cart = useSelector(selectCart);
+
+ const [isloading, setIsloading] = useState(false);
+ const cartStatus = useSelector(selectCartLoadingStatus);
+
+ const dispatch = useAppDispatch();
+
+ const isProductInCart = cart.lineItems.filter((item) => item.productId === productId).length > 0;
+
+ useEffect(() => {
+ if (cartStatus !== 'loading') {
+ setIsloading(false);
+ }
+ }, [cartStatus]);
+
+ function addToCart(event: MouseEvent): void {
+ event.preventDefault();
+ setIsloading(true);
+
+ dispatch(
+ updateCartThunk({
+ id: cart.id,
+ version: cart.version,
+ actions: [
+ {
+ action: 'addLineItem',
+ productId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ }),
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
{medium}
+
{dimensions}
+
+
+
+ {discountPrice ? formatPrice(discountPrice) : formatPrice(price)}
+
+ {discountPrice ? formatPrice(price) : ''}
+
+
+
: isProductInCart ? 'In Cart' : 'Add to Cart'}
+ handleClick={(event): void => addToCart(event)}
+ className={styles.card__button}
+ disabled={isProductInCart}
+ />
+
+
+
+ );
+}
+
+export default memo(ProductCard);
diff --git a/src/components/ProductsList/ProductCard/index.ts b/src/components/ProductsList/ProductCard/index.ts
new file mode 100644
index 0000000..6cdef7a
--- /dev/null
+++ b/src/components/ProductsList/ProductCard/index.ts
@@ -0,0 +1,3 @@
+import ProductCard from './ProductCard.tsx';
+
+export { ProductCard };
diff --git a/src/components/ProductsList/ProductCard/productCard.module.scss b/src/components/ProductsList/ProductCard/productCard.module.scss
new file mode 100644
index 0000000..067808f
--- /dev/null
+++ b/src/components/ProductsList/ProductCard/productCard.module.scss
@@ -0,0 +1,129 @@
+@import 'src/styles/common';
+
+.card {
+ position: relative;
+ width: 100%;
+ height: 100%;
+
+ // &::after {
+ // content: '';
+ // position: absolute;
+ // bottom: 2rem;
+ // right: 1.5rem;
+ // z-index: 1;
+ // width: 2rem;
+ // height: 1.5rem;
+ // background-color: $light-primary;
+ // clip-path: polygon(100% 0%, 0 0%, 50% 100%);
+ // transform: rotate(-90deg);
+ // pointer-events: none;
+ // opacity: 0;
+ // }
+
+ &:hover {
+ box-shadow: 0 5px 12px rgba($primary, 0.5);
+
+ &::after {
+ opacity: 1;
+ }
+
+ & .card__detail {
+ height: max-content;
+ opacity: 1;
+ }
+ }
+
+ &__link {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ text-decoration: none;
+ }
+
+ &__img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ &__descr {
+ width: 100%;
+ padding: 1.5rem 2rem 2rem;
+ z-index: 1;
+ background-color: $primary;
+ }
+
+ &__title {
+ margin-bottom: 1.5rem;
+ }
+
+ &__author {
+ @include font(semibold, p3, $light-primary);
+ }
+
+ &__detail {
+ height: 0;
+ opacity: 0;
+ }
+
+ &__dim {
+ margin-bottom: 2.5rem;
+ }
+
+ &__price {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ margin-bottom: 1rem;
+
+ &_current,
+ &_discount {
+ @include font(semibold, p3, $light-primary);
+ margin-right: 1rem;
+ }
+
+ &_discount {
+ text-decoration: solid line-through 0.15rem;
+ opacity: 0.5;
+ }
+ }
+
+ &__text {
+ @include font(regular, p3, $light-primary);
+ }
+
+ &__button {
+ position: relative;
+ width: 100%;
+ height: 4.4rem;
+ color: $light;
+ background-color: transparent;
+ border: 1px solid $light;
+ border-radius: 1rem;
+ font-size: 1.6rem;
+ z-index: 100;
+
+ &:hover {
+ font-size: 1.6rem;
+ background-color: $light;
+ color: $primary;
+ }
+
+ &:disabled {
+ cursor: default;
+ background-color: rgba($light, 0.1);
+ border: none;
+
+ &:hover {
+ background-color: rgba($light, 0.1);
+ color: rgba($light, 0.9);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/ProductsList/ProductsList.tsx b/src/components/ProductsList/ProductsList.tsx
new file mode 100644
index 0000000..1e91ed4
--- /dev/null
+++ b/src/components/ProductsList/ProductsList.tsx
@@ -0,0 +1,91 @@
+import { ReactElement, useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useSearchParams } from 'react-router-dom';
+import InfiniteScroll from 'react-infinite-scroll-component';
+import { selectCatalogLoadingInfo, selectProductsInfo } from '../../store/catalog/selectors';
+import { resetProductList } from '../../store/catalog/slice';
+import { useAppDispatch } from '../../store/store';
+import { productListThunk } from '../../store/catalog/thunks';
+import { getCartThunk } from '../../store/cart/thunks';
+import { selectCart, selectCartError } from '../../store/cart/selectors';
+import { ProductCard } from './ProductCard';
+import { Loader } from '../shared/Loader';
+import { ErrorMessage } from '../shared/ErrorMessage';
+import { changeParams, getSearchParams } from '../../utils';
+import { SearchParams } from '../../types/enums';
+import { resetCartError } from '../../store/cart/slice';
+
+import styles from './productsList.module.scss';
+
+function ProductsList(): ReactElement {
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const { productList, totalProducts } = useSelector(selectProductsInfo);
+ const { status, error } = useSelector(selectCatalogLoadingInfo);
+
+ const cartError = useSelector(selectCartError);
+
+ const cart = useSelector(selectCart);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (!cart.id) {
+ dispatch(getCartThunk());
+ }
+ }, [cart.id, dispatch]);
+
+ useEffect(() => {
+ changeParams(setSearchParams, '1', SearchParams.Page);
+ dispatch(productListThunk({ params: getSearchParams(searchParams), list: productList }));
+
+ return () => {
+ dispatch(resetProductList());
+ };
+ }, []);
+
+ useEffect(() => {
+ if (cartError) {
+ const timer = setTimeout(() => {
+ dispatch(resetCartError());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [cartError, dispatch]);
+
+ function loadProducts(): void {
+ changeParams(setSearchParams, `${Number(searchParams.get(SearchParams.Page)) + 1}`, SearchParams.Page);
+ dispatch(productListThunk({ params: getSearchParams(searchParams), list: productList }));
+ }
+
+ return (
+ <>
+ {!error && productList.length > 0 && (
+ }
+ scrollThreshold={0.8}
+ className={styles.products__scroll}
+ >
+
+ {productList.map((item) => (
+
+ ))}
+
+
+ )}
+ {status === 'success' && !error && productList.length === 0 && (
+ No items found┗(^o^ )┓三
+ )}
+
+ {error && }
+ {cartError && }
+ >
+ );
+}
+
+export default ProductsList;
diff --git a/src/components/ProductsList/index.ts b/src/components/ProductsList/index.ts
new file mode 100644
index 0000000..869fa6f
--- /dev/null
+++ b/src/components/ProductsList/index.ts
@@ -0,0 +1,3 @@
+import ProductsList from './ProductsList';
+
+export { ProductsList };
diff --git a/src/components/ProductsList/productsList.module.scss b/src/components/ProductsList/productsList.module.scss
new file mode 100644
index 0000000..80529c6
--- /dev/null
+++ b/src/components/ProductsList/productsList.module.scss
@@ -0,0 +1,59 @@
+@import '../../styles/common/';
+
+.products {
+ width: 100%;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(2, 44rem);
+ grid-auto-rows: 44rem;
+ gap: 2rem;
+
+ @media all and (max-width: 1024px) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ @media all and (max-width: 768px) {
+ grid-template-columns: 1fr;
+ }
+
+ &__item {
+ width: 100%;
+ background-color: rgba($primary, 0.1);
+ }
+
+ &__error {
+ @include font(semibold, h1, $light-primary);
+ line-height: 120%;
+ font-weight: 500;
+ padding: 2.5rem;
+ width: 100%;
+ min-height: 145px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ border-radius: $border-radius-btn-2x;
+ background-color: $primary;
+ }
+
+ &__scroll {
+ width: 100%;
+ }
+}
+
+.error__message {
+ position: fixed;
+ transform: translateX(-50%);
+ left: 50%;
+ bottom: 2rem;
+ max-width: 37rem;
+ width: 100%;
+ height: 4rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: $error-color;
+ border-radius: 2rem;
+ color: $light;
+ z-index: 100;
+}
diff --git a/src/components/Profile/GreetingTitle/GreetingTitle.tsx b/src/components/Profile/GreetingTitle/GreetingTitle.tsx
new file mode 100644
index 0000000..6f80131
--- /dev/null
+++ b/src/components/Profile/GreetingTitle/GreetingTitle.tsx
@@ -0,0 +1,27 @@
+import styles from './greetingTitle.module.scss';
+import classNames from 'classnames';
+import { ReactElement, useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { selectUserData } from '../../../store/user/selectors';
+import { useAppDispatch } from '../../../store/store';
+import { getUserThunk } from '../../../store/user/thunks';
+
+function GreetingTitle(): ReactElement {
+ const user = useSelector(selectUserData);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+ }, [user, dispatch]);
+ return (
+
+
+
Hello, {user.firstName}
+
Set your profile settings down below
+
+ );
+}
+
+export default GreetingTitle;
diff --git a/src/components/Profile/GreetingTitle/greetingTitle.module.scss b/src/components/Profile/GreetingTitle/greetingTitle.module.scss
new file mode 100644
index 0000000..5ee0ded
--- /dev/null
+++ b/src/components/Profile/GreetingTitle/greetingTitle.module.scss
@@ -0,0 +1,26 @@
+.greeting {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 4rem;
+ flex-wrap: wrap;
+
+ &__ava {
+ width: 4rem;
+ height: 4rem;
+ background-image: url('/images/profile-ava.jpg');
+ background-repeat: no-repeat;
+ background-size: contain;
+ border-radius: 50%;
+ }
+
+ &__title {
+ font-size: 4rem;
+ line-height: 4.2rem;
+ }
+
+ &__subtitle {
+ flex-basis: 100%;
+ padding-top: 1rem;
+ line-height: 2.2rem;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Profile/GreetingTitle/index.ts b/src/components/Profile/GreetingTitle/index.ts
new file mode 100644
index 0000000..b40efd6
--- /dev/null
+++ b/src/components/Profile/GreetingTitle/index.ts
@@ -0,0 +1,2 @@
+import GreetingTitle from './GreetingTitle';
+export { GreetingTitle };
diff --git a/src/components/Profile/Tabs/FirstTab/FirstTab.tsx b/src/components/Profile/Tabs/FirstTab/FirstTab.tsx
new file mode 100644
index 0000000..fb697d5
--- /dev/null
+++ b/src/components/Profile/Tabs/FirstTab/FirstTab.tsx
@@ -0,0 +1,186 @@
+import styles from './firstTab.module.scss';
+
+import classNames from 'classnames';
+import { Formik, FormikErrors } from 'formik';
+import { Input } from '../../../../types/enums';
+import { InputField } from '../../../shared/InputField';
+import { ReactElement, useEffect } from 'react';
+import { dateMYValidate, emailValidate, lastNameValidate, nameValidate } from '../../../../utils/validation';
+import { IUser } from '../../../../types/interfaces';
+import { UserDateOfBirth } from './UserDateOfBirth';
+import { Button } from '../../../shared/Button';
+import { useIsEditMode, useUpdateEditMode } from '../../../../pages/Profile/profileContext';
+import { useSelector } from 'react-redux';
+import { selectEditUserInfo, selectUserData } from '../../../../store/user/selectors';
+import { useAppDispatch } from '../../../../store/store';
+import { getUserThunk, updUserThunk } from '../../../../store/user/thunks';
+import { IUpdateUser } from '../../../../services/sdk/customer/types';
+import { months } from '../../../../constant';
+import { getMonthIndex } from '../../../../utils';
+import { ErrorMessage } from '../../../shared/ErrorMessage';
+import { deleteSuccessState, resetEditError } from '../../../../store/user/slice';
+
+function FirstTab(): ReactElement {
+ const { editStatus, editError, isSuccess } = useSelector(selectEditUserInfo);
+ const isEditMode = useIsEditMode();
+ const updateEditMode = useUpdateEditMode();
+ const user = useSelector(selectUserData);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+
+ if (editStatus === 'success') {
+ updateEditMode();
+ const timer = setTimeout(() => {
+ dispatch(deleteSuccessState());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+
+ if (editError) {
+ const timer = setTimeout(() => {
+ dispatch(resetEditError());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [user, dispatch, editStatus, editError]);
+
+ function handleSubmit(values: IUser): void {
+ const { year, month, date } = values;
+ const newMonth = getMonthIndex(month);
+ const dateOfBirth =
+ year +
+ '-' +
+ (String(newMonth).length === 1 ? `0${newMonth}` : newMonth) +
+ '-' +
+ (date.length === 1 ? `0${date}` : date);
+ const newValue: IUpdateUser = {
+ id: user.id,
+ version: user.version,
+ action: [
+ { action: 'setFirstName', firstName: values.firstName },
+ { action: 'setLastName', lastName: values.lastName },
+ { action: 'changeEmail', email: values.email },
+ { action: 'setDateOfBirth', dateOfBirth: dateOfBirth },
+ ],
+ };
+
+ dispatch(updUserThunk(newValue));
+ }
+
+ function validateDate(values: IUser): void | object | Promise> {
+ const errors: FormikErrors = {};
+ errors[Input.Date] = dateMYValidate(
+ `${values[Input.Date]}${months[Number(getMonthIndex(values[Input.Month])) - 1]}${values[Input.Year]}`,
+ );
+ if (!errors[Input.Date]) return {};
+ return errors;
+ }
+
+ return (
+ <>
+ {editError && (
+
+ )}
+
+ {({ handleSubmit, errors, touched, setFieldTouched, handleChange, resetForm }): ReactElement => (
+
+ )}
+
+ {isSuccess && (
+ Profile information was successfully updated ٩(。•́‿•̀。)۶
+ )}
+ >
+ );
+}
+
+export default FirstTab;
diff --git a/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/UserDateOfBirth.module.scss b/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/UserDateOfBirth.module.scss
new file mode 100644
index 0000000..e0dc9b0
--- /dev/null
+++ b/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/UserDateOfBirth.module.scss
@@ -0,0 +1,15 @@
+@import '../../../../../styles/common';
+
+.root {
+ margin-bottom: 4rem;
+
+ &__container {
+ display: flex;
+ gap: 2.5rem;
+ }
+
+ &__error {
+ @include input-error()
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/UserDateOfBirth.tsx b/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/UserDateOfBirth.tsx
new file mode 100644
index 0000000..8015e2f
--- /dev/null
+++ b/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/UserDateOfBirth.tsx
@@ -0,0 +1,45 @@
+import styles from './UserDateOfBirth.module.scss';
+import { ReactElement } from 'react';
+import { IUserFormProps } from '../../../../RegisterForm/UserForm/UserForm';
+import classNames from 'classnames';
+import { SelectField } from '../../../../shared/SelectField';
+import { Input } from '../../../../../types/enums';
+import { dates, months, years } from '../../../../../constant';
+
+function UserDateOfBirth({ handleChange, values, errors, isDisabled }: IUserFormProps): ReactElement {
+ const { date, month, year } = values;
+ return (
+
+
+
+
+
+
+
+ {errors?.date ?
{errors.date} : null}
+
+
+ );
+}
+export default UserDateOfBirth;
diff --git a/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/index.ts b/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/index.ts
new file mode 100644
index 0000000..39f5a36
--- /dev/null
+++ b/src/components/Profile/Tabs/FirstTab/UserDateOfBirth/index.ts
@@ -0,0 +1,2 @@
+import UserDateOfBirth from './UserDateOfBirth';
+export { UserDateOfBirth };
diff --git a/src/components/Profile/Tabs/FirstTab/firstTab.module.scss b/src/components/Profile/Tabs/FirstTab/firstTab.module.scss
new file mode 100644
index 0000000..eda56c4
--- /dev/null
+++ b/src/components/Profile/Tabs/FirstTab/firstTab.module.scss
@@ -0,0 +1,100 @@
+@import '../../../../styles/common';
+
+.form {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 4rem;
+
+ &__input {
+ margin-bottom: 1.5rem;
+
+ label {
+ span {
+ font-weight: 600;
+ }
+
+ input {
+ border: none;
+ padding: 0;
+ color: $dark;
+ }
+ }
+ }
+
+ &__email {
+ margin-bottom: 4.5rem;
+
+ @media (max-width: 480px) {
+ margin-bottom: 4rem;
+ }
+ }
+
+ &__dateOfBirth {
+ position: relative;
+ margin-bottom: 3rem;
+
+ &::before {
+ content: 'Date of Birth';
+ position: absolute;
+ top: -2.5rem;
+ @include font(semibold, p1, $dark);
+ line-height: 2.2rem;
+
+ @media (max-width: 480px) {
+ font-size: 18px;
+ top: -2.4rem;
+ }
+ }
+ }
+}
+
+.formEdit {
+ margin-bottom: 4rem;
+
+ &__email {
+ margin-bottom: 1rem;
+ }
+
+ &__input {
+ margin-bottom: 1.5rem;
+
+ label {
+ span {
+ font-weight: 600;
+ }
+
+ input {
+ border: none;
+ padding: 0.5rem 0;
+ color: $dark;
+ border-bottom: 1px solid $dark;
+ }
+ }
+ }
+
+ // &__input input {
+ // border-bottom: 1px solid $dark;
+ // }
+
+ &__closeBtn {
+ @include close-btn();
+ }
+
+ &__btn {
+ @include btn-primary();
+ width: 100%;
+ height: 7rem;
+
+ &:disabled {
+ opacity: 0.5;
+ }
+ }
+}
+
+.successResponse {
+ @include responseMessage($light-primary, $green);
+}
+
+.errorResponse {
+ @include responseMessage($light-primary, $error-color);
+}
diff --git a/src/components/Profile/Tabs/FirstTab/index.ts b/src/components/Profile/Tabs/FirstTab/index.ts
new file mode 100644
index 0000000..b2bfb3c
--- /dev/null
+++ b/src/components/Profile/Tabs/FirstTab/index.ts
@@ -0,0 +1,2 @@
+import FirstTab from './FirstTab';
+export { FirstTab };
diff --git a/src/components/Profile/Tabs/SecondTab/CustomerAdress/CustomerAddress.tsx b/src/components/Profile/Tabs/SecondTab/CustomerAdress/CustomerAddress.tsx
new file mode 100644
index 0000000..0e5ca92
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/CustomerAdress/CustomerAddress.tsx
@@ -0,0 +1,94 @@
+import styles from './address.module.scss';
+import classNames from 'classnames';
+import { MouseEvent, ReactElement, useEffect, useState } from 'react';
+import { ModalWindow } from '../../../../shared/ModalWindow';
+import { EditCard } from './EditCard';
+import { selectEditStatus, selectUserData } from '../../../../../store/user/selectors';
+import { useSelector } from 'react-redux';
+import { useAppDispatch } from '../../../../../store/store';
+import { getUserThunk } from '../../../../../store/user/thunks';
+import { Address } from '@commercetools/platform-sdk';
+
+interface IAddressComp {
+ values: Address;
+ key: number | string;
+ index: number;
+ deleteAddress: (addressId: string) => void;
+ addressId: string;
+ setDefaultAddress: (e: MouseEvent, addressId: string) => void;
+}
+
+function CustomerAddress({ addressId, values, deleteAddress, setDefaultAddress }: IAddressComp): ReactElement {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const editStatus = useSelector(selectEditStatus);
+ const user = useSelector(selectUserData);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+
+ if (editStatus === 'success') {
+ setIsModalOpen(false);
+ }
+ }, [user, dispatch, editStatus]);
+
+ function isDefaultShipping(): boolean {
+ return values.id === user.defaultShippingAddressId;
+ }
+ function isDefaultBilling(): boolean {
+ return values.id === user.defaultBillingAddressId;
+ }
+
+ return (
+
+
+
{values.streetName}
+
{`${values.postalCode} ${values.city}`}
+
{values.country}
+
+
+
+ }
+ isOpen={isModalOpen}
+ onClose={(): void => setIsModalOpen(!isModalOpen)}
+ />
+
+
+
+
+
+
+
+ );
+}
+
+export default CustomerAddress;
diff --git a/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/EditCard.tsx b/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/EditCard.tsx
new file mode 100644
index 0000000..cb44142
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/EditCard.tsx
@@ -0,0 +1,101 @@
+import styles from './editCard.module.scss';
+import { ReactElement, useEffect } from 'react';
+import { Input } from '../../../../../../types/enums';
+import { Formik } from 'formik';
+import { Button } from '../../../../../shared/Button';
+import { AddressForm } from '../../../../../RegisterForm/AddressForm';
+import { Address } from '@commercetools/platform-sdk';
+import { IAddressForm } from '../../../../../RegisterForm/AddressForm/AddressForm';
+import { useSelector } from 'react-redux';
+import { selectEditError, selectUserData } from '../../../../../../store/user/selectors';
+import { useAppDispatch } from '../../../../../../store/store';
+import { getUserThunk } from '../../../../../../store/user/thunks';
+import { IUpdateUser } from '../../../../../../services/sdk/customer/types';
+import { changeAddressThunk } from '../../../../../../store/user/thunks';
+import { ErrorMessage } from '../../../../../shared/ErrorMessage';
+
+interface IEditInitialValue {
+ shipping: IAddressForm;
+ billing: IAddressForm;
+}
+
+interface IEditCard {
+ values: Address;
+ isShipping: boolean;
+ addressId: string;
+}
+
+function EditCard({ values, isShipping, addressId }: IEditCard): ReactElement {
+ const user = useSelector(selectUserData);
+ const dispatch = useAppDispatch();
+ const editError = useSelector(selectEditError);
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+ }, [user, dispatch]);
+
+ const initialValue: IEditInitialValue = {
+ shipping: {
+ [Input.Street]: values.streetName || '',
+ [Input.Country]: values.country,
+ [Input.PostalCode]: values.postalCode || '',
+ [Input.City]: values.city || '',
+ },
+ billing: {
+ [Input.Street]: values.streetName || '',
+ [Input.Country]: values.country,
+ [Input.PostalCode]: values.postalCode || '',
+ [Input.City]: values.city || '',
+ },
+ };
+
+ function handleSubmit(values: IEditInitialValue): void {
+ const isShippingAddress = values.shipping.city !== '';
+ const addNewAddressData: IUpdateUser = {
+ id: user.id,
+ version: user.version,
+ action: [
+ {
+ action: 'changeAddress',
+ addressId: addressId,
+ address: {
+ streetName: isShippingAddress ? values.shipping.streetName : values.billing.streetName,
+ city: isShippingAddress ? values.shipping.city : values.billing.city,
+ postalCode: isShippingAddress ? values.shipping.postalCode : values.billing.postalCode,
+ country: isShippingAddress ? values.shipping.country : values.billing.country,
+ },
+ },
+ ],
+ };
+ dispatch(changeAddressThunk(addNewAddressData));
+ }
+
+ return (
+
+
Edit address
+
+ {({ handleSubmit, handleChange, setFieldTouched, touched, errors, values }): ReactElement => (
+
+ )}
+
+ {editError && (
+
+ )}
+
+ );
+}
+
+export default EditCard;
diff --git a/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/editCard.module.scss b/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/editCard.module.scss
new file mode 100644
index 0000000..dba673e
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/editCard.module.scss
@@ -0,0 +1,33 @@
+@import '../../../../../../styles/common';
+
+.root {
+
+ &__title {
+ margin-bottom: 6rem;
+ padding-top: 2rem;
+ font-size: 4rem;
+ line-height: 4.4rem;
+ }
+
+ &__formContainer h3 {
+ display: none;
+ }
+
+ &__addressForm {
+ &> :last-child {
+ opacity: 0;
+ user-select: none;
+ pointer-events: none;
+ }
+ }
+
+ &__btn {
+ width: 100%;
+ height: 7rem;
+ }
+
+}
+
+.errorResponse {
+ @include responseMessage($light-primary, $error-color);
+}
\ No newline at end of file
diff --git a/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/index.ts b/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/index.ts
new file mode 100644
index 0000000..2ea8f74
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/CustomerAdress/EditCard/index.ts
@@ -0,0 +1,2 @@
+import EditCard from './EditCard';
+export { EditCard };
diff --git a/src/components/Profile/Tabs/SecondTab/CustomerAdress/address.module.scss b/src/components/Profile/Tabs/SecondTab/CustomerAdress/address.module.scss
new file mode 100644
index 0000000..e873fd8
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/CustomerAdress/address.module.scss
@@ -0,0 +1,76 @@
+@import '../../../../../styles/common';
+
+.item {
+ display: flex;
+ padding: 2rem 0;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ border-bottom: 0.1rem solid $dark;
+
+ &:first-child {
+ border-top: 0.1rem solid $dark;
+ }
+
+ &:last-child {
+ margin-bottom: 6rem;
+ }
+
+ &__btns {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__data {
+ @include font(regular, p1, $dark);
+ line-height: 22px;
+ margin-bottom: 2rem;
+
+ @media (max-width: 480px) {
+ font-size: 1.8rem;
+ }
+ }
+}
+
+.defaultSettContainer {
+ display: flex;
+ gap: 1rem;
+
+ flex-basis: 100%;
+ justify-content: start;
+
+ &__address {
+ padding: 0.2rem 0.7rem 0.3rem;
+ border-radius: 0.5rem;
+ font-size: 1.4rem;
+ background-color: rgba($dark, 0.08);
+ transition: opacity $transition-set;
+
+ &_active {
+ background-color: rgba($green, 0.3);
+ color: $dark-green;
+ }
+
+ &:hover {
+ opacity: 0.9;
+ }
+ }
+}
+
+.configureBtns {
+ &__btn {
+ @include font(regular, p1, $dark);
+ text-align: right;
+ line-height: 22px;
+ opacity: 0.3;
+ transition: opacity $transition-set;
+
+ &:hover {
+ color: $primary;
+ opacity: 1;
+ }
+
+ @media (max-width: 480px) {
+ font-size: 1.8rem;
+ }
+ }
+}
diff --git a/src/components/Profile/Tabs/SecondTab/CustomerAdress/index.ts b/src/components/Profile/Tabs/SecondTab/CustomerAdress/index.ts
new file mode 100644
index 0000000..f648963
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/CustomerAdress/index.ts
@@ -0,0 +1,2 @@
+import CustomerAddress from './CustomerAddress';
+export { CustomerAddress };
diff --git a/src/components/Profile/Tabs/SecondTab/Radiobtn/Radiobtn.tsx b/src/components/Profile/Tabs/SecondTab/Radiobtn/Radiobtn.tsx
new file mode 100644
index 0000000..8382a9d
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/Radiobtn/Radiobtn.tsx
@@ -0,0 +1,41 @@
+import classNames from 'classnames';
+import styles from '../secondTab.module.scss';
+import { ReactElement } from 'react';
+
+interface IBtnProps {
+ isShipping: boolean;
+ setIsShipping: (value?: boolean) => void;
+ value: string;
+ label: string;
+ callback: () => void;
+}
+
+function Radiobtn({ isShipping, setIsShipping, value, label, callback }: IBtnProps): ReactElement {
+ const hasActiveClass = isShipping ? styles.toggleShipping__btn_active : '';
+
+ const handleRadioChange = (): void => {
+ if (!hasActiveClass) {
+ setIsShipping(!isShipping);
+ if (callback) {
+ callback();
+ }
+ }
+ };
+
+ return (
+
+ setIsShipping(!isShipping)}
+ onClick={handleRadioChange}
+ />
+
+
+ );
+}
+
+export default Radiobtn;
diff --git a/src/components/Profile/Tabs/SecondTab/Radiobtn/index.ts b/src/components/Profile/Tabs/SecondTab/Radiobtn/index.ts
new file mode 100644
index 0000000..8e7757c
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/Radiobtn/index.ts
@@ -0,0 +1,2 @@
+import Radiobtn from './Radiobtn';
+export { Radiobtn };
diff --git a/src/components/Profile/Tabs/SecondTab/SecondTab.tsx b/src/components/Profile/Tabs/SecondTab/SecondTab.tsx
new file mode 100644
index 0000000..fc03923
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/SecondTab.tsx
@@ -0,0 +1,218 @@
+import styles from './secondTab.module.scss';
+import { Formik } from 'formik';
+import { MouseEvent, ReactElement, useEffect, useState } from 'react';
+import { Button } from '../../../shared/Button';
+import { initialEditAddresses } from '../../../../constant';
+import { CustomerAddress } from './CustomerAdress';
+import { AddressForm } from '../../../RegisterForm/AddressForm';
+import classNames from 'classnames';
+import { IAddressForm } from '../../../RegisterForm/AddressForm/AddressForm';
+import { useIsEditMode, useUpdateEditMode } from '../../../../pages/Profile/profileContext';
+import { Radiobtn } from './Radiobtn';
+import { useSelector } from 'react-redux';
+import { selectEditUserInfo, selectUserData } from '../../../../store/user/selectors';
+import { useAppDispatch } from '../../../../store/store';
+import { getUserThunk } from '../../../../store/user/thunks';
+import { Address } from '@commercetools/platform-sdk';
+import { IAddNewAddress, IRemoveAddress, ISetDefaultAddress } from '../../../../services/sdk/customer/types';
+import { removeAddressThunk } from '../../../../store/user/thunks';
+import { setDefaultAddressIdThunk } from '../../../../store/user/thunks';
+import { addNewAddressThunk } from '../../../../store/user/thunks';
+import { ErrorMessage } from '../../../shared/ErrorMessage';
+import { deleteSuccessState, resetEditError } from '../../../../store/user/slice';
+
+export interface IAddressesProfile {
+ shipping: IAddressForm;
+ billing: IAddressForm;
+}
+
+function SecondTab(): ReactElement {
+ const [isShipping, setIsShipping] = useState(true);
+ const isEditMode = useIsEditMode();
+ const updateEditMode = useUpdateEditMode();
+ const user = useSelector(selectUserData);
+ const dispatch = useAppDispatch();
+ const { editStatus, editError, isSuccess } = useSelector(selectEditUserInfo);
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+
+ if (editStatus === 'success' && isEditMode) {
+ updateEditMode();
+ const timer = setTimeout(() => {
+ dispatch(deleteSuccessState());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+
+ if (editStatus === 'success' && !isEditMode) {
+ const timer = setTimeout(() => {
+ dispatch(deleteSuccessState());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+
+ if (editError) {
+ const timer = setTimeout(() => {
+ dispatch(resetEditError());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [user, dispatch, editStatus, editError]);
+
+ function handleSubmit(values: IAddressesProfile): void {
+ const isShippingAddress = values.shipping.city !== '';
+ const isDefault = isShippingAddress ? values.shipping.isDefault : values.billing.isDefault;
+ if (isDefault !== undefined) {
+ const addNewAddressData: IAddNewAddress = {
+ id: user.id,
+ version: user.version,
+ action: [
+ {
+ action: 'addAddress',
+ address: {
+ streetName: isShippingAddress ? values.shipping.streetName : values.billing.streetName,
+ city: isShippingAddress ? values.shipping.city : values.billing.city,
+ postalCode: isShippingAddress ? values.shipping.postalCode : values.billing.postalCode,
+ country: isShippingAddress ? values.shipping.country : values.billing.country,
+ },
+ },
+ ],
+ isShipping: isShippingAddress,
+ isDefault: isDefault,
+ };
+ dispatch(addNewAddressThunk(addNewAddressData));
+ }
+ }
+
+ function deleteAddress(addressId: string): void {
+ const addressData: IRemoveAddress = { version: user.version, customerId: user.id, addressId: addressId };
+
+ dispatch(removeAddressThunk(addressData));
+ }
+
+ function setDefaultAddress(e: MouseEvent, addressId: string): void {
+ const clickedElement = e.target;
+
+ if (clickedElement && clickedElement instanceof HTMLElement) {
+ const clickElIsActive = Array.from(clickedElement.classList).some((el) => el.includes('active'));
+ if (clickElIsActive) {
+ return;
+ }
+
+ const isShipAddress = clickedElement.getAttribute('data-isshipping') === 'true';
+ const thunkData: ISetDefaultAddress = {
+ customerId: user.id,
+ version: user.version,
+ addressId,
+ isShipping: isShipAddress,
+ };
+ dispatch(setDefaultAddressIdThunk(thunkData));
+ }
+ }
+
+ return (
+
+ {isEditMode && (
+ <>
+
+ {({ handleSubmit, handleChange, setFieldTouched, touched, errors, values, resetForm }): ReactElement => (
+ <>
+
+ setIsShipping(true)}
+ value={'shipping'}
+ label="Shipping address"
+ callback={resetForm}
+ >
+ setIsShipping(false)}
+ value={'billing'}
+ label="Billing address"
+ callback={resetForm}
+ >
+
+
+ >
+ )}
+
+ {editError && (
+
+ )}
+ >
+ )}
+
+ {!isEditMode && (
+
+ {isSuccess && (
+
Profile information was successfully updated ٩(。•́‿•̀。)۶
+ )}
+
+ )}
+
+ );
+}
+
+export default SecondTab;
diff --git a/src/components/Profile/Tabs/SecondTab/index.ts b/src/components/Profile/Tabs/SecondTab/index.ts
new file mode 100644
index 0000000..01c7a1a
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/index.ts
@@ -0,0 +1,2 @@
+import SecondTab from './SecondTab';
+export { SecondTab };
diff --git a/src/components/Profile/Tabs/SecondTab/secondTab.module.scss b/src/components/Profile/Tabs/SecondTab/secondTab.module.scss
new file mode 100644
index 0000000..2ddae86
--- /dev/null
+++ b/src/components/Profile/Tabs/SecondTab/secondTab.module.scss
@@ -0,0 +1,86 @@
+@import '../../../../styles/common';
+
+.root {
+
+ &__formContainer {
+ & h3 {
+ display: none;
+ }
+
+ &_form {
+ margin-bottom: 4rem;
+ }
+ }
+
+ &__btn {
+ width: 100%;
+ height: 7rem;
+ margin-bottom: 5rem;
+
+ &:disabled {
+ opacity: .5;
+ }
+ }
+
+ &__closeBtn {
+ @include close-btn();
+ }
+
+ &__addAddressBtn {
+ @include btn-primary();
+ margin-bottom: 4rem;
+ width: 100%;
+ height: 7rem;
+ }
+}
+
+.toggleShipping {
+ display: flex;
+ margin-bottom: 2rem;
+ border-bottom: 1px solid $dark;
+ text-transform: uppercase;
+
+ &__radio {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ height: 100%;
+ opacity: 0;
+ left: 0;
+ cursor: pointer;
+ }
+
+ &__btn {
+ position: relative;
+ padding: 1.2rem 2rem;
+ line-height: 1.6rem;
+ font-size: 1.4rem;
+ font-weight: 600;
+
+ &_active {
+ border: 1px solid $dark;
+ border-bottom: none;
+
+ &::after {
+ content: '';
+ width: 100%;
+ display: block;
+ position: absolute;
+ border-bottom: 1px solid white;
+ z-index: 1;
+ height: 1px;
+ left: 0;
+ bottom: -1px;
+ }
+ }
+ }
+
+}
+
+.successResponse {
+ @include responseMessage($light-primary, $green);
+}
+
+.errorResponse {
+ @include responseMessage($light-primary, $error-color);
+}
\ No newline at end of file
diff --git a/src/components/Profile/Tabs/Tabs.tsx b/src/components/Profile/Tabs/Tabs.tsx
new file mode 100644
index 0000000..b9f7fc2
--- /dev/null
+++ b/src/components/Profile/Tabs/Tabs.tsx
@@ -0,0 +1,46 @@
+import styles from './tabs.module.scss';
+import { ReactElement, useEffect, useState } from 'react';
+import classNames from 'classnames';
+import { useIsEditMode } from '../../../pages/Profile/profileContext';
+import { tabsList } from '../../../constant/profile';
+
+function Tabs(): ReactElement {
+ const [activeTab, setActiveTab] = useState(0);
+ const isTabListHidden = useIsEditMode();
+
+ useEffect(() => {
+ const storedActiveTab = localStorage.getItem('activeTab');
+ if (storedActiveTab !== null) {
+ setActiveTab(parseInt(storedActiveTab, 10));
+ }
+ }, []);
+
+ const handleTabClick = function (index: number): void {
+ setActiveTab(index);
+ localStorage.setItem('activeTab', index.toString());
+ };
+
+ return (
+
+ {isTabListHidden &&
{tabsList[activeTab].title}
}
+
+ {tabsList.map((tab, index) => {
+ return (
+
handleTabClick(index)}
+ >
+ {tab.label}
+
+ );
+ })}
+
+
{tabsList[activeTab].content}
+
+ );
+}
+
+export default Tabs;
diff --git a/src/components/Profile/Tabs/ThirdTab/ThirdTab.tsx b/src/components/Profile/Tabs/ThirdTab/ThirdTab.tsx
new file mode 100644
index 0000000..7cd668e
--- /dev/null
+++ b/src/components/Profile/Tabs/ThirdTab/ThirdTab.tsx
@@ -0,0 +1,130 @@
+import { Formik } from 'formik';
+import { ReactElement, useEffect } from 'react';
+import { PasswordField } from '../../../shared/PasswordField';
+import { Input } from '../../../../types/enums';
+import { Button } from '../../../shared/Button';
+import styles from './thirdTab.module.scss';
+import { initialChangePassord } from '../../../../constant';
+import { useIsEditMode, useUpdateEditMode } from '../../../../pages/Profile/profileContext';
+import { useSelector } from 'react-redux';
+import { selectEditUserInfo, selectUserData } from '../../../../store/user/selectors';
+import { useAppDispatch } from '../../../../store/store';
+import { getUserThunk, updPasswordThunk } from '../../../../store/user/thunks';
+import { IUpdPasswordData } from '../../../../store/user/types';
+import { ErrorMessage } from '../../../shared/ErrorMessage';
+import { deleteSuccessState, resetEditError } from '../../../../store/user/slice';
+
+export interface IChangePassword {
+ password: string;
+ newPassword: string;
+}
+
+function ThirdTab(): ReactElement {
+ const isEditMode = useIsEditMode();
+ const updateEditMode = useUpdateEditMode();
+ const user = useSelector(selectUserData);
+ const dispatch = useAppDispatch();
+ const { editStatus, editError, isSuccess } = useSelector(selectEditUserInfo);
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+
+ if (editStatus === 'success') {
+ updateEditMode();
+ const timer = setTimeout(() => {
+ dispatch(deleteSuccessState());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+
+ if (editError) {
+ const timer = setTimeout(() => {
+ dispatch(resetEditError());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [user, dispatch, editStatus, editError]);
+
+ function handleSubmit(values: IChangePassword): void {
+ const updPassData: IUpdPasswordData = {
+ email: user.email,
+ passwordData: {
+ id: user.id,
+ version: user.version,
+ currentPassword: values.password,
+ newPassword: values.newPassword,
+ },
+ };
+ dispatch(updPasswordThunk(updPassData));
+ }
+
+ return (
+
+ {!isEditMode && (
+
+
+
+ {isSuccess && (
+
Profile information was successfully updated ٩(。•́‿•̀。)۶
+ )}
+
+ )}
+ {isEditMode && (
+ <>
+
+ {({ handleSubmit, errors, touched, setFieldTouched, values }): ReactElement => (
+
+ )}
+
+ {editError && (
+
+ )}
+ >
+ )}
+
+ );
+}
+
+export default ThirdTab;
diff --git a/src/components/Profile/Tabs/ThirdTab/index.ts b/src/components/Profile/Tabs/ThirdTab/index.ts
new file mode 100644
index 0000000..cc2fdde
--- /dev/null
+++ b/src/components/Profile/Tabs/ThirdTab/index.ts
@@ -0,0 +1,2 @@
+import ThirdTab from './ThirdTab';
+export { ThirdTab };
diff --git a/src/components/Profile/Tabs/ThirdTab/thirdTab.module.scss b/src/components/Profile/Tabs/ThirdTab/thirdTab.module.scss
new file mode 100644
index 0000000..503049e
--- /dev/null
+++ b/src/components/Profile/Tabs/ThirdTab/thirdTab.module.scss
@@ -0,0 +1,53 @@
+@import '../../../../styles/common';
+
+.root {
+
+ &__editBtn {
+ @include btn-primary();
+ width: 100%;
+ height: 7rem;
+ }
+
+ &__label {
+ @include font(semibold, p1, $dark);
+ margin-bottom: 0.5rem;
+ line-height: 2.2rem;
+ }
+
+ &__input {
+ margin-bottom: 4rem;
+ background-color: inherit;
+ border: none;
+ }
+}
+
+.rootEdit {
+
+ &__input {
+ margin-bottom: 1rem;
+ }
+
+ &__closeBtn {
+ @include close-btn()
+ }
+}
+
+.form {
+
+ &__button {
+ width: 100%;
+ height: 7rem;
+
+ &:disabled {
+ opacity: 0.5;
+ }
+ }
+}
+
+.successResponse {
+ @include responseMessage($light-primary, $green);
+}
+
+.errorResponse {
+ @include responseMessage($light-primary, $error-color);
+}
\ No newline at end of file
diff --git a/src/components/Profile/Tabs/index.ts b/src/components/Profile/Tabs/index.ts
new file mode 100644
index 0000000..8a678cf
--- /dev/null
+++ b/src/components/Profile/Tabs/index.ts
@@ -0,0 +1,2 @@
+import Tabs from './Tabs';
+export { Tabs };
diff --git a/src/components/Profile/Tabs/tabs.module.scss b/src/components/Profile/Tabs/tabs.module.scss
new file mode 100644
index 0000000..c2b6dce
--- /dev/null
+++ b/src/components/Profile/Tabs/tabs.module.scss
@@ -0,0 +1,54 @@
+@import '../../../styles/common';
+
+.root {
+ &__title {
+ margin-bottom: 6rem;
+ padding-top: 2rem;
+ font-size: 4rem;
+ line-height: 4.4rem;
+ }
+}
+
+.tabsList {
+ display: flex;
+ margin-bottom: 4rem;
+ border-bottom: 1px solid $dark;
+
+ &_hidden {
+ display: none;
+ }
+
+ &__tab {
+ position: relative;
+ padding: 1.2rem 2rem;
+ line-height: 120%;
+ font-size: 1.4rem;
+ font-weight: 600;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+
+ @media(max-width: 800px) {
+ padding: 1.2rem 1.5rem;
+ }
+
+ &_active {
+ border: 1px solid $dark;
+ border-bottom: none;
+
+ &::after {
+ content: '';
+ width: 100%;
+ display: block;
+ position: absolute;
+ border-bottom: 2px solid white;
+ z-index: 1;
+ height: 1px;
+ left: 0;
+ bottom: -1px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/RegisterForm/AddressForm/AddressForm.tsx b/src/components/RegisterForm/AddressForm/AddressForm.tsx
new file mode 100644
index 0000000..cca0466
--- /dev/null
+++ b/src/components/RegisterForm/AddressForm/AddressForm.tsx
@@ -0,0 +1,102 @@
+import { ChangeEvent, ReactElement } from 'react';
+import { Field, FormikErrors, FormikTouched } from 'formik';
+import classNames from 'classnames';
+import { InputField } from '../../shared/InputField';
+import { SelectField } from '../../shared/SelectField';
+import { Input } from '../../../types/enums';
+import { INewAddress } from '../../../types/interfaces';
+import { countries } from '../../../constant';
+import { cityValidate, countryValidate, streetValidate, zipCodeValidate } from '../../../utils/validation';
+
+import styles from './addressForm.module.scss';
+
+export interface IAddressForm extends INewAddress {
+ isDefault?: boolean;
+}
+
+interface IAddressFormProps {
+ type: 'billing' | 'shipping';
+ handleChange: (e?: ChangeEvent) => void;
+ values: IAddressForm;
+ touched: FormikTouched | undefined;
+ errors: FormikErrors | undefined;
+ setFieldTouched: (field: string, isTouched?: boolean | undefined) => void;
+ className?: string;
+ isDisabled?: boolean;
+}
+
+function AddressForm({
+ type,
+ values,
+ errors,
+ handleChange,
+ touched,
+ setFieldTouched,
+ className,
+ isDisabled,
+}: IAddressFormProps): ReactElement {
+ return (
+
+
{`${type[0].toUpperCase() + type.slice(1).toLowerCase()} address`}
+
+
+
+
+
+
+
+
+ {errors?.[Input.Country] && touched?.[Input.Country] ? (
+ {errors[Input.Country]}
+ ) : null}
+
+
+
+
+
+
+ );
+}
+
+export default AddressForm;
diff --git a/src/components/RegisterForm/AddressForm/addressForm.module.scss b/src/components/RegisterForm/AddressForm/addressForm.module.scss
new file mode 100644
index 0000000..7503795
--- /dev/null
+++ b/src/components/RegisterForm/AddressForm/addressForm.module.scss
@@ -0,0 +1,33 @@
+@import 'src/styles/common';
+
+.address {
+ max-width: 100%;
+ display: flex;
+ flex-direction: column;
+
+ &__title {
+ @include font(semibold, h3, $dark);
+ line-height: 1;
+ margin-bottom: 1.5rem;
+ }
+
+ &__select {
+ width: 100%;
+ position: relative;
+ }
+
+ &__error {
+ @include input-error();
+ }
+
+ &__checkfield {
+ @include check-field();
+
+ width: max-content;
+ padding-top: 2rem;
+ }
+
+ &__checkbox {
+ @include checkbox();
+ }
+}
diff --git a/src/components/RegisterForm/AddressForm/index.ts b/src/components/RegisterForm/AddressForm/index.ts
new file mode 100644
index 0000000..26abaaa
--- /dev/null
+++ b/src/components/RegisterForm/AddressForm/index.ts
@@ -0,0 +1,3 @@
+import AddressForm from './AddressForm';
+
+export { AddressForm };
diff --git a/src/components/RegisterForm/RegisterForm.tsx b/src/components/RegisterForm/RegisterForm.tsx
new file mode 100644
index 0000000..24cf6dd
--- /dev/null
+++ b/src/components/RegisterForm/RegisterForm.tsx
@@ -0,0 +1,138 @@
+import { ReactElement, useState } from 'react';
+import { Formik, FormikErrors } from 'formik';
+import { UserForm } from './UserForm';
+import { IUserForm } from './UserForm/UserForm';
+import { AddressForm } from './AddressForm';
+import { IAddressForm } from './AddressForm/AddressForm';
+import { Button } from '../shared/Button';
+import { useAppDispatch } from '../../store/store';
+import { registerThunk } from '../../store/auth/thunks';
+import { initialRegisterForm } from '../../constant';
+import { getMonthIndex } from '../../utils';
+import { dateMYValidate } from '../../utils/validation';
+import { INewAddress, INewUser } from '../../types/interfaces';
+import { Input } from '../../types/enums';
+
+import styles from './registerForm.module.scss';
+import classnames from 'classnames';
+
+export interface IRegisterForm {
+ user: IUserForm;
+ shipping: IAddressForm;
+ billing: IAddressForm;
+}
+
+function RegisterForm(): ReactElement {
+ const [isSameAddress, setIsSameAddress] = useState(true);
+
+ const dispatch = useAppDispatch();
+ function onSubmit(values: IRegisterForm): void {
+ const { user, shipping, billing } = values;
+ const addresses: INewAddress[] = [
+ {
+ streetName: shipping.streetName.trim(),
+ city: shipping.city.trim(),
+ postalCode: shipping.postalCode.trim(),
+ country: shipping.country.trim(),
+ },
+ ];
+ if (!isSameAddress) {
+ addresses.push({
+ streetName: billing.streetName.trim(),
+ city: billing.city.trim(),
+ postalCode: billing.postalCode.trim(),
+ country: billing.country.trim(),
+ });
+ }
+
+ const billingIndex: number = isSameAddress ? 0 : 1;
+ let defaultBillIndex: number | undefined;
+ if (isSameAddress) {
+ defaultBillIndex = values.shipping.isDefault ? billingIndex : undefined;
+ } else {
+ defaultBillIndex = values.billing.isDefault ? billingIndex : undefined;
+ }
+
+ const newUser: INewUser = {
+ email: user.email.trim(),
+ password: user.password.trim(),
+ firstName: user.firstName.trim(),
+ lastName: user.lastName.trim(),
+ dateOfBirth: `${user.year}-${getMonthIndex(user.month)}-${user.date}`,
+ addresses,
+ shippingAddresses: [0],
+ defaultShippingAddress: values.shipping.isDefault ? 0 : undefined,
+ billingAddresses: [billingIndex],
+ defaultBillingAddress: defaultBillIndex,
+ };
+
+ dispatch(registerThunk(newUser));
+ }
+
+ function validateDate(values: IRegisterForm): void | object | Promise> {
+ const errors: FormikErrors = {};
+ const user: Record = {};
+
+ user[Input.Date] = dateMYValidate(
+ `${values.user[Input.Date]}${values.user[Input.Month]}${values.user[Input.Year]}`,
+ );
+
+ if (user[Input.Date]) {
+ errors.user = user;
+ }
+
+ return errors;
+ }
+
+ return (
+
+ {({ handleChange, handleSubmit, values, touched, setFieldTouched, errors }): ReactElement => (
+
+ )}
+
+ );
+}
+
+export default RegisterForm;
diff --git a/src/components/RegisterForm/UserForm/UserForm.tsx b/src/components/RegisterForm/UserForm/UserForm.tsx
new file mode 100644
index 0000000..3745de0
--- /dev/null
+++ b/src/components/RegisterForm/UserForm/UserForm.tsx
@@ -0,0 +1,109 @@
+import { ChangeEvent, ReactElement } from 'react';
+import classNames from 'classnames';
+import { FormikErrors, FormikTouched } from 'formik';
+import { InputField } from '../../shared/InputField';
+import { SelectField } from '../../shared/SelectField';
+import { PasswordField } from '../../shared/PasswordField';
+import { Input } from '../../../types/enums';
+import { dates, months, years } from '../../../constant';
+import { emailValidate, lastNameValidate, nameValidate } from '../../../utils/validation';
+
+import styles from './userForm.module.scss';
+
+export interface IUserForm {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ date: string;
+ month: string;
+ year: string;
+}
+
+export interface IUserFormProps {
+ handleChange: (e?: ChangeEvent) => void;
+ values: IUserForm;
+ touched: FormikTouched | undefined;
+ errors: FormikErrors | undefined;
+ setFieldTouched: (field: string, isTouched?: boolean | undefined) => void;
+ className?: string;
+ isDisabled?: boolean;
+}
+
+export default function UserForm({
+ handleChange,
+ className,
+ values,
+ touched,
+ setFieldTouched,
+ errors,
+}: IUserFormProps): ReactElement {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {errors?.date && touched?.date ?
{errors.date} : null}
+
+
+ );
+}
diff --git a/src/components/RegisterForm/UserForm/index.ts b/src/components/RegisterForm/UserForm/index.ts
new file mode 100644
index 0000000..6586a3b
--- /dev/null
+++ b/src/components/RegisterForm/UserForm/index.ts
@@ -0,0 +1,2 @@
+import UserForm from './UserForm.tsx';
+export { UserForm };
diff --git a/src/components/RegisterForm/UserForm/userForm.module.scss b/src/components/RegisterForm/UserForm/userForm.module.scss
new file mode 100644
index 0000000..4769da0
--- /dev/null
+++ b/src/components/RegisterForm/UserForm/userForm.module.scss
@@ -0,0 +1,32 @@
+@import 'src/styles/common';
+
+.user {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.selects {
+ width: 100%;
+ position: relative;
+
+ &__item {
+ position: relative;
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ gap: 2.5rem;
+
+ @media all and (max-width: 480px) {
+ gap: 2rem;
+ }
+
+ @media all and (max-width: 390px) {
+ gap: 1.5rem;
+ }
+ }
+
+ &__error {
+ @include input-error();
+ }
+}
diff --git a/src/components/RegisterForm/index.ts b/src/components/RegisterForm/index.ts
new file mode 100644
index 0000000..ada2e6e
--- /dev/null
+++ b/src/components/RegisterForm/index.ts
@@ -0,0 +1,3 @@
+import RegisterForm from './RegisterForm';
+
+export { RegisterForm };
diff --git a/src/components/RegisterForm/registerForm.module.scss b/src/components/RegisterForm/registerForm.module.scss
new file mode 100644
index 0000000..c085a5f
--- /dev/null
+++ b/src/components/RegisterForm/registerForm.module.scss
@@ -0,0 +1,20 @@
+@import 'src/styles/common';
+
+.form {
+ display: flex;
+ flex-direction: column;
+
+ &__subform {
+ margin-bottom: 6rem;
+ }
+
+ &__checkfield {
+ @include check-field();
+ width: max-content;
+ padding-top: 1rem;
+ }
+
+ &__checkbox {
+ @include checkbox();
+ }
+}
diff --git a/src/components/SearchField/SearchField.tsx b/src/components/SearchField/SearchField.tsx
new file mode 100644
index 0000000..39c0c5c
--- /dev/null
+++ b/src/components/SearchField/SearchField.tsx
@@ -0,0 +1,57 @@
+import { ChangeEvent, ReactElement, useState } from 'react';
+import { SetURLSearchParams, useSearchParams } from 'react-router-dom';
+import { useDebounce } from '../../hooks';
+import { SearchParams } from '../../types/enums';
+import { changeParams, getSearchParams } from '../../utils';
+import { useAppDispatch } from '../../store/store';
+import { productListThunk } from '../../store/catalog/thunks';
+
+import styles from './searchField.module.scss';
+
+function SearchField(): ReactElement {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [value, setValue] = useState(searchParams.get(SearchParams.Search) || '');
+ const dispatch = useAppDispatch();
+
+ function searchProductList(setSearchParams: SetURLSearchParams, value: string, field: SearchParams): void {
+ changeParams(setSearchParams, value, field);
+ changeParams(setSearchParams, '1', SearchParams.Page);
+ dispatch(productListThunk({ params: getSearchParams(searchParams), list: [] }));
+ }
+ const debounceChangeParams = useDebounce(searchProductList, 500);
+
+ function handleChange(event: ChangeEvent): void {
+ const newValue = event.target.value;
+ setValue(newValue);
+ debounceChangeParams(setSearchParams, newValue, SearchParams.Search);
+ }
+
+ function handleClick(): void {
+ setValue('');
+ debounceChangeParams(setSearchParams, '', SearchParams.Search);
+ }
+
+ return (
+
+ );
+}
+
+export default SearchField;
diff --git a/src/components/SearchField/index.ts b/src/components/SearchField/index.ts
new file mode 100644
index 0000000..e0c17b7
--- /dev/null
+++ b/src/components/SearchField/index.ts
@@ -0,0 +1,3 @@
+import SearchField from './SearchField';
+
+export { SearchField };
diff --git a/src/components/SearchField/searchField.module.scss b/src/components/SearchField/searchField.module.scss
new file mode 100644
index 0000000..05b4380
--- /dev/null
+++ b/src/components/SearchField/searchField.module.scss
@@ -0,0 +1,46 @@
+@import 'src/styles/common';
+
+.search {
+ position: relative;
+ width: 100%;
+
+ &__label {
+ width: 100%;
+ }
+
+ &__input {
+ width: 100%;
+ border: none;
+ border-bottom: 0.1rem solid $dark;
+ padding: 0.5rem 0 0.5rem;
+ background-color: transparent;
+ transition: all 0.3s ease-in-out;
+
+ &::placeholder {
+ color: $placeholder-grey;
+ opacity: 0.7;
+ }
+
+ &:focus {
+ background-color: rgba($primary, 0.1);
+ }
+
+ &:hover {
+ border-bottom-color: $primary;
+ }
+ }
+
+ &__btn {
+ position: absolute;
+ text-align: right;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ cursor: pointer;
+ transition: all 0.2s ease-in;
+
+ &:hover {
+ color: $primary;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx
new file mode 100644
index 0000000..3810cff
--- /dev/null
+++ b/src/components/Slider/Slider.tsx
@@ -0,0 +1,56 @@
+import { ReactElement } from 'react';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import { Navigation, Pagination } from 'swiper/modules';
+
+import 'swiper/scss';
+import 'swiper/scss/pagination';
+import 'swiper/scss/navigation';
+
+import styles from './slider.module.scss';
+import classnames from 'classnames';
+import { Button } from '../shared/Button';
+
+export interface SliderProps {
+ images: string[] | undefined;
+ handleClick: () => void;
+ fullscreen: boolean;
+ className?: string;
+}
+
+export function Slider({ images, fullscreen, handleClick, className }: SliderProps): ReactElement {
+ return (
+
+ {fullscreen && (
+
+ )}
+
+
{
+ if (!fullscreen) {
+ handleClick();
+ }
+ }}
+ >
+ {images?.map((image) => (
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/Slider/slider.module.scss b/src/components/Slider/slider.module.scss
new file mode 100644
index 0000000..6cc4e99
--- /dev/null
+++ b/src/components/Slider/slider.module.scss
@@ -0,0 +1,159 @@
+@import '../../styles/common/';
+
+.product__slider {
+ width: 50vw;
+ user-select: none;
+
+ @media all and (max-width: 850px) {
+ width: 100vw;
+ }
+
+ &_button,
+ &_button:hover {
+ height: 4rem;
+ padding: 0 2rem;
+ font-size: 2rem;
+ position: fixed;
+ right: 2rem;
+ top: 2rem;
+ z-index: 110;
+ }
+
+ :global(.swiper) {
+ width: 100%;
+ margin-left: 0;
+
+ .slide {
+ position: relative;
+ width: 100%;
+
+ img {
+ display: block;
+ position: relative;
+ width: 100%;
+ object-fit: cover;
+ }
+ }
+ }
+}
+
+.product__slider_regular {
+ width: 100%;
+ height: $slider-height;
+ cursor: zoom-in;
+
+ @media all and (max-width: 850px) {
+ max-width: 100vw;
+ }
+
+ @media all and (max-width: 480px) {
+ height: calc(100vh - 5rem);
+ }
+
+ .slide {
+ img {
+ height: $slider-height;
+
+ @media all and (max-width: 480px) {
+ height: calc(100vh - 5rem);
+ }
+ }
+ }
+}
+
+.product__slider_fullscreen {
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ padding: 0;
+ height: 100vh;
+ z-index: 100;
+
+ .slide {
+ height: 100vh;
+ overflow-y: scroll;
+
+ img {
+ min-height: 100vh;
+ height: auto;
+ object-fit: cover;
+ cursor: all-scroll;
+ }
+ }
+
+ @media all and (max-width: 850px) {
+ .slide {
+ overflow: auto;
+
+ img {
+ min-width: 180vw;
+ height: auto;
+ object-fit: contain;
+ }
+ }
+ }
+}
+
+// Controls
+
+.product__slider_regular,
+.product__slider_fullscreen {
+ :global(.swiper-button-next),
+ :global(.swiper-button-prev) {
+ width: 4rem;
+ height: 4rem;
+ top: auto;
+ bottom: 2rem;
+ left: auto;
+ right: 1.5rem;
+ cursor: pointer;
+ z-index: 11;
+ }
+
+ :global(.swiper-button-prev) {
+ left: 1.5rem;
+ }
+
+ :global(.swiper-button-next::after),
+ :global(.swiper-button-prev::after) {
+ content: '';
+ background-image: url('../../assets/icons/arrow-right.svg');
+ background-position: center;
+ background-size: contain;
+ background-repeat: no-repeat;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+ }
+
+ :global(.swiper-button-prev::after) {
+ transform: rotate(-180deg);
+ }
+
+ :global(.swiper-pagination) {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ gap: 1rem;
+ bottom: 3.5rem;
+ }
+
+ :global(.swiper-pagination-bullet) {
+ display: block;
+ width: 0.8rem;
+ height: 0.8rem;
+ margin: 0;
+ background-color: $primary;
+ opacity: 0.2;
+ }
+
+ :global(.swiper-pagination-bullet-active) {
+ background-color: rgba($primary, 0.9);
+ opacity: 1;
+ }
+}
diff --git a/src/components/SortingField/SortingField.tsx b/src/components/SortingField/SortingField.tsx
new file mode 100644
index 0000000..385b90c
--- /dev/null
+++ b/src/components/SortingField/SortingField.tsx
@@ -0,0 +1,40 @@
+import { ChangeEvent, ReactElement, useState } from 'react';
+import { sorting } from '../../constant';
+import { useSearchParams } from 'react-router-dom';
+import { SearchParams } from '../../types/enums';
+import { changeParams, getSearchParams } from '../../utils';
+import { useAppDispatch } from '../../store/store';
+import { productListThunk } from '../../store/catalog/thunks';
+
+import styles from './sortingField.module.scss';
+
+function SortingField(): ReactElement {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [sort, setSort] = useState(searchParams.get(SearchParams.Sort) || '');
+ const dispatch = useAppDispatch();
+
+ function handleChange(event: ChangeEvent): void {
+ const newValue = event.target.value;
+ setSort(newValue);
+ changeParams(setSearchParams, newValue, SearchParams.Sort);
+ changeParams(setSearchParams, '1', SearchParams.Page);
+ dispatch(productListThunk({ params: getSearchParams(searchParams), list: [] }));
+ }
+
+ return (
+
+ );
+}
+
+export default SortingField;
diff --git a/src/components/SortingField/index.ts b/src/components/SortingField/index.ts
new file mode 100644
index 0000000..6e35abb
--- /dev/null
+++ b/src/components/SortingField/index.ts
@@ -0,0 +1,3 @@
+import SortingField from './SortingField';
+
+export { SortingField };
diff --git a/src/components/SortingField/sortingField.module.scss b/src/components/SortingField/sortingField.module.scss
new file mode 100644
index 0000000..3ac5ad0
--- /dev/null
+++ b/src/components/SortingField/sortingField.module.scss
@@ -0,0 +1,55 @@
+@import 'src/styles/common';
+
+.sort {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+
+ &__label {
+ position: relative;
+
+ &:hover::after {
+ background-color: $primary;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 1rem;
+ right: 0;
+ width: 0.8em;
+ height: 0.5em;
+ background-color: $dark;
+ clip-path: polygon(100% 0%, 0 0%, 50% 100%);
+ pointer-events: none;
+ transition: all 0.2s ease-in;
+ }
+ }
+
+ &__input {
+ @include font(semibold, p1, $dark);
+ min-width: 20rem;
+ appearance: none;
+ -webkit-appearance: none;
+ position: relative;
+ padding: 0.5rem 0 0.5rem;
+ display: flex;
+ flex: 1;
+ border: none;
+ border-bottom: 0.1rem solid $dark;
+ background-color: transparent;
+ text-transform: capitalize;
+ margin: 0;
+ cursor: pointer;
+ transition: border-color 0.3s ease-in-out;
+
+ &:focus {
+ background-color: rgba($primary, 0.05);
+ }
+
+ &:hover {
+ border-bottom-color: $primary;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/shared/Accordion/Accordion.test.tsx b/src/components/shared/Accordion/Accordion.test.tsx
new file mode 100644
index 0000000..1406d79
--- /dev/null
+++ b/src/components/shared/Accordion/Accordion.test.tsx
@@ -0,0 +1,17 @@
+import { render } from '@testing-library/react';
+import Accordion from './Accordion';
+jest.mock('../../../constant/metaData.ts', () => {});
+
+const exampleData = [
+ { title: 'Section 1', content: 'Content 1' },
+ { title: 'Section 2', content: 'Content 2' },
+];
+
+describe('Test Accordion shared component', () => {
+ test('Should Accordion component renders with provided data', () => {
+ const { getByText } = render();
+
+ expect(getByText('Section 1')).toBeInTheDocument();
+ expect(getByText('Section 2')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/shared/Accordion/Accordion.tsx b/src/components/shared/Accordion/Accordion.tsx
new file mode 100644
index 0000000..f051bcb
--- /dev/null
+++ b/src/components/shared/Accordion/Accordion.tsx
@@ -0,0 +1,18 @@
+import { ReactElement } from 'react';
+import { IAccordionItem } from './AccordionItem/AccordionItem';
+import { AccordionItem } from './AccordionItem';
+
+export interface IAccordionProps {
+ data: IAccordionItem[];
+ className: string;
+}
+
+export default function Accordion({ data, className }: IAccordionProps): ReactElement {
+ return (
+
+ {data.map((item) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/shared/Accordion/AccordionItem/AccordionItem.test.tsx b/src/components/shared/Accordion/AccordionItem/AccordionItem.test.tsx
new file mode 100644
index 0000000..35e61dc
--- /dev/null
+++ b/src/components/shared/Accordion/AccordionItem/AccordionItem.test.tsx
@@ -0,0 +1,32 @@
+import { render, fireEvent } from '@testing-library/react';
+import { AccordionItem } from '.';
+jest.mock('../../../../constant/metaData.ts', () => {});
+
+const exampleItem = {
+ title: 'Example Title',
+ content: 'Example Content',
+};
+
+describe('Test AccordionItem shared component', () => {
+ test('Should renders the AccordionItem component with closed state by default', () => {
+ const { getByText, queryByText } = render();
+
+ const titleElement = getByText(exampleItem.title);
+ const contentElement = queryByText(exampleItem.content);
+
+ expect(titleElement).toBeInTheDocument();
+ expect(contentElement).toBeNull();
+ });
+
+ test('Should toggles the AccordionItem content when clicked', () => {
+ const { getByText, queryByText } = render();
+
+ const titleElement = getByText(exampleItem.title);
+
+ fireEvent.click(titleElement);
+
+ const contentElement = queryByText(exampleItem.content);
+
+ expect(contentElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/shared/Accordion/AccordionItem/AccordionItem.tsx b/src/components/shared/Accordion/AccordionItem/AccordionItem.tsx
new file mode 100644
index 0000000..335ce48
--- /dev/null
+++ b/src/components/shared/Accordion/AccordionItem/AccordionItem.tsx
@@ -0,0 +1,30 @@
+import { ReactElement, useState } from 'react';
+import { splitToParagraphs } from '../../../../utils';
+
+export interface IAccordionItem {
+ title: string;
+ content: string;
+}
+
+import styles from './accordionItem.module.scss';
+import classnames from 'classnames';
+
+export default function AccordionItem({ title, content }: IAccordionItem): ReactElement {
+ const [isOpen, setIsOpen] = useState(false);
+
+ function toggleAccordion(): void {
+ setIsOpen(!isOpen);
+ }
+
+ return (
+
+
+
{title}
+
+
+ {isOpen && {splitToParagraphs(content)}
}
+
+ );
+}
diff --git a/src/components/shared/Accordion/AccordionItem/accordionItem.module.scss b/src/components/shared/Accordion/AccordionItem/accordionItem.module.scss
new file mode 100644
index 0000000..2428c30
--- /dev/null
+++ b/src/components/shared/Accordion/AccordionItem/accordionItem.module.scss
@@ -0,0 +1,55 @@
+@import '../../../../styles/common/';
+
+.accordion__item {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid $bullet-grey;
+ cursor: pointer;
+
+ &:last-child {
+ border-bottom: 1px solid $bullet-grey;
+ }
+
+ &_header {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 2rem 0;
+ user-select: none;
+
+ .title {
+ font-size: 2rem;
+ }
+
+ .icon__toggle {
+ display: block;
+ padding-left: 5px;
+ width: 2rem;
+ height: 2rem;
+ background-position: center;
+ background-size: contain;
+ background-repeat: no-repeat;
+ margin-bottom: -2px;
+
+ &_down {
+ background-image: url('../../../../assets/icons/arrow-right.svg');
+ transform: rotate(90deg);
+ }
+
+ &_up {
+ background-image: url('../../../../assets/icons/arrow-right.svg');
+ transform: rotate(-90deg);
+ }
+ }
+ }
+
+ &_content {
+ width: 95%;
+ padding-bottom: 4rem;
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ }
+}
diff --git a/src/components/shared/Accordion/AccordionItem/index.ts b/src/components/shared/Accordion/AccordionItem/index.ts
new file mode 100644
index 0000000..8144cb1
--- /dev/null
+++ b/src/components/shared/Accordion/AccordionItem/index.ts
@@ -0,0 +1,2 @@
+import AccordionItem from './AccordionItem';
+export { AccordionItem };
diff --git a/src/components/shared/Accordion/index.ts b/src/components/shared/Accordion/index.ts
new file mode 100644
index 0000000..4ab53da
--- /dev/null
+++ b/src/components/shared/Accordion/index.ts
@@ -0,0 +1,2 @@
+import Accordion from './Accordion';
+export { Accordion };
diff --git a/src/components/shared/Button/Button.test.tsx b/src/components/shared/Button/Button.test.tsx
new file mode 100644
index 0000000..8a2d12b
--- /dev/null
+++ b/src/components/shared/Button/Button.test.tsx
@@ -0,0 +1,19 @@
+import { render, screen } from '@testing-library/react';
+import { Button } from '.';
+
+describe('Test Button shared component', () => {
+ test('Should renders with correct props', () => {
+ const props = {
+ type: 'submit',
+ name: 'Submit',
+ className: 'submit-in-test-button',
+ } as const;
+
+ render();
+
+ const buttonElement = screen.getByRole('button', { name: /Submit/i });
+ expect(buttonElement).toBeInTheDocument();
+ expect(buttonElement).toHaveAttribute('type', 'submit');
+ expect(buttonElement).toHaveClass('submit-in-test-button');
+ });
+});
diff --git a/src/components/shared/Button/Button.tsx b/src/components/shared/Button/Button.tsx
new file mode 100644
index 0000000..fb81088
--- /dev/null
+++ b/src/components/shared/Button/Button.tsx
@@ -0,0 +1,31 @@
+import { ReactElement, MouseEvent } from 'react';
+import classnames from 'classnames';
+
+import styles from './button.module.scss';
+
+interface IButtonProps {
+ type: 'submit' | 'reset' | 'button';
+ name: string | ReactElement;
+ className?: string;
+ disabled?: boolean;
+ handleClick?: (event: MouseEvent) => void;
+}
+
+const noop = (): void => {
+ return;
+};
+
+function Button({ type, name, className, handleClick = noop, disabled }: IButtonProps): ReactElement {
+ return (
+
+ );
+}
+
+export default Button;
diff --git a/src/components/shared/Button/button.module.scss b/src/components/shared/Button/button.module.scss
new file mode 100644
index 0000000..86d68b5
--- /dev/null
+++ b/src/components/shared/Button/button.module.scss
@@ -0,0 +1,5 @@
+@import 'src/styles/common';
+
+.btn {
+ @include btn-primary();
+}
\ No newline at end of file
diff --git a/src/components/shared/Button/index.ts b/src/components/shared/Button/index.ts
new file mode 100644
index 0000000..d0db3c1
--- /dev/null
+++ b/src/components/shared/Button/index.ts
@@ -0,0 +1,3 @@
+import Button from './Button';
+
+export { Button };
diff --git a/src/components/shared/ErrorMessage/ErrorMessage.test.tsx b/src/components/shared/ErrorMessage/ErrorMessage.test.tsx
new file mode 100644
index 0000000..5fbaeed
--- /dev/null
+++ b/src/components/shared/ErrorMessage/ErrorMessage.test.tsx
@@ -0,0 +1,13 @@
+import { render, screen } from '@testing-library/react';
+import { ErrorMessage } from '.';
+
+describe('Test ErrorMessage shared component', () => {
+ test('Should renders with correct text', () => {
+ const errorMessageText = 'Error message';
+
+ render();
+
+ const errorMessageElement = screen.getByText(errorMessageText);
+ expect(errorMessageElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/shared/ErrorMessage/ErrorMessage.tsx b/src/components/shared/ErrorMessage/ErrorMessage.tsx
new file mode 100644
index 0000000..2e5571a
--- /dev/null
+++ b/src/components/shared/ErrorMessage/ErrorMessage.tsx
@@ -0,0 +1,14 @@
+import { ReactElement } from 'react';
+
+import styles from './errorMessage.module.scss';
+
+interface IErrorMessageProps {
+ text: string;
+ className?: string;
+}
+
+function ErrorMessage({ text, className }: IErrorMessageProps): ReactElement {
+ return {text}
;
+}
+
+export default ErrorMessage;
diff --git a/src/components/shared/ErrorMessage/errorMessage.module.scss b/src/components/shared/ErrorMessage/errorMessage.module.scss
new file mode 100644
index 0000000..a1f2b77
--- /dev/null
+++ b/src/components/shared/ErrorMessage/errorMessage.module.scss
@@ -0,0 +1,8 @@
+@import 'src/styles/common';
+
+.error {
+ @include font(regular, p2, $error-color);
+ display: block;
+ text-align: left;
+ margin: -1.5rem 0 1.5rem;
+}
\ No newline at end of file
diff --git a/src/components/shared/ErrorMessage/index.ts b/src/components/shared/ErrorMessage/index.ts
new file mode 100644
index 0000000..aeed2f8
--- /dev/null
+++ b/src/components/shared/ErrorMessage/index.ts
@@ -0,0 +1,3 @@
+import ErrorMessage from './ErrorMessage';
+
+export { ErrorMessage };
diff --git a/src/components/shared/InputField/InputField.test.tsx b/src/components/shared/InputField/InputField.test.tsx
new file mode 100644
index 0000000..5cef6e6
--- /dev/null
+++ b/src/components/shared/InputField/InputField.test.tsx
@@ -0,0 +1,47 @@
+import { render, screen } from '@testing-library/react';
+import { Formik } from 'formik';
+import InputField, { InputFieldProps } from './InputField';
+
+describe('Test InputField shared component', () => {
+ function renderInputField(props: InputFieldProps): HTMLInputElement {
+ render(
+ {}}>
+ {(): JSX.Element => }
+ ,
+ );
+ const inputElement = screen.getByPlaceholderText('Enter text') as HTMLInputElement;
+ return inputElement;
+ }
+
+ test('Should renders correctly with no error', () => {
+ const props = {
+ fieldName: 'testField',
+ placeholder: 'Enter text',
+ type: 'text',
+ error: undefined,
+ touched: false,
+ validate: (): string => '',
+ setFieldTouched: jest.fn(),
+ };
+
+ renderInputField(props);
+ });
+
+ test('Should renders error message when error and touched are true', () => {
+ const props = {
+ fieldName: 'testField',
+ placeholder: 'Enter text',
+ type: 'text',
+ error: 'This is an error message',
+ touched: true,
+ validate: (): string => '',
+ setFieldTouched: jest.fn(),
+ };
+
+ const inputElement = renderInputField(props);
+ expect(inputElement).toBeInTheDocument();
+
+ const errorMessageElement = screen.getByText('This is an error message');
+ expect(errorMessageElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/shared/InputField/InputField.tsx b/src/components/shared/InputField/InputField.tsx
new file mode 100644
index 0000000..6419b82
--- /dev/null
+++ b/src/components/shared/InputField/InputField.tsx
@@ -0,0 +1,58 @@
+import { ReactElement } from 'react';
+import classnames from 'classnames';
+import { Field } from 'formik';
+
+import styles from './inputField.module.scss';
+
+export interface InputFieldProps {
+ fieldName: string;
+ placeholder: string;
+ type?: string;
+ error: string | undefined;
+ touched: boolean | undefined;
+ validate: (value: string) => string;
+ setFieldTouched: (field: string, isTouched?: boolean | undefined) => void;
+ isDisabled?: boolean;
+ labelText?: string;
+ hideLabel?: boolean;
+ className?: string;
+ children?: ReactElement;
+}
+
+export default function InputField(props: InputFieldProps): ReactElement {
+ const {
+ labelText,
+ fieldName,
+ type,
+ placeholder,
+ className,
+ children,
+ touched,
+ error,
+ validate,
+ setFieldTouched,
+ isDisabled,
+ hideLabel,
+ } = props;
+
+ return (
+
+
+
+ {error && touched ? {error} : null}
+
+ );
+}
diff --git a/src/components/shared/InputField/index.ts b/src/components/shared/InputField/index.ts
new file mode 100644
index 0000000..a6d4797
--- /dev/null
+++ b/src/components/shared/InputField/index.ts
@@ -0,0 +1,2 @@
+import InputField from './InputField.tsx';
+export { InputField };
diff --git a/src/components/shared/InputField/inputField.module.scss b/src/components/shared/InputField/inputField.module.scss
new file mode 100644
index 0000000..99441c7
--- /dev/null
+++ b/src/components/shared/InputField/inputField.module.scss
@@ -0,0 +1,32 @@
+@import 'src/styles/common';
+
+.field {
+ width: 100%;
+ position: relative;
+ margin-bottom: 1rem;
+
+ &__label {
+ width: inherit;
+ position: relative;
+ }
+
+ &__input {
+ width: inherit;
+ border: none;
+ border-bottom: 0.1rem solid $dark;
+ padding: 0.5rem 0;
+ background-color: transparent;
+
+ &::placeholder {
+ color: $placeholder-grey;
+ }
+
+ &:focus {
+ background-color: rgba($primary, 0.1);
+ }
+ }
+
+ &__error {
+ @include input-error();
+ }
+}
diff --git a/src/components/shared/Loader/Loader.test.tsx b/src/components/shared/Loader/Loader.test.tsx
new file mode 100644
index 0000000..e767d2a
--- /dev/null
+++ b/src/components/shared/Loader/Loader.test.tsx
@@ -0,0 +1,25 @@
+import { render, screen } from '@testing-library/react';
+import Loader from './Loader';
+
+describe('Test Loader component: ', () => {
+ test('should render "Loading..." text', () => {
+ const component = render();
+
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('should render spinner', () => {
+ const component = render();
+
+ expect(screen.getByTestId('spinner')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('should render spinner', () => {
+ const component = render();
+
+ expect(screen.getByTestId('points')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/components/shared/Loader/Loader.tsx b/src/components/shared/Loader/Loader.tsx
new file mode 100644
index 0000000..b06a1dc
--- /dev/null
+++ b/src/components/shared/Loader/Loader.tsx
@@ -0,0 +1,23 @@
+import { ReactElement } from 'react';
+
+import styles from './loader.module.scss';
+
+interface LoaderProps {
+ type: 'spinner' | 'text' | 'points';
+}
+
+function Loader({ type }: LoaderProps): ReactElement {
+ return (
+ <>
+ {type === 'spinner' && (
+
+
+
+ )}
+ {type === 'text' && Loading...
}
+ {type === 'points' && }
+ >
+ );
+}
+
+export default Loader;
diff --git a/src/components/shared/Loader/__snapshots__/Loader.test.tsx.snap b/src/components/shared/Loader/__snapshots__/Loader.test.tsx.snap
new file mode 100644
index 0000000..f14b030
--- /dev/null
+++ b/src/components/shared/Loader/__snapshots__/Loader.test.tsx.snap
@@ -0,0 +1,224 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test Loader component: should render "Loading..." text 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[`Test Loader component: should render spinner 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[`Test Loader component: should render spinner 2`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ ,
+ "container":
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/components/shared/Loader/index.ts b/src/components/shared/Loader/index.ts
new file mode 100644
index 0000000..a26ed67
--- /dev/null
+++ b/src/components/shared/Loader/index.ts
@@ -0,0 +1,3 @@
+import Loader from './Loader';
+
+export { Loader };
diff --git a/src/components/shared/Loader/loader.module.scss b/src/components/shared/Loader/loader.module.scss
new file mode 100644
index 0000000..e78ea14
--- /dev/null
+++ b/src/components/shared/Loader/loader.module.scss
@@ -0,0 +1,73 @@
+@import 'src/styles/common';
+
+.loader {
+ @include font(semibold, p1, $primary);
+ padding: 1rem 0;
+}
+
+.spinner {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 15;
+ width: 8rem;
+ height: 8rem;
+ background-color: $primary;
+ border-radius: 50%;
+ box-shadow: 0 0.5rem 1.2rem 0 rgba(0, 71, 255, 0.5);
+;
+
+ &__circle {
+ position: absolute;
+ top: 1.5rem;
+ left: 1.5rem;
+ width: 5rem;
+ height: 5rem;
+ border: 0.5rem solid $light-primary;
+ border-bottom-color: transparent;
+ border-radius: 50%;
+ display: inline-block;
+ box-sizing: border-box;
+ animation: rotation 1s linear infinite;
+ }
+}
+
+@keyframes rotation {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.point {
+ width: 1rem;
+ height: 1rem;
+ border-radius: 50%;
+ display: block;
+ margin: 1.5rem auto;
+ position: relative;
+ color: $light-primary;
+ box-sizing: border-box;
+ animation: animloader 1s linear infinite;
+}
+
+@keyframes animloader {
+ 0% {
+ box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px;
+ }
+ 25% {
+ box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px;
+ }
+ 50% {
+ box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px;
+ }
+ 75% {
+ box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px;
+ }
+ 100% {
+ box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px;
+ }
+}
\ No newline at end of file
diff --git a/src/components/shared/ModalWindow/ModalWindow.test.tsx b/src/components/shared/ModalWindow/ModalWindow.test.tsx
new file mode 100644
index 0000000..2d5b214
--- /dev/null
+++ b/src/components/shared/ModalWindow/ModalWindow.test.tsx
@@ -0,0 +1,39 @@
+import { render, fireEvent } from '@testing-library/react';
+import ModalWindow from './ModalWindow';
+
+describe('Test ModalWindow shared component', () => {
+ const onCloseMock = jest.fn();
+
+ it('Should renders children when isOpen is true', () => {
+ const { getByText } = render(
+
+ Modal Content
+ ,
+ );
+
+ expect(getByText('Modal Content')).toBeInTheDocument();
+ });
+
+ it('Should not render children when isOpen is false', () => {
+ const { queryByText } = render(
+
+ Modal Content
+ ,
+ );
+
+ expect(queryByText('Modal Content')).toBeNull();
+ });
+
+ it('Should calls onClose when Close button is clicked', () => {
+ const { getByText } = render(
+
+ Modal Content
+ ,
+ );
+
+ const closeButton = getByText('Close');
+ fireEvent.click(closeButton);
+
+ expect(onCloseMock).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/shared/ModalWindow/ModalWindow.tsx b/src/components/shared/ModalWindow/ModalWindow.tsx
new file mode 100644
index 0000000..e45fdb0
--- /dev/null
+++ b/src/components/shared/ModalWindow/ModalWindow.tsx
@@ -0,0 +1,52 @@
+import { ReactElement, useEffect } from 'react';
+import styles from './modalWindow.module.scss';
+
+interface IModalWindow {
+ children: ReactElement;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+function ModalWindow({ children, isOpen, onClose }: IModalWindow): ReactElement | null {
+ useEffect(() => {
+ const handleBackdropClick = (e: MouseEvent): void => {
+ if (isOpen && e.target === document.querySelector(`.${styles.root}`)) {
+ onClose();
+ }
+ };
+
+ if (isOpen) {
+ document.body.classList.add('modal-open');
+ document.addEventListener('click', handleBackdropClick);
+ } else {
+ document.body.classList.remove('modal-open');
+ document.removeEventListener('click', handleBackdropClick);
+ }
+
+ return () => {
+ document.body.classList.remove('modal-open');
+ document.removeEventListener('click', handleBackdropClick);
+ };
+ }, [isOpen, onClose]);
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+ <>
+ {isOpen && (
+
+
+
+ Close
+
+ {children}
+
+
+ )}
+ >
+ );
+}
+
+export default ModalWindow;
diff --git a/src/components/shared/ModalWindow/index.ts b/src/components/shared/ModalWindow/index.ts
new file mode 100644
index 0000000..5b2ada6
--- /dev/null
+++ b/src/components/shared/ModalWindow/index.ts
@@ -0,0 +1,2 @@
+import ModalWindow from './ModalWindow';
+export { ModalWindow };
diff --git a/src/components/shared/ModalWindow/modalWindow.module.scss b/src/components/shared/ModalWindow/modalWindow.module.scss
new file mode 100644
index 0000000..48140fe
--- /dev/null
+++ b/src/components/shared/ModalWindow/modalWindow.module.scss
@@ -0,0 +1,36 @@
+@import '../../../styles/common';
+
+.root {
+ position: fixed;
+ display: flex;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 101;
+
+ &__container {
+ position: relative;
+ display: flex;
+ width: 72rem;
+ padding: 4rem;
+ flex-direction: column;
+ background-color: $light-primary;
+ border-radius: 0.8rem;
+
+ @media(max-width: 800px) {
+ width: 37rem;
+ }
+ }
+
+ &__closeBtn {
+ position: absolute;
+ @include close-btn();
+ top: 2rem;
+ right: 2rem;
+ cursor: pointer;
+ }
+}
\ No newline at end of file
diff --git a/src/components/shared/PasswordField/PasswordChecklist/PasswordChecklist.test.tsx b/src/components/shared/PasswordField/PasswordChecklist/PasswordChecklist.test.tsx
new file mode 100644
index 0000000..2013eab
--- /dev/null
+++ b/src/components/shared/PasswordField/PasswordChecklist/PasswordChecklist.test.tsx
@@ -0,0 +1,26 @@
+import { render } from '@testing-library/react';
+import { PasswordChecklist } from '.';
+
+describe('Test PasswordChecklist shared component', () => {
+ test('Should renders with all rules as invalid', () => {
+ const { container } = render();
+
+ expect(container).toBeInTheDocument();
+
+ const ruleElements = container.querySelectorAll('.check-item');
+ ruleElements.forEach((ruleElement) => {
+ expect(ruleElement.outerHTML).not.toContain('class="valid"');
+ });
+ });
+
+ test('Should renders with all rules as valid', () => {
+ const { container } = render();
+
+ expect(container).toBeInTheDocument();
+
+ const ruleElements = container.querySelectorAll('.check-item');
+ ruleElements.forEach((ruleElement) => {
+ expect(ruleElement).toHaveClass('valid');
+ });
+ });
+});
diff --git a/src/components/shared/PasswordField/PasswordChecklist/PasswordChecklist.tsx b/src/components/shared/PasswordField/PasswordChecklist/PasswordChecklist.tsx
new file mode 100644
index 0000000..783f887
--- /dev/null
+++ b/src/components/shared/PasswordField/PasswordChecklist/PasswordChecklist.tsx
@@ -0,0 +1,35 @@
+import { ReactElement } from 'react';
+import classnames from 'classnames';
+
+import styles from './passwordChecklist.module.scss';
+
+interface IPasswordRule {
+ rule: string;
+ isValid: boolean;
+}
+
+interface IPasswordListProps {
+ password: string;
+}
+
+export default function PasswordChecklist({ password }: IPasswordListProps): ReactElement {
+ const rules: IPasswordRule[] = [
+ { rule: 'lower case letter (a—z)', isValid: /[a-z]/.test(password) },
+ { rule: 'upper case letter (A—Z)', isValid: /[A-Z]/.test(password) },
+ { rule: 'number (0—9)', isValid: /[0-9]/.test(password) },
+ { rule: 'special character (!@#$%^&*)', isValid: /[!@#$%^&*]/.test(password) },
+ { rule: 'min 8 characters long', isValid: password.length >= 8 },
+ ];
+
+ return (
+
+
+ {rules.map((rule, index) => (
+ -
+ {rule.rule}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/shared/PasswordField/PasswordChecklist/index.ts b/src/components/shared/PasswordField/PasswordChecklist/index.ts
new file mode 100644
index 0000000..1d7a40a
--- /dev/null
+++ b/src/components/shared/PasswordField/PasswordChecklist/index.ts
@@ -0,0 +1,2 @@
+import PasswordChecklist from './PasswordChecklist.tsx';
+export { PasswordChecklist };
diff --git a/src/components/shared/PasswordField/PasswordChecklist/passwordChecklist.module.scss b/src/components/shared/PasswordField/PasswordChecklist/passwordChecklist.module.scss
new file mode 100644
index 0000000..08a583d
--- /dev/null
+++ b/src/components/shared/PasswordField/PasswordChecklist/passwordChecklist.module.scss
@@ -0,0 +1,43 @@
+@import 'src/styles/common';
+
+.rules {
+ width: 100%;
+ padding: 1.8rem 2rem;
+ background-color: rgba($bullet-grey, 0.3);
+ border-radius: 1rem;
+ margin: 1.5rem 0 2rem;
+
+ &__item {
+ position: relative;
+ font-size: 1.5rem;
+ margin-bottom: -0.2rem;
+ transition: $transition-set;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: calc(50% - 0.4rem);
+ width: 1rem;
+ height: 1rem;
+ border-radius: 1rem;
+ background-color: $bullet-grey;
+ }
+
+ &_valid {
+ &::before {
+ background-color: $green;
+ }
+
+ .rules__name {
+ opacity: 1;
+ }
+ }
+ }
+
+ &__name {
+ margin-left: 2rem;
+ opacity: 0.6;
+ transition: $transition-set;
+ }
+}
diff --git a/src/components/shared/PasswordField/PasswordField.tsx b/src/components/shared/PasswordField/PasswordField.tsx
new file mode 100644
index 0000000..fa03d26
--- /dev/null
+++ b/src/components/shared/PasswordField/PasswordField.tsx
@@ -0,0 +1,38 @@
+import { ReactElement, useState, MouseEvent } from 'react';
+import { InputField } from '../InputField';
+import { InputFieldProps } from '../InputField/InputField';
+import { PasswordChecklist } from './PasswordChecklist';
+import { passwordValidate } from '../../../utils/validation';
+
+import styles from './passwordField.module.scss';
+
+interface PasswordFieldProps extends Omit {
+ value: string;
+ formName?: string;
+ isDisabled?: boolean;
+}
+
+export default function PasswordField(props: PasswordFieldProps): ReactElement {
+ const [hidden, setHidden] = useState(true);
+
+ function togglePasswordType(e: MouseEvent): void {
+ e.preventDefault();
+ setHidden((hidden) => !hidden);
+ }
+
+ function validate(value: string): string {
+ return passwordValidate(value, props.formName === 'register');
+ }
+
+ return (
+ <>
+
+
+
+
+ {props.formName === 'register' && }
+ >
+ );
+}
diff --git a/src/components/shared/PasswordField/index.ts b/src/components/shared/PasswordField/index.ts
new file mode 100644
index 0000000..f96609b
--- /dev/null
+++ b/src/components/shared/PasswordField/index.ts
@@ -0,0 +1,2 @@
+import PasswordField from './PasswordField.tsx';
+export { PasswordField };
diff --git a/src/components/shared/PasswordField/passwordField.module.scss b/src/components/shared/PasswordField/passwordField.module.scss
new file mode 100644
index 0000000..acdbe39
--- /dev/null
+++ b/src/components/shared/PasswordField/passwordField.module.scss
@@ -0,0 +1,15 @@
+@import 'src/styles/common';
+
+.button__toggle {
+ position: absolute;
+ text-align: right;
+ right: 0;
+ bottom: 0;
+ cursor: pointer;
+
+ transition: all 0.2s ease-in;
+
+ &:hover {
+ color: $primary;
+ }
+}
\ No newline at end of file
diff --git a/src/components/shared/SelectField/SelectField.test.tsx b/src/components/shared/SelectField/SelectField.test.tsx
new file mode 100644
index 0000000..121c443
--- /dev/null
+++ b/src/components/shared/SelectField/SelectField.test.tsx
@@ -0,0 +1,30 @@
+import { render, screen, act } from '@testing-library/react';
+import { Formik, Form } from 'formik';
+import { SelectField } from '.';
+
+describe('Test SelectField shared component', () => {
+ const handleChange = jest.fn();
+
+ it('Should render SelectField component', () => {
+ act(() => {
+ render(
+ {}}>
+ {(): JSX.Element => (
+
+ )}
+ ,
+ );
+ });
+
+ expect(screen.getByText('Select an option')).toBeInTheDocument();
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/shared/SelectField/SelectField.tsx b/src/components/shared/SelectField/SelectField.tsx
new file mode 100644
index 0000000..f889748
--- /dev/null
+++ b/src/components/shared/SelectField/SelectField.tsx
@@ -0,0 +1,52 @@
+import { ChangeEvent, ReactElement } from 'react';
+import classnames from 'classnames';
+import { Field } from 'formik';
+
+import styles from './selectField.module.scss';
+
+interface SelectFieldProps {
+ value: string;
+ handleChange: (e?: ChangeEvent) => void;
+ fieldName: string;
+ placeholder: string;
+ options: string[];
+ className?: string;
+ validate?: (value: string) => string;
+ isDisabled?: boolean;
+}
+
+export default function SelectField({
+ value,
+ fieldName,
+ options,
+ className,
+ placeholder,
+ validate,
+ isDisabled,
+}: SelectFieldProps): ReactElement {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/shared/SelectField/index.ts b/src/components/shared/SelectField/index.ts
new file mode 100644
index 0000000..d3d6f21
--- /dev/null
+++ b/src/components/shared/SelectField/index.ts
@@ -0,0 +1,2 @@
+import SelectField from './SelectField.tsx';
+export { SelectField };
diff --git a/src/components/shared/SelectField/selectField.module.scss b/src/components/shared/SelectField/selectField.module.scss
new file mode 100644
index 0000000..dc116b9
--- /dev/null
+++ b/src/components/shared/SelectField/selectField.module.scss
@@ -0,0 +1,50 @@
+@import 'src/styles/common';
+
+.select {
+ position: relative;
+ width: 100%;
+ border-bottom: 0.1rem solid $dark;
+ margin-bottom: 1rem;
+ cursor: pointer;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 1rem;
+ right: 0;
+ width: 0.8em;
+ height: 0.5em;
+ background-color: $dark;
+ clip-path: polygon(100% 0%, 0 0%, 50% 100%);
+ pointer-events: none;
+ transition: all 0.2s ease-in;
+ }
+
+ &:hover::after {
+ background-color: $primary;
+ }
+
+ &__field {
+ appearance: none;
+ -webkit-appearance: none;
+ position: relative;
+ padding: 0.5rem 0 0.5rem;
+ width: 100%;
+ display: flex;
+ flex: 1;
+ background-color: transparent;
+ color: $placeholder-grey;
+ text-transform: capitalize;
+ border: none;
+ margin: 0;
+ cursor: pointer;
+
+ &_selected {
+ color: $dark;
+ }
+
+ &:focus {
+ background-color: rgba($primary, 0.05);
+ }
+ }
+}
diff --git a/src/constant/about-teamwork.ts b/src/constant/about-teamwork.ts
new file mode 100644
index 0000000..7588c7b
--- /dev/null
+++ b/src/constant/about-teamwork.ts
@@ -0,0 +1,73 @@
+import { IContentBlock } from '../components/AboutUs/ContentBlock/ContentBlock';
+
+export const teamworkText: IContentBlock[] = [
+ {
+ title: '',
+ paragraphs: [
+ 'Hey! We are the team who has been working on this project for the past 7 weeks. We did our best to plan carefully, work collaboratively and execute meticulously to deliver visually appealing and user-friendly e-commerce web application.',
+ 'Let’s have a look at our workflow and the approaches we used to make them work for our team.',
+ ],
+ result: '',
+ },
+ {
+ title: 'Project Kickoff',
+ paragraphs: [
+ 'We did this meeting right before Sprint 1 started. The goal was to understand the project’s scope and requirements, as well as each others thoughts and expectations. We discussed tech stack, collaborative tools and shared ideas about the workflow and communication. Also we took time to agree on the project’s topic.',
+ 'We needed more than one call to cover all questions.',
+ ],
+ result: 'tech stack, collaborative tools, basic workflow and the topic are agreed on and approved.',
+ },
+ {
+ title: 'Sprint Planning',
+ paragraphs: [
+ 'Every sprint started with 2 planning meetings.',
+ 'The first one was a high-level overview of the sprint’s requirements. The requirements were split into functional parts, which were assigned to each team member to carefully research, and then share the knowledge and implementation ideas with the rest of the team. This was our homework for the next planning meeting.',
+ 'The second meeting was a brief presentation of what we learned while doing the homework, and a discussion of how each of us planned to implement the assigned features.',
+ ],
+ result:
+ 'Miro board with research and plan, Trello board with assigned tasks for the sprint, new GitHub sprint branch.',
+ },
+ {
+ title: 'Daily stand-ups',
+ paragraphs: [
+ 'Daily stand-ups were our regular routine to track progress, address challenges, and adjust priorities. It was a great opportunity to keep up with everybody’s work, get help if needed and just make sure that all are safe and sane.',
+ ],
+ result: 'regular support and progress updates are shared.',
+ },
+ {
+ title: 'Development',
+ paragraphs: [
+ 'By this phase, everybody knew exactly what their scope for the current sprint was and where to start.',
+ 'So we just followed the flow: 1) break the assigned feature into manageable chunks, 2) create a new branch, 3) code with love, care and attention to details, 4) commit, 5) push, 6) pull request',
+ 'After review — approve — merge, repeat with a new branch for another feature.',
+ ],
+ result: 'pull request is pulled.',
+ },
+ {
+ title: 'Review',
+ paragraphs: [
+ 'We agreed, that any Pull Request should have been reviewed within a day max. We used GitHub to address issues and questions. If needed switched to Discord for further conversation. When all issues are solved — approve, merge.',
+ 'Nobody worked with resolving conflicts before, so for the first conflict we had a group call to share this magical experience.',
+ ],
+ result: 'new feature is merged.',
+ },
+ {
+ title: 'Sprint Closure & Deploy',
+ paragraphs: [
+ 'Before every deploy we planned a final meeting for the current sprint. Together we went through all sprint requirements and acceptance criteria, clicked through the user flow and the cross-check flow. Then, deploy — and move to the next sprint.',
+ ],
+ result: 'sprint functionality is implemented and deployed.',
+ },
+ {
+ title: 'Design',
+ paragraphs: [
+ 'The design was made on demand taking into account designer’s workload and inspiration. There was no process in there — pure chaos and pain.',
+ ],
+ result: '',
+ },
+ {
+ title: 'Collaborative Tools',
+ paragraphs: ['GitHub, Discord, Trello, Miro'],
+ result: '',
+ },
+];
diff --git a/src/constant/aboutus.ts b/src/constant/aboutus.ts
new file mode 100644
index 0000000..57a864e
--- /dev/null
+++ b/src/constant/aboutus.ts
@@ -0,0 +1,62 @@
+import { IAboutDataTabs } from '../types/interfaces';
+
+export const studentDataTabs: IAboutDataTabs[] = [
+ {
+ profilePicture: '/images/ph-elena.jpg',
+ stName: 'Elena Yaroma',
+ role: 'Front-end Developer',
+ github: 'https://github.com/ElenaYrm',
+ body: {
+ textAbout:
+ 'First of all, I try to understand task and how certain things work and thanks to that I am a fast learner. I have 1 year experience as a Front-end developer and took part in 2 real projects during this time. My current stack: HTML, CSS, Sass, JavaScript, TypeScript, React, Redux, Webpack, Vite, Jest. I am a person who always wants to expand knowledge and experience. And my next steps are to find a job and expand my knowledge in React and Angular.',
+ recommendations: [
+ 'Repository setup (Vite + React + TypeScript + Eslint + Jest)',
+ 'Github actions setup (tests and deploy on pull requests and merge)',
+ 'Work with the server SDK (Login/Register, Catalog, Cart)',
+ 'Store setting (Auth, Cart, User)',
+ 'Register form implementation (Addresses) + merge of all form parts together',
+ 'Catalog page implementation incl. Infinite scroll',
+ 'Work with React Testing Library',
+ ],
+ },
+ },
+ {
+ profilePicture: '/images/ph-german.jpg',
+ stName: 'German Gribanov',
+ role: 'Front-end Developer',
+ github: 'https://github.com/GermanGrib',
+ body: {
+ textAbout:
+ 'As Conrad Hilton said, "Success seems to be connected with action. Successful people keep moving. They make mistakes, but they don\'t quit." This was also true on our project; despite mistakes and misunderstandings, we kept moving forward, each person fulfilling their role. I consider myself fortunate to have had such a wonderful team in the final project. 99% of our successful work rests on their fragile, beautiful, feminine shoulders. I am convinced that success depends on the team.',
+ recommendations: [
+ 'Work with the server SDK (Profile)',
+ 'Store setting (User)',
+ 'Login form implementation',
+ 'Profile page implementation incl. Tabs',
+ 'About page implementation',
+ 'Trello board setup',
+ 'Work with React Testing Library',
+ ],
+ },
+ },
+ {
+ profilePicture: '/images/ph-nastia.jpg',
+ stName: 'Nastia Piven',
+ role: 'Front-end Developer',
+ github: 'https://github.com/HereEast',
+ body: {
+ textAbout:
+ 'Being a designer in tech, the most often phrase that was smashed into my face was: "That\'s impossible to implement". Then I thought — wow, I also want to have this power and impact! So here I am learning JS and finishing this project on a tech stack I\'ve never used before: React, Redux, and Jest. Also, I am interested in creative dev and looking forward to working with WebGL/Three.js, GreenSock, Processing, and more. That is LOVE — the only thing that matters.',
+ recommendations: [
+ 'Routing setup',
+ 'Register form implementation (User information) incl. Password checklist',
+ 'Work with the server SDK (Product)',
+ 'Store setting (Product, Catalog)',
+ 'Product page implementation incl. Slider',
+ 'Cart page implementation',
+ 'Project design',
+ 'Work with React Testing Library',
+ ],
+ },
+ },
+];
diff --git a/src/constant/filters.ts b/src/constant/filters.ts
new file mode 100644
index 0000000..2f29002
--- /dev/null
+++ b/src/constant/filters.ts
@@ -0,0 +1,168 @@
+import { IColorFilterItem, IFilterItem } from '../components/Filters/types';
+
+export const sizes: IFilterItem[] = [
+ {
+ label: 'Small',
+ value: 'Small',
+ },
+ {
+ label: 'Medium',
+ value: 'Medium',
+ },
+ {
+ label: 'Large',
+ value: 'Large',
+ },
+];
+
+export const colors: IColorFilterItem[] = [
+ {
+ label: 'Black',
+ value: 'black',
+ color: '#000000',
+ },
+ {
+ label: 'Blue',
+ value: 'blue',
+ color: '#0000ff',
+ },
+ {
+ label: 'Brown',
+ value: 'brown',
+ color: '#a52a2a',
+ },
+ {
+ label: 'Colorful',
+ value: 'colorful',
+ color: '#ffc56b',
+ },
+ {
+ label: 'Green',
+ value: 'green',
+ color: '#008000',
+ },
+ {
+ label: 'Grey',
+ value: 'grey',
+ color: '#808080',
+ },
+ {
+ label: 'Orange',
+ value: 'orange',
+ color: '#ffa500',
+ },
+ {
+ label: 'Pink',
+ value: 'pink',
+ color: '#ffc0cb',
+ },
+ {
+ label: 'Purple',
+ value: 'purple',
+ color: '#a020f0',
+ },
+ {
+ label: 'Red',
+ value: 'red',
+ color: '#ff0000',
+ },
+ {
+ label: 'White',
+ value: 'white',
+ color: '#ffffff',
+ },
+];
+
+export const brands: IFilterItem[] = [
+ {
+ label: 'Anish Kapoor',
+ value: 'Anish Kapoor',
+ },
+ {
+ label: 'Austin Lee',
+ value: 'Austin Lee',
+ },
+ {
+ label: 'Bob-nosa',
+ value: 'Bob-nosa',
+ },
+ {
+ label: 'Bosco Sodi',
+ value: 'Bosco Sodi',
+ },
+ {
+ label: 'Cédrix Crespel',
+ value: 'Cédrix Crespel',
+ },
+ {
+ label: 'Chung Chang-Sup',
+ value: 'Chung Chang-Sup',
+ },
+ {
+ label: 'Erin Armstrong',
+ value: 'Erin Armstrong',
+ },
+ {
+ label: 'Jeff Koons',
+ value: 'Jeff Koons',
+ },
+ {
+ label: 'Jef Verheyen',
+ value: 'Jef Verheyen',
+ },
+ {
+ label: 'Joan Mitchell',
+ value: 'Joan Mitchell',
+ },
+ {
+ label: 'John Zabawa',
+ value: 'John Zabawa',
+ },
+ {
+ label: 'KAWS',
+ value: 'KAWS',
+ },
+ {
+ label: 'Lester Johnson',
+ value: 'Lester Johnson',
+ },
+ {
+ label: 'Masoami RAKU',
+ value: 'Masoami RAKU',
+ },
+ {
+ label: 'Michel Mouffe',
+ value: 'Michel Mouffe',
+ },
+ {
+ label: 'Richard Diebenkorn',
+ value: 'Richard Diebenkorn',
+ },
+ {
+ label: 'Squeak Carnwath',
+ value: 'Squeak Carnwath',
+ },
+];
+
+export const sorting: IFilterItem[] = [
+ {
+ label: 'Recommended',
+ value: '',
+ },
+ {
+ label: 'Name: A-Z',
+ value: 'name.en-US asc',
+ },
+ {
+ label: 'Name: Z-A',
+ value: 'name.en-US desc',
+ },
+ {
+ label: 'Price: low to high',
+ value: 'price asc',
+ },
+ {
+ label: 'Price: high to low',
+ value: 'price desc',
+ },
+];
diff --git a/src/constant/index.ts b/src/constant/index.ts
new file mode 100644
index 0000000..c166063
--- /dev/null
+++ b/src/constant/index.ts
@@ -0,0 +1,8 @@
+export * from './initialForms';
+export * from './initialStates';
+export * from './selects';
+export * from './validation';
+export * from './metaData';
+export * from './requests';
+export * from './filters';
+export * from './productAccordion';
diff --git a/src/constant/initialForms.ts b/src/constant/initialForms.ts
new file mode 100644
index 0000000..f00e6ab
--- /dev/null
+++ b/src/constant/initialForms.ts
@@ -0,0 +1,57 @@
+import { IRegisterForm } from '../components/RegisterForm/RegisterForm';
+import { ILoginForm } from '../components/LoginForm/LoginForm';
+import { Input } from '../types/enums';
+import { IChangePassword } from '../components/Profile/Tabs/ThirdTab/ThirdTab';
+
+export const initialRegisterForm: IRegisterForm = {
+ user: {
+ [Input.Email]: '',
+ [Input.Password]: '',
+ [Input.FirstName]: '',
+ [Input.LastName]: '',
+ [Input.Date]: '',
+ [Input.Month]: '',
+ [Input.Year]: '',
+ },
+ shipping: {
+ [Input.Street]: '',
+ [Input.Country]: '',
+ [Input.PostalCode]: '',
+ [Input.City]: '',
+ [Input.IsDefault]: false,
+ },
+ billing: {
+ [Input.Street]: '',
+ [Input.Country]: '',
+ [Input.PostalCode]: '',
+ [Input.City]: '',
+ [Input.IsDefault]: false,
+ },
+};
+
+export const initialLoginForm: ILoginForm = {
+ [Input.Email]: '',
+ [Input.Password]: '',
+};
+
+export const initialChangePassord: IChangePassword = {
+ [Input.Password]: '',
+ [Input.NewPassword]: '',
+};
+
+export const initialEditAddresses = {
+ shipping: {
+ [Input.Street]: '',
+ [Input.Country]: '',
+ [Input.PostalCode]: '',
+ [Input.City]: '',
+ [Input.IsDefault]: false,
+ },
+ billing: {
+ [Input.Street]: '',
+ [Input.Country]: '',
+ [Input.PostalCode]: '',
+ [Input.City]: '',
+ [Input.IsDefault]: false,
+ },
+};
diff --git a/src/constant/initialProduct.ts b/src/constant/initialProduct.ts
new file mode 100644
index 0000000..492c7f4
--- /dev/null
+++ b/src/constant/initialProduct.ts
@@ -0,0 +1,23 @@
+import { IProductSlice } from '../store/product/types';
+import { IProduct } from '../types/interfaces';
+
+export const initialProduct: IProduct = {
+ artist: '',
+ title: '',
+ year: '',
+ description: '',
+ images: [],
+ price: 0,
+ discountPrice: 0,
+ productId: '',
+ medium: '',
+ dimensions: '',
+ color: '',
+ size: '',
+};
+
+export const initialProductSlice: IProductSlice = {
+ status: 'initial',
+ error: '',
+ product: initialProduct,
+};
diff --git a/src/constant/initialStates.ts b/src/constant/initialStates.ts
new file mode 100644
index 0000000..73f381a
--- /dev/null
+++ b/src/constant/initialStates.ts
@@ -0,0 +1,86 @@
+import { IUser } from '../types/interfaces';
+import { IAuthSlice } from '../store/auth/types';
+import { IProductSlice } from '../store/product/types';
+import { IProduct } from '../types/interfaces';
+import { ICatalogSlice } from '../store/catalog/types';
+import { IUserSlice } from '../store/user/types';
+import { ICart, ICartSlice } from '../store/cart/types';
+
+export const initialUser: IUser = {
+ id: '',
+ version: 0,
+ email: '',
+ firstName: '',
+ lastName: '',
+ password: '',
+ date: '',
+ month: '',
+ year: '',
+ defaultShippingAddressId: '',
+ defaultBillingAddressId: '',
+ shippingAddressIds: [],
+ billingAddressIds: [],
+ addresses: [],
+};
+
+export const initialUserState: IUserSlice = {
+ status: 'initial',
+ error: '',
+ user: initialUser,
+ isSuccess: false,
+ editStatus: 'initial',
+ editError: '',
+};
+
+export const initialProduct: IProduct = {
+ artist: '',
+ title: '',
+ year: '',
+ description: '',
+ images: [],
+ price: 0,
+ discountPrice: 0,
+ productId: '',
+ dimensions: '',
+ size: '',
+ medium: '',
+ color: '',
+};
+
+export const initialAuthState: IAuthSlice = {
+ isAuthorized: !!localStorage.getItem('art-token'),
+ isNewUser: false,
+ status: 'initial',
+ error: '',
+ userId: '',
+};
+
+export const initialProductSlice: IProductSlice = {
+ status: 'initial',
+ error: '',
+ product: initialProduct,
+};
+
+export const initialProductListSlice: ICatalogSlice = {
+ status: 'initial',
+ error: '',
+ productList: [],
+ categories: [],
+ totalProducts: 0,
+};
+
+export const initialBasket: ICart = {
+ id: '',
+ version: 0,
+ lineItems: [],
+ totalPrice: 0,
+ totalQuantity: 0,
+ codes: [],
+};
+
+export const initialCartSlice: ICartSlice = {
+ status: 'initial',
+ error: '',
+ basket: initialBasket,
+ discounts: [],
+};
diff --git a/src/constant/metaData.ts b/src/constant/metaData.ts
new file mode 100644
index 0000000..bf50386
--- /dev/null
+++ b/src/constant/metaData.ts
@@ -0,0 +1,17 @@
+const {
+ VITE_CTP_API_URL,
+ VITE_CTP_AUTH_URL,
+ VITE_CTP_CLIENT_ID,
+ VITE_CTP_CLIENT_SECRET,
+ VITE_CTP_PROJECT_KEY,
+ VITE_CTP_SCOPES,
+} = import.meta.env;
+
+export {
+ VITE_CTP_API_URL,
+ VITE_CTP_AUTH_URL,
+ VITE_CTP_CLIENT_ID,
+ VITE_CTP_CLIENT_SECRET,
+ VITE_CTP_PROJECT_KEY,
+ VITE_CTP_SCOPES,
+};
diff --git a/src/constant/productAccordion.ts b/src/constant/productAccordion.ts
new file mode 100644
index 0000000..3ed3568
--- /dev/null
+++ b/src/constant/productAccordion.ts
@@ -0,0 +1,17 @@
+export const productAccordionData = [
+ {
+ title: 'Shipping & Taxes',
+ content:
+ 'Scoop Shipping is available for all artworks with a secure checkout option on our platform. Once selected, gallery partners have 5-7 business days to prepare and pack the order for shipping. Ships from New York, NY, US. Taxes may apply at checkout.',
+ },
+ {
+ title: 'Cancellation Policy and Returns',
+ content:
+ 'If you have changed your mind about an order made via Buy Now or Make an Offer, you may submit a cancellation request within 24 hours of order confirmation.',
+ },
+ {
+ title: 'About Scoop',
+ content:
+ 'Scoop is for art collecting ⸂⸂⸜(രᴗര๑)⸝⸃⸃.\n As the leading marketplace for art by the world’s emerging and established artists, we’ve made it easy for new and experienced collectors to discover, buy, and sell art—and so much more. Everything you’ll ever need to collect art, you’ll find on Scoop.',
+ },
+];
diff --git a/src/constant/profile.tsx b/src/constant/profile.tsx
new file mode 100644
index 0000000..2d87cc0
--- /dev/null
+++ b/src/constant/profile.tsx
@@ -0,0 +1,22 @@
+import { ITabsList } from '../types/interfaces';
+import { FirstTab } from '../components/Profile/Tabs/FirstTab';
+import { SecondTab } from '../components/Profile/Tabs/SecondTab';
+import { ThirdTab } from '../components/Profile/Tabs/ThirdTab';
+
+export const tabsList: ITabsList[] = [
+ {
+ label: 'PERSONAL INFORMATION',
+ content: ,
+ title: 'Edit personal information',
+ },
+ {
+ label: 'ADDRESSES',
+ content: ,
+ title: 'Add new address',
+ },
+ {
+ label: 'PASSWORD',
+ content: ,
+ title: 'Change password',
+ },
+];
diff --git a/src/constant/requests.ts b/src/constant/requests.ts
new file mode 100644
index 0000000..423634d
--- /dev/null
+++ b/src/constant/requests.ts
@@ -0,0 +1,2 @@
+export const LANG_CODE: string = 'en-US';
+export const PAGE_LIMIT: number = 12;
diff --git a/src/constant/selects.ts b/src/constant/selects.ts
new file mode 100644
index 0000000..a9f3326
--- /dev/null
+++ b/src/constant/selects.ts
@@ -0,0 +1,25 @@
+const currentYear = new Date().getFullYear();
+export const years: string[] = Array(90)
+ .fill('')
+ .map((_, index) => `${currentYear - index}`);
+
+export const dates: string[] = Array(31)
+ .fill('')
+ .map((_, index) => `${index + 1}`);
+
+export const months: string[] = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+];
+
+export const countries: string[] = ['US'];
diff --git a/src/constant/validation.ts b/src/constant/validation.ts
new file mode 100644
index 0000000..b3b5c86
--- /dev/null
+++ b/src/constant/validation.ts
@@ -0,0 +1,41 @@
+const isRequired = 'is required';
+const isNotValid = 'is not valid';
+
+export const errorMsg = {
+ email: {
+ empty: `Email ${isRequired}`,
+ invalid: 'Email must match the pattern: user@example.com',
+ },
+ password: {
+ empty: `Password ${isRequired}`,
+ invalid: `Password must be min 8 characters long, contain min
+ 1 uppercase and 1 lowercase letter, 1 number, 1 special character, and no spaces`,
+ invalidCheck: 'Password must meet the requirements below',
+ },
+ firstName: {
+ empty: `First name ${isRequired}`,
+ invalid: `First name ${isNotValid}`,
+ },
+ lastName: {
+ empty: `Last name ${isRequired}`,
+ invalid: `Last name ${isNotValid}`,
+ },
+ street: {
+ empty: `Street ${isRequired}`,
+ },
+ city: {
+ empty: `City ${isRequired}`,
+ invalid: `City ${isNotValid}`,
+ },
+ date: {
+ empty: 'Date, month and year of birth are required',
+ invalid: 'User must be at least 13 years old',
+ },
+ zipCode: {
+ empty: `Postal code ${isRequired}`,
+ invalid: 'Postal code must match the pattern: 12345',
+ },
+ country: {
+ empty: `Country ${isRequired}`,
+ },
+};
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
new file mode 100644
index 0000000..c5fd8f6
--- /dev/null
+++ b/src/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useDebounce';
diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts
new file mode 100644
index 0000000..946effb
--- /dev/null
+++ b/src/hooks/useDebounce.ts
@@ -0,0 +1,23 @@
+/* eslint-disable @typescript-eslint/no-explicit-any*/
+
+import { useCallback, useRef } from 'react';
+
+type Timer = ReturnType;
+type cbFunc = (...args: any[]) => void;
+
+export function useDebounce(callback: cbFunc, delay: number): cbFunc {
+ const timer = useRef();
+
+ return useCallback(
+ (...args: any[]) => {
+ if (timer.current) {
+ clearTimeout(timer.current);
+ }
+
+ timer.current = setTimeout(() => {
+ callback(...args);
+ }, delay);
+ },
+ [callback, delay],
+ );
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..1832c65
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+import { App } from './components/App';
+import { store } from './store/store.ts';
+
+import './styles/index.scss';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+
+ ,
+);
diff --git a/src/pages/AboutUs/AboutUs.tsx b/src/pages/AboutUs/AboutUs.tsx
new file mode 100644
index 0000000..2c68a5b
--- /dev/null
+++ b/src/pages/AboutUs/AboutUs.tsx
@@ -0,0 +1,20 @@
+import styles from './aboutUs.module.scss';
+import { ReactElement } from 'react';
+import { AboutTabs } from '../../components/AboutUs/AboutTabs';
+import { TeamProcess } from '../../components/AboutUs/TeamProcess';
+import { RSLogo } from '../../components/AboutUs/RSLogo';
+import { teamworkText } from '../../constant/about-teamwork';
+
+function AboutUs(): ReactElement {
+ return (
+
+ );
+}
+
+export default AboutUs;
diff --git a/src/pages/AboutUs/aboutUs.module.scss b/src/pages/AboutUs/aboutUs.module.scss
new file mode 100644
index 0000000..898c04b
--- /dev/null
+++ b/src/pages/AboutUs/aboutUs.module.scss
@@ -0,0 +1,7 @@
+.root {
+ padding: 10rem 2rem 0;
+
+ @media(max-width: 480px) {
+ padding: 8rem 1rem 0;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/AboutUs/index.ts b/src/pages/AboutUs/index.ts
new file mode 100644
index 0000000..0cce2a5
--- /dev/null
+++ b/src/pages/AboutUs/index.ts
@@ -0,0 +1,2 @@
+import AboutUs from './AboutUs';
+export { AboutUs };
diff --git a/src/pages/Cart/Cart.test.tsx b/src/pages/Cart/Cart.test.tsx
new file mode 100644
index 0000000..5c7c4c5
--- /dev/null
+++ b/src/pages/Cart/Cart.test.tsx
@@ -0,0 +1,17 @@
+// import { render, screen } from '@testing-library/react';
+// import { Cart } from './index';
+
+jest.mock('../../constant/metaData', () => {});
+jest.mock('../../services/sdk/auth/methods', () => {});
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../services/sdk/catalog/methods', () => {});
+jest.mock('../../services/sdk/customer/methods', () => {});
+jest.mock('../../services/sdk/cart/methods', () => {});
+
+describe('Test Cart page: ', () => {
+ test.todo('cart');
+ // test('should render Cart page', () => {
+ // render();
+ // expect(screen.getByText('Cart')).toBeInTheDocument();
+ // });
+});
diff --git a/src/pages/Cart/Cart.tsx b/src/pages/Cart/Cart.tsx
new file mode 100644
index 0000000..f44b9a1
--- /dev/null
+++ b/src/pages/Cart/Cart.tsx
@@ -0,0 +1,117 @@
+import { ReactElement, useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { useAppDispatch } from '../../store/store';
+import { deleteCartThunk, getCartThunk, updateCartThunk } from '../../store/cart/thunks';
+import { selectCartData, selectCartLoadingInfo } from '../../store/cart/selectors';
+import { EmptyCart } from './EmptyMessage';
+import { Button } from '../../components/shared/Button';
+import { Total } from './Total';
+import { CartList } from './CartList';
+import { IItemCart } from '../../store/cart/types';
+import { ErrorMessage } from '../../components/shared/ErrorMessage';
+import { resetCartError } from '../../store/cart/slice';
+
+import styles from './cart.module.scss';
+
+export default function Cart(): ReactElement {
+ const [isConfirmPopup, setIsConfirmPopup] = useState(false);
+
+ const { basket } = useSelector(selectCartData);
+ const { status, error } = useSelector(selectCartLoadingInfo);
+ const isEmpty = basket.lineItems.length === 0;
+
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (error) {
+ const timer = setTimeout(() => {
+ dispatch(resetCartError());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [error, dispatch]);
+
+ useEffect(() => {
+ if (isConfirmPopup) {
+ document.body.style.overflow = 'hidden';
+ } else {
+ document.body.style.overflow = '';
+ }
+ }, [isConfirmPopup]);
+
+ useEffect(() => {
+ dispatch(getCartThunk());
+ }, [dispatch]);
+
+ function openConfirmPopup(): void {
+ setIsConfirmPopup(true);
+ }
+
+ function clearCart(): void {
+ setIsConfirmPopup(false);
+ dispatch(deleteCartThunk({ id: basket.id, version: basket.version }));
+ }
+
+ function removeCartItem(item: IItemCart): void {
+ dispatch(
+ updateCartThunk({
+ id: basket.id,
+ version: basket.version,
+ actions: [
+ {
+ action: 'removeLineItem',
+ lineItemId: item.itemId,
+ quantity: item.quantity,
+ },
+ ],
+ }),
+ );
+ }
+
+ return (
+
+ {isEmpty && status !== 'loading' &&
}
+ {!isEmpty && (
+
+
+
Cart({basket.lineItems.length})
+
+
+
+
+
+ )}
+ {isConfirmPopup && (
+
+
+
Are you sure you want to clear the Cart?
+
+
+
+
+ )}
+ {error &&
}
+
+ );
+}
diff --git a/src/pages/Cart/CartList/CartList.tsx b/src/pages/Cart/CartList/CartList.tsx
new file mode 100644
index 0000000..28a7402
--- /dev/null
+++ b/src/pages/Cart/CartList/CartList.tsx
@@ -0,0 +1,107 @@
+import { ReactElement } from 'react';
+import { Button } from '../../../components/shared/Button';
+import { ICart, IItemCart } from '../../../store/cart/types';
+import { formatPrice } from '../../../utils';
+import { useAppDispatch } from '../../../store/store';
+import { updateCartThunk } from '../../../store/cart/thunks';
+import { Link } from 'react-router-dom';
+import { PATH } from '../../../router/constants/paths';
+import { Page } from '../../../router/types';
+
+import styles from './cartList.module.scss';
+
+interface ICartListProps {
+ basket: ICart;
+ handleRemoveCartItem: (item: IItemCart) => void;
+}
+
+export default function CartList({ basket, handleRemoveCartItem }: ICartListProps): ReactElement {
+ const dispatch = useAppDispatch();
+
+ function handleAddQuantity(item: IItemCart): void {
+ if (item.quantity === 10) return;
+ updateQuantity(item, item.quantity + 1);
+ }
+
+ function handleRemoveQuantity(item: IItemCart): void {
+ if (item.quantity === 1) return;
+ updateQuantity(item, item.quantity - 1);
+ }
+
+ function updateQuantity(item: IItemCart, quantity: number): void {
+ dispatch(
+ updateCartThunk({
+ id: basket.id,
+ version: basket.version,
+ actions: [
+ {
+ action: 'changeLineItemQuantity',
+ lineItemId: item.itemId,
+ quantity: quantity,
+ },
+ ],
+ }),
+ );
+ }
+
+ return (
+
+ {basket.lineItems.map((item) => (
+ -
+
+
+

+
+
+
+
+
+
{item.name}
+
{item.artist}
+
+
+ {item.discountedPrice ? (
+ {formatPrice(item.discountedPrice / 100)}
+ ) : null}
+ {formatPrice(item.price / 100)}
+
+
+
+
Qt:
+
{item.quantity}
+
+ handleRemoveQuantity(item)}
+ />
+ handleAddQuantity(item)}
+ />
+
+
+
+
+
handleRemoveCartItem(item)}
+ />
+
+ {formatPrice(((item.discountedPrice || item.price) / 100) * item.quantity)}
+
+
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/pages/Cart/CartList/cartList.module.scss b/src/pages/Cart/CartList/cartList.module.scss
new file mode 100644
index 0000000..5fa3978
--- /dev/null
+++ b/src/pages/Cart/CartList/cartList.module.scss
@@ -0,0 +1,192 @@
+@import '../../../styles/common/';
+
+.items__list {
+ margin-bottom: 2rem;
+}
+
+.item {
+ width: 100%;
+ padding: 2rem 0;
+ border-top: 1px solid $dark;
+
+ &:last-child {
+ border-bottom: 1px solid $dark;
+ }
+
+ &__container {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ gap: 2rem;
+ }
+
+ &__image {
+ position: relative;
+ flex-shrink: 0;
+ width: 10rem;
+ height: 13rem;
+ background-color: $primary;
+ overflow: hidden;
+
+ img {
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ &__button_remove {
+ height: auto;
+ background-color: transparent;
+ box-shadow: none;
+ font-size: 2rem;
+ color: $text-grey;
+
+ @media all and (max-width: 480px) {
+ font-size: 1.8rem;
+ }
+
+ &:active,
+ &:hover {
+ background-color: transparent;
+ box-shadow: none;
+ font-size: 2rem;
+ color: $primary;
+
+ @media all and (max-width: 480px) {
+ font-size: 1.8rem;
+ }
+ }
+ }
+}
+
+.item__info {
+ position: relative;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 1.5rem;
+
+ .header {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ gap: 2rem;
+
+ @media all and (max-width: 610px) {
+ flex-direction: column;
+ gap: 2.5rem;
+ }
+
+ &__link {
+ color: $dark;
+ text-decoration: none;
+
+ &:hover,
+ &:active {
+ color: $primary;
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 2.5px;
+ }
+ }
+
+ &__artist {
+ margin-bottom: 1rem;
+ }
+
+ &__prices {
+ display: flex;
+ gap: 1rem;
+
+ .price {
+ &:nth-child(2) {
+ opacity: 0.3;
+ text-decoration: line-through;
+ }
+ }
+ }
+
+ &__controls {
+ height: 4.4rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ font-weight: 500;
+
+ @media all and (max-width: 480px) {
+ font-size: 1.8rem;
+ }
+
+ .controls__count,
+ .controls__buttons_btn {
+ box-shadow: none;
+ border-radius: 0;
+ height: inherit;
+ }
+
+ .controls__buttons {
+ height: inherit;
+ display: flex;
+ gap: 1px;
+
+ &_btn {
+ width: 4.4rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-shrink: 0;
+ background-color: $dark;
+ background-size: 50%;
+ background-repeat: no-repeat;
+ background-position: center;
+
+ &:last-child {
+ background-image: url('../../../assets/icons/ic-plus.svg');
+ }
+
+ &:first-child {
+ background-image: url('../../../assets/icons/ic-minus.svg');
+ }
+
+ &:hover:not(:disabled) {
+ background-color: $primary;
+ }
+
+ &:disabled {
+ cursor: default;
+ opacity: 0.5;
+ }
+ }
+ }
+
+ .controls__count {
+ position: relative;
+ width: 6rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid $dark;
+ text-align: center;
+ font-weight: 400;
+ user-select: none;
+ }
+ }
+ }
+
+ .footer {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+
+ @media all and (max-width: 610px) {
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ gap: 2.5rem;
+ }
+
+ &__total {
+ font-weight: 600;
+ }
+ }
+}
diff --git a/src/pages/Cart/CartList/index.ts b/src/pages/Cart/CartList/index.ts
new file mode 100644
index 0000000..8ccac51
--- /dev/null
+++ b/src/pages/Cart/CartList/index.ts
@@ -0,0 +1,2 @@
+import CartList from './CartList';
+export { CartList };
diff --git a/src/pages/Cart/EmptyMessage/EmptyMessage.tsx b/src/pages/Cart/EmptyMessage/EmptyMessage.tsx
new file mode 100644
index 0000000..2f86546
--- /dev/null
+++ b/src/pages/Cart/EmptyMessage/EmptyMessage.tsx
@@ -0,0 +1,20 @@
+import { ReactElement } from 'react';
+import { Link } from 'react-router-dom';
+import { PATH } from '../../../router/constants/paths';
+import { Page } from '../../../router/types';
+
+import styles from './emptyMessage.module.scss';
+
+export default function EmptyCart(): ReactElement {
+ return (
+
+
Cart is empty (´•̥̥̥o•̥̥̥`)
+
+ We highly recommend to check out Scoop’s catalog and get some amazing art for your eyes and your soul.
+
+
+ To Catalog
+
+
+ );
+}
diff --git a/src/pages/Cart/EmptyMessage/emptyMessage.module.scss b/src/pages/Cart/EmptyMessage/emptyMessage.module.scss
new file mode 100644
index 0000000..10cce06
--- /dev/null
+++ b/src/pages/Cart/EmptyMessage/emptyMessage.module.scss
@@ -0,0 +1,29 @@
+@import '../../../styles/common/';
+
+.cart__empty {
+ width: 100%;
+ padding: 4rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ border-radius: 4rem;
+ border: 1px solid $dark;
+ margin: 8rem 2rem;
+
+ .title {
+ margin-bottom: 2rem;
+ }
+
+ .text {
+ margin-bottom: 3rem;
+ max-width: 70rem;
+ width: 100%;
+ }
+
+ .link {
+ @include btn-primary();
+ max-width: 52rem;
+ width: 100%;
+ }
+}
diff --git a/src/pages/Cart/EmptyMessage/index.ts b/src/pages/Cart/EmptyMessage/index.ts
new file mode 100644
index 0000000..512d858
--- /dev/null
+++ b/src/pages/Cart/EmptyMessage/index.ts
@@ -0,0 +1,2 @@
+import EmptyCart from './EmptyMessage';
+export { EmptyCart };
diff --git a/src/pages/Cart/Total/Total.tsx b/src/pages/Cart/Total/Total.tsx
new file mode 100644
index 0000000..6bb34fc
--- /dev/null
+++ b/src/pages/Cart/Total/Total.tsx
@@ -0,0 +1,178 @@
+import { ReactElement, FormEvent, useState, useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useAppDispatch } from '../../../store/store';
+import { updateCartThunk, getDiscountsThunk } from '../../../store/cart/thunks';
+import { selectCartData } from '../../../store/cart/selectors';
+import { Button } from '../../../components/shared/Button';
+import { IPromoCode } from '../../../store/cart/types';
+import { formatPrice } from '../../../utils';
+import { ErrorMessage } from '../../../components/shared/ErrorMessage';
+
+import styles from './total.module.scss';
+
+const DELIVERY_PRICE = 120;
+const CODE_ERROR = 'The code you entered is not valid.';
+
+export default function Total(): ReactElement {
+ const [code, setCode] = useState('');
+ const [isError, setIsError] = useState(false);
+ const [isCheckoutPopup, setIsCheckoutPopup] = useState(false);
+
+ const { basket, discounts } = useSelector(selectCartData);
+
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (discounts.length === 0) {
+ dispatch(getDiscountsThunk());
+ }
+ }, [dispatch, discounts]);
+
+ useEffect(() => {
+ if (isCheckoutPopup) {
+ document.body.style.overflow = 'hidden';
+ } else {
+ document.body.style.overflow = '';
+ }
+ }, [isCheckoutPopup]);
+
+ function getCodeName(codeId: string): string | undefined {
+ const code = discounts.find((item) => item.id === codeId);
+
+ return code?.code;
+ }
+
+ function applyCode(e: FormEvent): void {
+ e.preventDefault();
+
+ const isCodeValid = discounts.find((item) => item.code === code);
+
+ if (!isCodeValid) {
+ setIsError(true);
+ return;
+ }
+
+ dispatch(
+ updateCartThunk({
+ id: basket.id,
+ version: basket.version,
+ actions: [
+ {
+ action: 'addDiscountCode',
+ code: code,
+ },
+ ],
+ }),
+ );
+ setCode('');
+ }
+
+ function removeCode(item: IPromoCode): void {
+ dispatch(
+ updateCartThunk({
+ id: basket.id,
+ version: basket.version,
+ actions: [
+ {
+ action: 'removeDiscountCode',
+ discountCode: {
+ typeId: 'discount-code',
+ id: item.id,
+ },
+ },
+ ],
+ }),
+ );
+ }
+
+ function countSubtotal(): number {
+ const subtotal = basket.lineItems.reduce((acc, item) => {
+ const price = ((item.discountedPrice || item.price) / 100) * item.quantity;
+ return acc + price;
+ }, 0);
+
+ return subtotal;
+ }
+
+ function countCodeDiscount(): number {
+ return basket.totalPrice / 100 - countSubtotal();
+ }
+
+ return (
+
+
Total
+
+ -
+ Subtotal
+ {formatPrice(countSubtotal())}
+
+ -
+ Delivery
+ {formatPrice(DELIVERY_PRICE)}
+
+ -
+ Code Discount
+ {formatPrice(countCodeDiscount())}
+
+ -
+ Total
+ {formatPrice(basket.totalPrice / 100 + DELIVERY_PRICE)}
+
+
+
+
setIsCheckoutPopup(true)}
+ />
+
+
+
Have a promo code?
+
+
+ {isError &&
}
+
+ {basket.codes.length > 0 && (
+
+ {basket.codes.map((item) => (
+ -
+ {getCodeName(item.id) && (
+ {getCodeName(item.id)} is applied
+ )}
+ removeCode(item)}
+ />
+
+ ))}
+
+ )}
+
+ {isCheckoutPopup && (
+
+
+
You are awesome 😘😘😘
+ setIsCheckoutPopup(false)}
+ />
+
+
+ )}
+
+ );
+}
diff --git a/src/pages/Cart/Total/index.ts b/src/pages/Cart/Total/index.ts
new file mode 100644
index 0000000..4adbabe
--- /dev/null
+++ b/src/pages/Cart/Total/index.ts
@@ -0,0 +1,2 @@
+import Total from './Total';
+export { Total };
diff --git a/src/pages/Cart/Total/total.module.scss b/src/pages/Cart/Total/total.module.scss
new file mode 100644
index 0000000..276b4f9
--- /dev/null
+++ b/src/pages/Cart/Total/total.module.scss
@@ -0,0 +1,179 @@
+@import '../../../styles/common/';
+
+.total {
+ flex-shrink: 0;
+ width: 40rem;
+ height: max-content;
+ padding: 3.5rem 3rem 3rem;
+ background-color: #fff;
+ border-radius: 4rem;
+ margin-top: -2rem;
+
+ @media all and (max-width: $cart-tablet-width) {
+ width: 100%;
+ }
+
+ @media all and (max-width: 480px) {
+ padding: 3rem 2.5rem;
+ }
+
+ &__title {
+ @include font(semibold, h2, $dark);
+ margin-bottom: 2rem;
+ }
+
+ &__list {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ margin-bottom: 2rem;
+
+ &_item {
+ display: flex;
+ justify-content: space-between;
+
+ &:nth-child(3) {
+ padding-bottom: 3rem;
+ }
+
+ &:last-child {
+ padding-top: 3rem;
+ border-top: 1px solid $dark;
+ font-weight: 600;
+ }
+ }
+ }
+
+ &__button_checkout {
+ width: 100%;
+ margin-bottom: 6rem;
+ }
+
+ .promo {
+ width: 100%;
+ padding: 2rem 2.5rem 2.5rem;
+ background-color: rgba($primary, 0.1);
+ border-radius: 2rem;
+ font-size: 1.8rem;
+
+ &__title {
+ font-weight: 500;
+ margin-bottom: 1rem;
+ }
+
+ &__form {
+ width: 100%;
+ display: flex;
+
+ &_input,
+ &_button {
+ height: 4.4rem;
+ border-radius: 0;
+ border: 1px solid $dark;
+ }
+
+ &_input {
+ flex-grow: 1;
+ padding: 0 1rem;
+ min-width: 100px;
+ border-right: none;
+
+ &::placeholder {
+ color: $dark;
+ opacity: 0.3;
+ }
+ }
+
+ &_button {
+ flex-shrink: 0;
+ padding: 0 1.5rem 0.2rem;
+ font-size: 1.8rem;
+ font-weight: 500;
+ background-color: $dark;
+ box-shadow: none;
+
+ &:active,
+ &:hover {
+ background-color: $primary;
+ border: 1px solid $primary;
+ }
+ }
+ }
+
+ &__codes {
+ padding-top: 2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ font-size: 1.8rem;
+
+ .code__item {
+ display: flex;
+ justify-content: space-between;
+ padding: 0.4rem 1rem 0.5rem;
+ background-color: rgba($primary, 0.15);
+ border-radius: 0.6rem;
+
+ &_button {
+ height: auto;
+ background-color: transparent;
+ box-shadow: none;
+ font-size: 1.8rem;
+ color: rgba($dark, 0.3);
+
+ &:hover {
+ color: rgba($primary, 1);
+ }
+ }
+ }
+ }
+
+ &__error {
+ margin: 0.5rem 0 -0.5rem;
+ text-align: right;
+ }
+ }
+}
+
+.popup {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba($dark, 0.3);
+ z-index: 100;
+
+ &__content {
+ padding: 4rem;
+ max-width: 48rem;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: $light;
+ border-radius: 4rem;
+ margin: 2rem;
+
+ @media all and (max-width: 480px) {
+ margin: 1rem;
+ padding: 3rem;
+ }
+ }
+
+ &__header {
+ text-align: center;
+ margin-bottom: 2rem;
+ }
+
+ &__button {
+ @include btn-primary();
+ width: 100%;
+ }
+}
diff --git a/src/pages/Cart/cart.module.scss b/src/pages/Cart/cart.module.scss
new file mode 100644
index 0000000..2efee45
--- /dev/null
+++ b/src/pages/Cart/cart.module.scss
@@ -0,0 +1,101 @@
+@import '../../styles/common/';
+
+.cart {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+
+ &__container {
+ width: 100%;
+ padding: 10rem 2rem 6rem;
+ display: flex;
+ justify-content: space-between;
+ gap: 3rem;
+
+ @media all and (max-width: $cart-tablet-width) {
+ flex-direction: column;
+ gap: 8rem;
+ }
+
+ @media all and (max-width: 480px) {
+ padding: 9rem 1rem 6rem;
+ }
+ }
+}
+
+.items {
+ width: 100%;
+
+ &__title {
+ @include font(semibold, h1, $dark);
+ margin-bottom: 2rem;
+ }
+
+ &__button_clear {
+ @include btn-secondary();
+ }
+}
+
+.popup {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba($dark, 0.3);
+ z-index: 100;
+
+ &__content {
+ padding: 4rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: $light;
+ border-radius: 4rem;
+ margin: 2rem;
+
+ @media all and (max-width: 480px) {
+ margin: 1rem;
+ padding: 3rem;
+ }
+ }
+
+ &__header {
+ text-align: center;
+ margin-bottom: 2rem;
+ }
+
+ &__buttons {
+ width: 100%;
+ display: flex;
+ gap: 1rem;
+
+ &_cancel {
+ @include btn-secondary();
+ flex-shrink: 0;
+ width: fit-content;
+ padding: 0 3rem;
+ }
+
+ &_confirm {
+ @include btn-primary();
+ flex-grow: 1;
+ }
+
+ @media all and (max-width: 480px) {
+ flex-direction: column-reverse;
+
+ &_cancel {
+ width: 100%;
+ }
+ }
+ }
+}
+
+.error__message {
+ @include bottom-page-message($light, $error-color);
+}
diff --git a/src/pages/Cart/index.ts b/src/pages/Cart/index.ts
new file mode 100644
index 0000000..8a1d3f9
--- /dev/null
+++ b/src/pages/Cart/index.ts
@@ -0,0 +1,2 @@
+import Cart from './Cart';
+export { Cart };
diff --git a/src/pages/Catalog/Catalog.tsx b/src/pages/Catalog/Catalog.tsx
new file mode 100644
index 0000000..4b9fde6
--- /dev/null
+++ b/src/pages/Catalog/Catalog.tsx
@@ -0,0 +1,47 @@
+import { ReactElement, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { Breadcrumbs } from '../../components/Breadcrumbs';
+import { SearchField } from '../../components/SearchField';
+import { SortingField } from '../../components/SortingField';
+import { Filters } from '../../components/Filters';
+import { ProductsList } from '../../components/ProductsList';
+import { CategoryFilter } from '../../components/Filters/CategoryFilter';
+import { ModalFilters } from './ModalFilters';
+
+import styles from './catalog.module.scss';
+
+function Catalog(): ReactElement {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+
+
+ {}} />
+
+
+
+
+
+ setIsOpen(true)}>
+ Filters
+
+
+
+
+
+
+
+ {isOpen && setIsOpen(false)} />}
+
+ );
+}
+
+export default Catalog;
diff --git a/src/pages/Catalog/ModalFilters/ModalFilters.tsx b/src/pages/Catalog/ModalFilters/ModalFilters.tsx
new file mode 100644
index 0000000..26ff2dd
--- /dev/null
+++ b/src/pages/Catalog/ModalFilters/ModalFilters.tsx
@@ -0,0 +1,42 @@
+import { ReactElement, useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import classnames from 'classnames';
+import { Filters } from '../../../components/Filters';
+
+import styles from './modalFilters.module.scss';
+
+interface ModalFiltersProps {
+ onClick: () => void;
+ className?: string;
+}
+
+function ModalFilters({ className, onClick }: ModalFiltersProps): ReactElement {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [prevParams] = useState(searchParams);
+
+ useEffect(() => {
+ document.body.className = 'stop-scroll';
+
+ return () => {
+ document.body.className = '';
+ };
+ }, []);
+
+ function closeFilters(): void {
+ setSearchParams(prevParams);
+ onClick();
+ }
+
+ return (
+
+
+ Cancel
+
+
Filters
+
+
+
+ );
+}
+
+export default ModalFilters;
diff --git a/src/pages/Catalog/ModalFilters/index.ts b/src/pages/Catalog/ModalFilters/index.ts
new file mode 100644
index 0000000..443739b
--- /dev/null
+++ b/src/pages/Catalog/ModalFilters/index.ts
@@ -0,0 +1,3 @@
+import ModalFilters from './ModalFilters';
+
+export { ModalFilters };
diff --git a/src/pages/Catalog/ModalFilters/modalFilters.module.scss b/src/pages/Catalog/ModalFilters/modalFilters.module.scss
new file mode 100644
index 0000000..c07b06f
--- /dev/null
+++ b/src/pages/Catalog/ModalFilters/modalFilters.module.scss
@@ -0,0 +1,31 @@
+@import 'src/styles/common';
+
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 110;
+ background-color: $light-primary;
+ overflow: auto;
+ padding: 8rem 1rem;
+
+ &__title {
+ @include font(semibold, h1, $dark);
+ margin-bottom: 2rem;
+ }
+
+ &__btn {
+ @include font(semibold, p2, $dark);
+ opacity: 0.3;
+
+ position: absolute;
+ top: 2rem;
+ right: 1rem;
+ }
+
+ &__filters {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Catalog/catalog.module.scss b/src/pages/Catalog/catalog.module.scss
new file mode 100644
index 0000000..8ababd4
--- /dev/null
+++ b/src/pages/Catalog/catalog.module.scss
@@ -0,0 +1,101 @@
+@import 'src/styles/common';
+
+.catalog {
+ padding: 7.5rem 1.5rem;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+
+ @media all and (max-width: 480px) {
+ padding-top: 5.5rem;
+ }
+
+ &__filters {
+ @media all and (max-width: 768px) {
+ display: none;
+ }
+ }
+
+ &__search {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 2rem;
+ margin-bottom: 3.5rem;
+
+ @media all and (max-width: 768px) {
+ margin-bottom: 2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 5rem;
+ }
+ }
+
+ &__sorting {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ &__wrapper {
+ width: 100%;
+ display: grid;
+ grid-template-columns: minmax(23%, 1fr) 3fr;
+ gap: 2rem;
+
+ @media all and (max-width: 1024px) {
+ grid-template-columns: minmax(20%, 1fr) 2fr;
+ }
+
+ @media all and (max-width: 768px) {
+ display: block;
+ }
+ }
+
+ &__content {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ }
+
+ &__category {
+ margin-bottom: 5rem;
+ }
+
+ &__modal {
+ position: relative;
+ display: none;
+ padding: 0.5rem 0;
+ font-weight: 600;
+ transition: color 0.3s ease-in-out;
+
+ &::after {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 0.1rem;
+ left: 0;
+ bottom: 0;
+ background-color: $dark;
+ transition: background-color 0.3s ease-in-out;
+ }
+
+ &:hover {
+ color: $primary;
+
+ &::after {
+ background-color: $primary;
+ }
+ }
+
+ @media all and (max-width: 768px) {
+ display: block;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Catalog/index.ts b/src/pages/Catalog/index.ts
new file mode 100644
index 0000000..7d05545
--- /dev/null
+++ b/src/pages/Catalog/index.ts
@@ -0,0 +1,3 @@
+import Catalog from './Catalog';
+
+export { Catalog };
diff --git a/src/pages/Home/Home.test.tsx b/src/pages/Home/Home.test.tsx
new file mode 100644
index 0000000..d93453a
--- /dev/null
+++ b/src/pages/Home/Home.test.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from '@testing-library/react';
+import { Home } from '../Home';
+import * as reduxHooks from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+
+jest.mock('react-redux');
+jest.mock('../../constant/metaData', () => {});
+jest.mock('../../services/sdk/auth/methods', () => {});
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../services/sdk/catalog/methods', () => {});
+jest.mock('../../services/sdk/customer/methods', () => {});
+jest.mock('../../services/sdk/cart/methods', () => {});
+
+const useDispatchSpy = jest.spyOn(reduxHooks, 'useDispatch');
+
+describe('Test Home page: ', () => {
+ test('should render Home page without success register message', () => {
+ jest.spyOn(reduxHooks, 'useSelector').mockReturnValue(false);
+ useDispatchSpy.mockReturnValue(jest.fn());
+ const component = render(
+
+
+ ,
+ );
+ expect(screen.getByTestId('home')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('should render Home page with success register message', () => {
+ jest.spyOn(reduxHooks, 'useSelector').mockReturnValue(true);
+ useDispatchSpy.mockReturnValue(jest.fn());
+ const component = render(
+
+
+ ,
+ );
+ expect(screen.getByText('Hello and welcome!ヾ(☆▽☆)')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
new file mode 100644
index 0000000..145991f
--- /dev/null
+++ b/src/pages/Home/Home.tsx
@@ -0,0 +1,81 @@
+import { ReactElement, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import classnames from 'classnames';
+import { selectIsNewUser } from '../../store/auth/selectors';
+import { useAppDispatch } from '../../store/store';
+import { Page } from '../../router/types';
+import { PATH } from '../../router/constants/paths';
+import { deleteNotice } from '../../store/auth/slice';
+
+import styles from './home.module.scss';
+
+export default function Home(): ReactElement {
+ const isNewUser = useSelector(selectIsNewUser);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (isNewUser) {
+ const timer = setTimeout(() => {
+ dispatch(deleteNotice());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [isNewUser, dispatch]);
+
+ return (
+
+
+
+
+ Main
+
+
+ Catalog
+
+
+ Login
+
+
+ Register
+
+
+ Profile
+
+
+ Cart
+
+
+ About
+
+
+
+
+
+
+
+ To keep the project's concept clean, we use word ⭑"Artists"⋆。★ instead of "Brands".
+
+
+ For ☆。discounted⭑ products check these artists: Cédrix Crespel, KAWS
+
+
+ For ⭑a single image⋆。★ in the slider check these artists: Jef Verheyen, Masoami Raku
+
+
+ For ☆。a promo code★ use these codes: ART (-10%) and Special (-5%) for all items in a cart
+
+
+
+
+ {isNewUser && (
+
+ Hello and welcome!ヾ(☆▽☆)
+
+ )}
+
+ );
+}
diff --git a/src/pages/Home/__snapshots__/Home.test.tsx.snap b/src/pages/Home/__snapshots__/Home.test.tsx.snap
new file mode 100644
index 0000000..b929df0
--- /dev/null
+++ b/src/pages/Home/__snapshots__/Home.test.tsx.snap
@@ -0,0 +1,505 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test Home page: should render Home page with success register message 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+
+
+ To keep the project's concept clean, we use word ⭑
+
+ "Artists"
+
+ ⋆。★ instead of "Brands".
+
+
+ For ☆。
+
+ discounted
+
+ ⭑ products check these artists: Cédrix Crespel, KAWS
+
+
+ For ⭑
+
+ a single image
+
+ ⋆。★ in the slider check these artists: Jef Verheyen, Masoami Raku
+
+
+ For ☆。
+
+ a promo code
+
+ ★ use these codes: ART (-10%) and Special (-5%) for all items in a cart
+
+
+
+
+
+ Hello and welcome!ヾ(☆▽☆)
+
+
+
+
+ ,
+ "container":
+
+
+
+
+
+ To keep the project's concept clean, we use word ⭑
+
+ "Artists"
+
+ ⋆。★ instead of "Brands".
+
+
+ For ☆。
+
+ discounted
+
+ ⭑ products check these artists: Cédrix Crespel, KAWS
+
+
+ For ⭑
+
+ a single image
+
+ ⋆。★ in the slider check these artists: Jef Verheyen, Masoami Raku
+
+
+ For ☆。
+
+ a promo code
+
+ ★ use these codes: ART (-10%) and Special (-5%) for all items in a cart
+
+
+
+
+
+ Hello and welcome!ヾ(☆▽☆)
+
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
+
+exports[`Test Home page: should render Home page without success register message 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+
+
+ To keep the project's concept clean, we use word ⭑
+
+ "Artists"
+
+ ⋆。★ instead of "Brands".
+
+
+ For ☆。
+
+ discounted
+
+ ⭑ products check these artists: Cédrix Crespel, KAWS
+
+
+ For ⭑
+
+ a single image
+
+ ⋆。★ in the slider check these artists: Jef Verheyen, Masoami Raku
+
+
+ For ☆。
+
+ a promo code
+
+ ★ use these codes: ART (-10%) and Special (-5%) for all items in a cart
+
+
+
+
+
+ ,
+ "container":
+
+
+
+
+
+ To keep the project's concept clean, we use word ⭑
+
+ "Artists"
+
+ ⋆。★ instead of "Brands".
+
+
+ For ☆。
+
+ discounted
+
+ ⭑ products check these artists: Cédrix Crespel, KAWS
+
+
+ For ⭑
+
+ a single image
+
+ ⋆。★ in the slider check these artists: Jef Verheyen, Masoami Raku
+
+
+ For ☆。
+
+ a promo code
+
+ ★ use these codes: ART (-10%) and Special (-5%) for all items in a cart
+
+
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/pages/Home/home.module.scss b/src/pages/Home/home.module.scss
new file mode 100644
index 0000000..6499bd8
--- /dev/null
+++ b/src/pages/Home/home.module.scss
@@ -0,0 +1,76 @@
+@import '../../styles/common/';
+
+.home {
+ padding: 10rem 2rem 8rem;
+ width: 100%;
+ height: 100%;
+ background-image: url('/images/bg-home.jpg');
+ background-size: cover;
+ background-position: left;
+ background-repeat: no-repeat;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 1rem;
+
+ &__content {
+ max-width: 52rem;
+ width: 100%;
+
+ @media all and (max-width: 480px) {
+ padding: 0 1rem;
+ }
+
+ &_container {
+ width: 100%;
+ padding: 4rem 3rem;
+ background-color: $light;
+ border-radius: 4rem;
+ border: 1px solid $dark;
+ text-align: center;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 0.5rem;
+ }
+ }
+
+ &__link {
+ @include link();
+ width: fit-content;
+ margin: 0 auto;
+ padding: 1px;
+ position: relative;
+ }
+}
+
+.info {
+ gap: 2rem;
+
+ em {
+ text-decoration: underline;
+ font-weight: 500;
+ }
+}
+
+.message {
+ max-width: 37rem;
+ width: 100%;
+ padding: 0.8rem 2rem;
+ background-color: $primary;
+ border-radius: $border-radius-btn;
+ box-shadow: 0 5px 5px rgba($dark, 0.2);
+ color: $light;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ bottom: 6rem;
+
+ transition: $transition-set;
+}
diff --git a/src/pages/Home/index.ts b/src/pages/Home/index.ts
new file mode 100644
index 0000000..a86ff67
--- /dev/null
+++ b/src/pages/Home/index.ts
@@ -0,0 +1,2 @@
+import Home from './Home';
+export { Home };
diff --git a/src/pages/Login/Login.test.tsx b/src/pages/Login/Login.test.tsx
new file mode 100644
index 0000000..7658eff
--- /dev/null
+++ b/src/pages/Login/Login.test.tsx
@@ -0,0 +1,26 @@
+import { render, screen } from '@testing-library/react';
+import * as reduxHooks from 'react-redux';
+import * as routerHooks from 'react-router-dom';
+import { Login } from './index';
+
+jest.mock('react-redux');
+jest.mock('react-router-dom');
+jest.mock('../../constant/metaData', () => {});
+jest.mock('../../services/sdk/auth/methods', () => {});
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../services/sdk/catalog/methods', () => {});
+jest.mock('../../services/sdk/customer/methods', () => {});
+jest.mock('../../services/sdk/cart/methods', () => {});
+
+const useDispatchSpy = jest.spyOn(reduxHooks, 'useDispatch');
+const useNavigateSpy = jest.spyOn(routerHooks, 'useNavigate');
+
+describe('Test Login page: ', () => {
+ test('should render Login page without error', () => {
+ useDispatchSpy.mockReturnValue(jest.fn());
+ useNavigateSpy.mockReturnValue(jest.fn());
+ const component = render();
+ expect(screen.getByText('Login')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/pages/Login/Login.tsx b/src/pages/Login/Login.tsx
new file mode 100644
index 0000000..c2e5dff
--- /dev/null
+++ b/src/pages/Login/Login.tsx
@@ -0,0 +1,51 @@
+import { ReactElement, useEffect } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { LoginForm } from '../../components/LoginForm';
+import { ErrorMessage } from '../../components/shared/ErrorMessage';
+import { PATH } from '../../router/constants/paths';
+import { selectAuthError, selectIsAuthorized } from '../../store/auth/selectors';
+import { Page } from '../../router/types';
+import { useAppDispatch } from '../../store/store';
+import { resetError } from '../../store/auth/slice';
+
+import styles from './login.module.scss';
+
+export default function Login(): ReactElement {
+ const navigate = useNavigate();
+ const isAuthorized = useSelector(selectIsAuthorized);
+ const error = useSelector(selectAuthError);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (isAuthorized) {
+ navigate(PATH[Page.Home]);
+ }
+
+ return (): void => {
+ dispatch(resetError());
+ };
+ }, [isAuthorized, navigate, dispatch]);
+
+ return (
+
+
+
+
+ Register
+
+
+
+ Login
+ to your account
+
+
+ {error &&
}
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/Login/__snapshots__/Login.test.tsx.snap b/src/pages/Login/__snapshots__/Login.test.tsx.snap
new file mode 100644
index 0000000..0afbd34
--- /dev/null
+++ b/src/pages/Login/__snapshots__/Login.test.tsx.snap
@@ -0,0 +1,228 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test Login page: should render Login page without error 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx
new file mode 100644
index 0000000..518a6d4
--- /dev/null
+++ b/src/pages/Login/index.tsx
@@ -0,0 +1,2 @@
+import Login from './Login';
+export { Login };
diff --git a/src/pages/Login/login.module.scss b/src/pages/Login/login.module.scss
new file mode 100644
index 0000000..ca4d503
--- /dev/null
+++ b/src/pages/Login/login.module.scss
@@ -0,0 +1,64 @@
+@import 'src/styles/common';
+
+.login {
+ width: 100%;
+ height: 100%;
+ display: flex;
+
+ &__container {
+ width: 54rem;
+ padding: 8rem 2rem 0;
+ margin: 0 0 12rem;
+
+ @media all and (max-width: 480px) {
+ padding: 1rem 1rem 0;
+ }
+ }
+
+ &__form {
+ width: 100%;
+ }
+
+ &__link {
+ @include link();
+ display: inline-block;
+ margin-bottom: 6rem;
+ }
+
+ &__title {
+ @include font(semibold, h1, $dark);
+ margin-bottom: 3rem;
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__image {
+ flex: 1;
+ background-image: url('/images/bg-form-2.jpg');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+
+ position: relative;
+ z-index: 1000;
+ }
+}
+
+@media all and (max-width: 800px) {
+ .login {
+ flex-direction: column;
+
+ &__container {
+ max-width: 80rem;
+ width: 100%;
+ margin-bottom: 6rem;
+ }
+
+ &__image {
+ flex: auto;
+ width: 100%;
+ height: 40rem;
+ background-position: top;
+ }
+ }
+}
diff --git a/src/pages/NotFound/NotFound.test.tsx b/src/pages/NotFound/NotFound.test.tsx
new file mode 100644
index 0000000..ae87d0f
--- /dev/null
+++ b/src/pages/NotFound/NotFound.test.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from '@testing-library/react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import userEvent from '@testing-library/user-event';
+import { NotFound } from './index';
+import { ReactElement } from 'react';
+import { PATH } from '../../router/constants/paths.ts';
+import { Page } from '../../router/types';
+
+jest.mock('../../constant/metaData', () => {});
+
+describe('Test NotFound page: ', () => {
+ test('should render NotFound page', () => {
+ const component = render(
+
+
+ ,
+ );
+ expect(screen.getByText('Page not found (´•̥̥̥o•̥̥̥`)')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('should redirect to Main page on link click', async () => {
+ const HomePage = (): ReactElement => Main page
;
+
+ render(
+
+
+ } />
+ } />
+
+ ,
+ );
+
+ const mainLink = screen.getByText('Back to Main');
+ expect(mainLink).toBeInTheDocument();
+
+ await userEvent.click(mainLink);
+ expect(screen.getByText('Main page')).toBeInTheDocument();
+ });
+});
diff --git a/src/pages/NotFound/NotFound.tsx b/src/pages/NotFound/NotFound.tsx
new file mode 100644
index 0000000..b3c1912
--- /dev/null
+++ b/src/pages/NotFound/NotFound.tsx
@@ -0,0 +1,20 @@
+import { ReactElement } from 'react';
+import { PATH } from '../../router/constants/paths';
+import { Page } from '../../router/types';
+import { Link } from 'react-router-dom';
+
+import styles from './notFound.module.scss';
+
+export default function NotFound(): ReactElement {
+ return (
+
+
+
Page not found (´•̥̥̥o•̥̥̥`)
+
That's sad... But you know you are always welcome on the main page, right?
+
+ Back to Main
+
+
+
+ );
+}
diff --git a/src/pages/NotFound/__snapshots__/NotFound.test.tsx.snap b/src/pages/NotFound/__snapshots__/NotFound.test.tsx.snap
new file mode 100644
index 0000000..f9b3dd9
--- /dev/null
+++ b/src/pages/NotFound/__snapshots__/NotFound.test.tsx.snap
@@ -0,0 +1,104 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test NotFound page: should render NotFound page 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+ Page not found (´•̥̥̥o•̥̥̥\`)
+
+
+ That's sad... But you know you are always welcome on the main page, right?
+
+
+ Back to Main
+
+
+
+
+ ,
+ "container":
+
+
+
+ Page not found (´•̥̥̥o•̥̥̥\`)
+
+
+ That's sad... But you know you are always welcome on the main page, right?
+
+
+ Back to Main
+
+
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/pages/NotFound/index.ts b/src/pages/NotFound/index.ts
new file mode 100644
index 0000000..4bd391f
--- /dev/null
+++ b/src/pages/NotFound/index.ts
@@ -0,0 +1,2 @@
+import NotFound from './NotFound';
+export { NotFound };
diff --git a/src/pages/NotFound/notFound.module.scss b/src/pages/NotFound/notFound.module.scss
new file mode 100644
index 0000000..ad6612c
--- /dev/null
+++ b/src/pages/NotFound/notFound.module.scss
@@ -0,0 +1,38 @@
+@import 'src/styles/common';
+
+.notFound {
+ width: 100%;
+ height: calc(100vh - 4rem);
+ background-image: url('/images/bg-404.jpg');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &__content {
+ max-width: 60rem;
+ width: 100%;
+ padding: 5rem;
+ background-color: $light;
+ border-radius: 4rem;
+ border: 1px solid $dark;
+ text-align: center;
+
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ margin: 0 2rem;
+
+ @media all and (max-width: 600px) {
+ padding: 4rem;
+ margin: 0 1rem;
+ }
+ }
+
+ &__btn {
+ @include btn-primary();
+ }
+}
diff --git a/src/pages/Product/Product.tsx b/src/pages/Product/Product.tsx
new file mode 100644
index 0000000..5730d3d
--- /dev/null
+++ b/src/pages/Product/Product.tsx
@@ -0,0 +1,51 @@
+import { ReactElement, useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import { RootState } from '../../store/store';
+import { productThunk } from '../../store/product/thunks';
+import { useAppDispatch } from '../../store/store';
+import { Slider } from '../../components/Slider/Slider';
+import { ProductDetails } from './ProductDetails';
+import { Loader } from '../../components/shared/Loader';
+import { NotFound } from '../NotFound';
+
+import styles from './product.module.scss';
+
+export default function Product(): ReactElement {
+ const id = useParams().id || '';
+
+ const { product, status, error } = useSelector((store: RootState) => store.product);
+ const { productId } = product;
+ const dispatch = useAppDispatch();
+
+ const [fullscreen, setFullscreen] = useState(false);
+
+ function handleFullScreen(): void {
+ setFullscreen((mode) => (mode = !mode));
+ }
+
+ useEffect(() => {
+ if (productId !== id) {
+ dispatch(productThunk(id));
+ }
+ }, [id, dispatch, productId]);
+
+ useEffect(() => {
+ document.body.style.overflow = fullscreen ? 'hidden' : '';
+ }, [fullscreen]);
+
+ return (
+
+ {status === 'loading' ? (
+
+ ) : error ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ );
+}
diff --git a/src/pages/Product/ProductDetails/ProductDetails.tsx b/src/pages/Product/ProductDetails/ProductDetails.tsx
new file mode 100644
index 0000000..c2e5362
--- /dev/null
+++ b/src/pages/Product/ProductDetails/ProductDetails.tsx
@@ -0,0 +1,120 @@
+import { ReactElement, useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { selectCart, selectCartError } from '../../../store/cart/selectors';
+import { useAppDispatch } from '../../../store/store';
+import { updateCartThunk } from '../../../store/cart/thunks';
+import { IProduct } from '../../../types/interfaces';
+import { formatPrice, splitToParagraphs } from '../../../utils';
+import { Button } from '../../../components/shared/Button';
+import { productAccordionData } from '../../../constant';
+import { Accordion } from '../../../components/shared/Accordion';
+import { ErrorMessage } from '../../../components/shared/ErrorMessage';
+
+import styles from './productDetails.module.scss';
+import classnames from 'classnames';
+import { resetCartError } from '../../../store/cart/slice';
+
+interface IProductDetailsProps {
+ product: IProduct;
+}
+
+export default function ProductDetails({ product }: IProductDetailsProps): ReactElement {
+ const cart = useSelector(selectCart);
+ const error = useSelector(selectCartError);
+
+ const { productId } = product;
+ const cartItem = cart.lineItems.find((item) => item.productId === productId);
+
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (error) {
+ const timer = setTimeout(() => {
+ dispatch(resetCartError());
+ }, 3000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }
+ }, [error, dispatch]);
+
+ function addToCart(): void {
+ dispatch(
+ updateCartThunk({
+ id: cart.id,
+ version: cart.version,
+ actions: [
+ {
+ action: 'addLineItem',
+ productId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ }),
+ );
+ }
+
+ function removeCartItem(): void {
+ dispatch(
+ updateCartThunk({
+ id: cart.id,
+ version: cart.version,
+ actions: [
+ {
+ action: 'removeLineItem',
+ lineItemId: cartItem?.itemId,
+ quantity: cartItem?.quantity,
+ },
+ ],
+ }),
+ );
+ }
+
+ return (
+ <>
+
+
+
{product.artist}
+
+ {product.title}, {product.year}
+
+
+ {product.discountPrice ? (
+ {formatPrice(product.discountPrice)}
+ ) : null}
+ {formatPrice(product.price)}
+
+
+ {!cartItem ? (
+
+ ) : (
+
+ )}
+
+
+
+
{splitToParagraphs(product.description)}
+
+ Technique:
+ {product.medium}
+
+
+ Size:
+ {product.dimensions}
+
+
+
+
+
+
+ {error && }
+ >
+ );
+}
diff --git a/src/pages/Product/ProductDetails/index.ts b/src/pages/Product/ProductDetails/index.ts
new file mode 100644
index 0000000..5f784c2
--- /dev/null
+++ b/src/pages/Product/ProductDetails/index.ts
@@ -0,0 +1,2 @@
+import ProductDetails from './ProductDetails';
+export { ProductDetails };
diff --git a/src/pages/Product/ProductDetails/productDetails.module.scss b/src/pages/Product/ProductDetails/productDetails.module.scss
new file mode 100644
index 0000000..951b648
--- /dev/null
+++ b/src/pages/Product/ProductDetails/productDetails.module.scss
@@ -0,0 +1,122 @@
+@import '../../../styles/common';
+
+.product__details {
+ max-width: 50vw;
+ width: 100%;
+ padding: 6rem 2rem 10rem 0;
+
+ transition: all 0.1s;
+
+ @media all and (max-width: 850px) {
+ max-width: 100vw;
+ padding: 2rem 2rem 12rem;
+ }
+
+ @media all and (max-width: 480px) {
+ padding: 2rem 1rem 12rem;
+ }
+}
+
+.artist {
+ font-size: 4rem;
+}
+
+.title {
+ font-size: 4rem;
+ margin-bottom: 6rem;
+}
+
+.price {
+ display: flex;
+ gap: 2rem;
+ font-size: 3rem;
+ font-weight: 600;
+ margin-bottom: 2rem;
+
+ @media all and (max-width: 480px) {
+ font-size: 2.9rem;
+ }
+
+ &.price__double {
+ width: 100%;
+
+ .price__full {
+ color: $text-grey;
+ display: inline;
+ position: relative;
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: -3%;
+ top: calc(50% - 2px);
+ width: 106%;
+ height: 4px;
+ background-color: $primary;
+ z-index: 10;
+ }
+ }
+ }
+}
+
+.button {
+ width: 100%;
+ margin-bottom: 9rem;
+
+ &__remove {
+ color: $dark;
+ font-weight: 600;
+ background-color: $btn-grey;
+ box-shadow: none;
+
+ &:hover {
+ color: $dark;
+ background-color: $btn-dark-grey;
+ }
+ }
+
+ @media all and (max-width: 480px) {
+ font-size: 2.9rem;
+ margin-bottom: 7rem;
+
+ &:hover {
+ font-size: 2.88rem;
+ }
+ }
+}
+
+.info {
+ width: 100%;
+ margin-bottom: 4rem;
+
+ &__title {
+ font-size: 2rem;
+ margin-bottom: 2rem;
+ }
+
+ &__description {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ .text {
+ &:first-child {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ margin-bottom: 4rem;
+ }
+
+ &:not(:first-child) {
+ span {
+ text-decoration: underline;
+ padding-right: 1rem;
+ }
+ }
+ }
+ }
+}
+
+.error__message {
+ @include bottom-page-message($light, $error-color);
+}
diff --git a/src/pages/Product/index.ts b/src/pages/Product/index.ts
new file mode 100644
index 0000000..944f4cb
--- /dev/null
+++ b/src/pages/Product/index.ts
@@ -0,0 +1,2 @@
+import Product from './Product';
+export { Product };
diff --git a/src/pages/Product/product.module.scss b/src/pages/Product/product.module.scss
new file mode 100644
index 0000000..d949d2d
--- /dev/null
+++ b/src/pages/Product/product.module.scss
@@ -0,0 +1,17 @@
+@import '../../styles/common';
+
+.product {
+ padding-top: 6rem;
+ width: 100%;
+ display: flex;
+ justify-content: flex-start;
+ gap: 2rem;
+
+ @media all and (max-width: 850px) {
+ flex-direction: column;
+ }
+
+ @media all and (max-width: 480px) {
+ padding-top: 5rem;
+ }
+}
diff --git a/src/pages/Profile/Profile.test.tsx b/src/pages/Profile/Profile.test.tsx
new file mode 100644
index 0000000..5b2d130
--- /dev/null
+++ b/src/pages/Profile/Profile.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen } from '@testing-library/react';
+import * as reduxHooks from 'react-redux';
+import * as routerHooks from 'react-router-dom';
+import { Profile } from './index';
+import { EditingProvider } from './profileContext.tsx';
+import { initialUserMock } from '../../__mocks__/store';
+
+jest.mock('react-redux');
+jest.mock('react-router-dom');
+jest.mock('../../constant/metaData', () => {});
+jest.mock('../../services/sdk/auth/methods', () => {});
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../services/sdk/catalog/methods', () => {});
+jest.mock('../../services/sdk/customer/methods', () => {});
+jest.mock('../../services/sdk/cart/methods', () => {});
+
+const useDispatchSpy = jest.spyOn(reduxHooks, 'useDispatch');
+const useSelectorSpy = jest.spyOn(reduxHooks, 'useSelector');
+const useNavigateSpy = jest.spyOn(routerHooks, 'useNavigate');
+
+describe('Test Profile page: ', () => {
+ test('should render Profile page', () => {
+ useNavigateSpy.mockReturnValue(jest.fn());
+ useDispatchSpy.mockReturnValue(jest.fn());
+ useSelectorSpy
+ .mockReturnValueOnce(() => true)
+ .mockReturnValueOnce(() => initialUserMock)
+ .mockReturnValueOnce(() => ({ status: 'success', error: '' }));
+
+ render(
+
+
+ ,
+ );
+ expect(screen.getByTestId('profile')).toBeInTheDocument();
+ });
+});
diff --git a/src/pages/Profile/Profile.tsx b/src/pages/Profile/Profile.tsx
new file mode 100644
index 0000000..e0bf8c8
--- /dev/null
+++ b/src/pages/Profile/Profile.tsx
@@ -0,0 +1,52 @@
+import styles from './profile.module.scss';
+import { ReactElement, useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useAppDispatch } from '../../store/store';
+import { selectUserData, selectUserLoadingInfo } from '../../store/user/selectors';
+import { getUserThunk } from '../../store/user/thunks';
+import { Loader } from '../../components/shared/Loader';
+import { ErrorMessage } from '../../components/shared/ErrorMessage';
+import { Tabs } from '../../components/Profile/Tabs';
+import { useIsEditMode } from './profileContext';
+import { GreetingTitle } from '../../components/Profile/GreetingTitle';
+import { selectIsAuthorized } from '../../store/auth/selectors';
+import { PATH } from '../../router/constants/paths.ts';
+import { Page } from '../../router/types';
+import { useNavigate } from 'react-router-dom';
+
+export default function Profile(): ReactElement {
+ const navigate = useNavigate();
+
+ const isAuthorized = useSelector(selectIsAuthorized);
+ const user = useSelector(selectUserData);
+ const { status, error } = useSelector(selectUserLoadingInfo);
+
+ const isEditMode = useIsEditMode();
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (!user.id) {
+ dispatch(getUserThunk());
+ }
+
+ if (!isAuthorized) {
+ navigate(PATH[Page.Login]);
+ }
+ }, [user, dispatch, isAuthorized, navigate]);
+
+ return (
+
+
+ {status === 'loading' &&
}
+ {error &&
}
+
+ {status === 'success' && !error && (
+
+ {!isEditMode && }
+
+
+ )}
+
+
+ );
+}
diff --git a/src/pages/Profile/index.ts b/src/pages/Profile/index.ts
new file mode 100644
index 0000000..5103fd5
--- /dev/null
+++ b/src/pages/Profile/index.ts
@@ -0,0 +1,2 @@
+import Profile from './Profile';
+export { Profile };
diff --git a/src/pages/Profile/profile.module.scss b/src/pages/Profile/profile.module.scss
new file mode 100644
index 0000000..b176b49
--- /dev/null
+++ b/src/pages/Profile/profile.module.scss
@@ -0,0 +1,32 @@
+@import '../../styles/common';
+
+.main__container {
+ height: 100%;
+ background-image: url('../../../public/images/bg-home.jpg');
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+}
+
+.root {
+ margin: auto;
+ height: 100%;
+ max-width: 72rem;
+ background-color: $light-primary;
+
+ &__container {
+ padding: 4rem;
+ padding-top: 10rem;
+ position: relative;
+ height: 100%;
+
+ @media(max-width: 720px) {
+ padding: 2rem;
+ padding-top: 10rem;
+ }
+
+ @media(max-width: 480px) {
+ padding: 10rem 1rem 4rem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Profile/profileContext.tsx b/src/pages/Profile/profileContext.tsx
new file mode 100644
index 0000000..477943f
--- /dev/null
+++ b/src/pages/Profile/profileContext.tsx
@@ -0,0 +1,45 @@
+import { ReactElement, ReactNode, createContext, useContext, useState } from 'react';
+
+type EditContextType = boolean;
+type UpdateEditModeType = (value?: boolean) => void;
+
+const EditContext = createContext(undefined);
+const EditUpdateContext = createContext(undefined);
+
+export function useIsEditMode(): EditContextType {
+ const context = useContext(EditContext);
+ if (context === undefined) {
+ throw new Error('useIsEditMode must be used within an EditingProvider');
+ }
+ return context;
+}
+
+export function useUpdateEditMode(): UpdateEditModeType {
+ const context = useContext(EditUpdateContext);
+ if (context === undefined) {
+ throw new Error('useUpdateEditMode must be used within an EditingProvider');
+ }
+ return context;
+}
+
+interface EditingProviderProps {
+ children: ReactNode;
+}
+
+export function EditingProvider({ children }: EditingProviderProps): ReactElement {
+ const [isEditMode, setEditMode] = useState(false);
+
+ function updateEditMode(value?: EditContextType): void {
+ if (value !== undefined) {
+ setEditMode(value);
+ } else {
+ setEditMode((prevEditMode) => !prevEditMode);
+ }
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/pages/Register/Register.test.tsx b/src/pages/Register/Register.test.tsx
new file mode 100644
index 0000000..01fd8bd
--- /dev/null
+++ b/src/pages/Register/Register.test.tsx
@@ -0,0 +1,26 @@
+import { render, screen } from '@testing-library/react';
+import * as reduxHooks from 'react-redux';
+import * as routerHooks from 'react-router-dom';
+import { Register } from './index';
+
+jest.mock('react-redux');
+jest.mock('react-router-dom');
+jest.mock('../../constant/metaData', () => {});
+jest.mock('../../services/sdk/auth/methods', () => {});
+jest.mock('../../services/sdk/product/methods', () => {});
+jest.mock('../../services/sdk/catalog/methods', () => {});
+jest.mock('../../services/sdk/customer/methods', () => {});
+jest.mock('../../services/sdk/cart/methods', () => {});
+
+const useDispatchSpy = jest.spyOn(reduxHooks, 'useDispatch');
+const useNavigateSpy = jest.spyOn(routerHooks, 'useNavigate');
+
+describe('Test Register page: ', () => {
+ test('should render Register page without error', () => {
+ useDispatchSpy.mockReturnValue(jest.fn());
+ useNavigateSpy.mockReturnValue(jest.fn());
+ const component = render();
+ expect(screen.getByText('Register')).toBeInTheDocument();
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/pages/Register/Register.tsx b/src/pages/Register/Register.tsx
new file mode 100644
index 0000000..a328ccd
--- /dev/null
+++ b/src/pages/Register/Register.tsx
@@ -0,0 +1,51 @@
+import { ReactElement, useEffect } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { RegisterForm } from '../../components/RegisterForm';
+import { ErrorMessage } from '../../components/shared/ErrorMessage';
+import { PATH } from '../../router/constants/paths';
+import { selectAuthError, selectIsAuthorized } from '../../store/auth/selectors';
+import { Page } from '../../router/types';
+import { useAppDispatch } from '../../store/store';
+import { resetError } from '../../store/auth/slice';
+
+import styles from './register.module.scss';
+
+export default function Register(): ReactElement {
+ const navigate = useNavigate();
+ const isAuthorized = useSelector(selectIsAuthorized);
+ const error = useSelector(selectAuthError);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (isAuthorized) {
+ navigate(PATH[Page.Home]);
+ }
+
+ return (): void => {
+ dispatch(resetError());
+ };
+ }, [isAuthorized, navigate, dispatch]);
+
+ return (
+
+
+
+
+ Login
+
+
+
+ Register
+ a new account
+
+
+ {error &&
}
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/Register/__snapshots__/Register.test.tsx.snap b/src/pages/Register/__snapshots__/Register.test.tsx.snap
new file mode 100644
index 0000000..02d617d
--- /dev/null
+++ b/src/pages/Register/__snapshots__/Register.test.tsx.snap
@@ -0,0 +1,2146 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Test Register page: should render Register page without error 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/pages/Register/index.ts b/src/pages/Register/index.ts
new file mode 100644
index 0000000..9077cd8
--- /dev/null
+++ b/src/pages/Register/index.ts
@@ -0,0 +1,2 @@
+import Register from './Register';
+export { Register };
diff --git a/src/pages/Register/register.module.scss b/src/pages/Register/register.module.scss
new file mode 100644
index 0000000..d096668
--- /dev/null
+++ b/src/pages/Register/register.module.scss
@@ -0,0 +1,64 @@
+@import 'src/styles/common';
+
+.register {
+ width: 100%;
+ height: 100%;
+ display: flex;
+
+ &__container {
+ width: 54rem;
+ padding: 8rem 2rem 0;
+ margin: 0 0 12rem;
+
+ @media all and (max-width: 480px) {
+ padding: 1rem 1rem 0;
+ }
+ }
+
+ &__form {
+ width: 100%;
+ }
+
+ &__link {
+ @include link();
+ display: inline-block;
+ margin-bottom: 6rem;
+ }
+
+ &__title {
+ @include font(semibold, h1, $dark);
+ margin-bottom: 3rem;
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__image {
+ flex: 1;
+ background-image: url('/images/bg-form.jpg');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+
+ position: relative;
+ z-index: 1000;
+ }
+}
+
+@media all and (max-width: 800px) {
+ .register {
+ flex-direction: column;
+
+ &__container {
+ max-width: 80rem;
+ width: 100%;
+ margin-bottom: 6rem;
+ }
+
+ &__image {
+ flex: auto;
+ width: 100%;
+ height: 40rem;
+ background-position: top;
+ }
+ }
+}
diff --git a/src/router/Router/Router.tsx b/src/router/Router/Router.tsx
new file mode 100644
index 0000000..3ba6966
--- /dev/null
+++ b/src/router/Router/Router.tsx
@@ -0,0 +1,43 @@
+import { ReactElement } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import { Home } from '../../pages/Home';
+import { Login } from '../../pages/Login';
+import { Register } from '../../pages/Register';
+import { NotFound } from '../../pages/NotFound';
+import { Layout } from '../hoc/Layout';
+import { Cart } from '../../pages/Cart';
+import { Profile } from '../../pages/Profile';
+import { ProtectedRoute } from '../hoc/ProtectedRoute';
+import { PATH } from '../constants/paths';
+import { Page } from '../types';
+import { Product } from '../../pages/Product';
+import { Catalog } from '../../pages/Catalog';
+import { EditingProvider } from '../../pages/Profile/profileContext';
+import { AboutUs } from '../../pages/AboutUs';
+
+export default function Router(): ReactElement {
+ return (
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+ }
+ />
+ }>
+
+
+ );
+}
diff --git a/src/router/Router/index.ts b/src/router/Router/index.ts
new file mode 100644
index 0000000..628af0a
--- /dev/null
+++ b/src/router/Router/index.ts
@@ -0,0 +1,2 @@
+import Router from './Router';
+export { Router };
diff --git a/src/router/constants/paths.ts b/src/router/constants/paths.ts
new file mode 100644
index 0000000..1c74e1d
--- /dev/null
+++ b/src/router/constants/paths.ts
@@ -0,0 +1,13 @@
+import { Page } from '../types';
+
+export const PATH = {
+ [Page.About]: '/about',
+ [Page.Home]: '/',
+ [Page.Login]: '/login',
+ [Page.Register]: '/register',
+ [Page.Profile]: '/profile',
+ [Page.Cart]: '/cart',
+ [Page.Product]: '/product',
+ [Page.Catalog]: '/catalog',
+ [Page.NotFound]: '*',
+};
diff --git a/src/router/hoc/Layout/Layout.tsx b/src/router/hoc/Layout/Layout.tsx
new file mode 100644
index 0000000..3e2ac55
--- /dev/null
+++ b/src/router/hoc/Layout/Layout.tsx
@@ -0,0 +1,18 @@
+import { ReactElement } from 'react';
+import { Header } from '../../../components/Header';
+import { Footer } from '../../../components/Footer';
+import { Outlet } from 'react-router-dom';
+
+export default function Layout(): ReactElement {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/router/hoc/Layout/index.ts b/src/router/hoc/Layout/index.ts
new file mode 100644
index 0000000..54c8590
--- /dev/null
+++ b/src/router/hoc/Layout/index.ts
@@ -0,0 +1,2 @@
+import Layout from './Layout.tsx';
+export { Layout };
diff --git a/src/router/hoc/ProtectedRoute/ProtectedRoute.test.tsx b/src/router/hoc/ProtectedRoute/ProtectedRoute.test.tsx
new file mode 100644
index 0000000..4f20834
--- /dev/null
+++ b/src/router/hoc/ProtectedRoute/ProtectedRoute.test.tsx
@@ -0,0 +1,47 @@
+import { render, screen } from '@testing-library/react';
+import { Navigate } from 'react-router-dom';
+import { PATH } from '../../constants/paths';
+import ProtectedRoute from './ProtectedRoute';
+
+import * as reduxHooks from 'react-redux';
+
+jest.mock('react-redux');
+jest.mock('react-router-dom');
+
+const mockedUseSelector = jest.spyOn(reduxHooks, 'useSelector');
+
+describe('Test ProtectedRoute component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should render children when isAuthorized is true', () => {
+ mockedUseSelector.mockReturnValue(true);
+
+ const protectedContent = 'Protected content';
+
+ const { container } = render(
+
+ {protectedContent}
+ ,
+ );
+
+ expect(container.textContent).toBe(protectedContent);
+ expect(Navigate).not.toHaveBeenCalled();
+ });
+
+ it('should redirect when isAuthorized is false', () => {
+ mockedUseSelector.mockReturnValue(false);
+
+ const protectedContent = 'Protected content';
+
+ render(
+
+ {protectedContent}
+ ,
+ );
+
+ expect(Navigate).toHaveBeenCalled();
+ expect(screen.getByText).not.toBe(protectedContent);
+ });
+});
diff --git a/src/router/hoc/ProtectedRoute/ProtectedRoute.tsx b/src/router/hoc/ProtectedRoute/ProtectedRoute.tsx
new file mode 100644
index 0000000..1184bcf
--- /dev/null
+++ b/src/router/hoc/ProtectedRoute/ProtectedRoute.tsx
@@ -0,0 +1,13 @@
+import { ReactElement } from 'react';
+import { Navigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { selectIsAuthorized } from '../../../store/auth/selectors';
+import { IProtectedRoute } from '../../types';
+
+export default function ProtectedRoute({ children, redirectLink }: IProtectedRoute): ReactElement {
+ const isAuthorized = useSelector(selectIsAuthorized);
+
+ if (!isAuthorized) return ;
+
+ return children;
+}
diff --git a/src/router/hoc/ProtectedRoute/index.ts b/src/router/hoc/ProtectedRoute/index.ts
new file mode 100644
index 0000000..054c5a2
--- /dev/null
+++ b/src/router/hoc/ProtectedRoute/index.ts
@@ -0,0 +1,2 @@
+import ProtectedRoute from './ProtectedRoute';
+export { ProtectedRoute };
diff --git a/src/router/types/index.ts b/src/router/types/index.ts
new file mode 100644
index 0000000..7ea1960
--- /dev/null
+++ b/src/router/types/index.ts
@@ -0,0 +1,18 @@
+import { ReactElement } from 'react';
+
+export interface IProtectedRoute {
+ children: ReactElement;
+ redirectLink: string;
+}
+
+export enum Page {
+ About = 'about',
+ Home = 'home',
+ Login = 'login',
+ Register = 'register',
+ Profile = 'profile',
+ Cart = 'cart',
+ Catalog = 'catalog',
+ NotFound = 'notFound',
+ Product = 'product',
+}
diff --git a/src/services/sdk/auth/clients/anonymousFlowClient.ts b/src/services/sdk/auth/clients/anonymousFlowClient.ts
new file mode 100644
index 0000000..b920511
--- /dev/null
+++ b/src/services/sdk/auth/clients/anonymousFlowClient.ts
@@ -0,0 +1,7 @@
+import { ClientBuilder } from '@commercetools/sdk-client-v2';
+import { anonymousMiddleware, httpMiddlewareOptions } from '../middlewares';
+
+export const anonymousFlowClient = new ClientBuilder()
+ .withAnonymousSessionFlow(anonymousMiddleware)
+ .withHttpMiddleware(httpMiddlewareOptions)
+ .build();
diff --git a/src/services/sdk/auth/clients/authFlowClient.ts b/src/services/sdk/auth/clients/authFlowClient.ts
new file mode 100644
index 0000000..a6b3699
--- /dev/null
+++ b/src/services/sdk/auth/clients/authFlowClient.ts
@@ -0,0 +1,7 @@
+import { ClientBuilder } from '@commercetools/sdk-client-v2';
+import { authMiddlewareOptions, httpMiddlewareOptions } from '../middlewares';
+
+export const authFlowClient = new ClientBuilder()
+ .withClientCredentialsFlow(authMiddlewareOptions)
+ .withHttpMiddleware(httpMiddlewareOptions)
+ .build();
diff --git a/src/services/sdk/auth/clients/index.ts b/src/services/sdk/auth/clients/index.ts
new file mode 100644
index 0000000..058193f
--- /dev/null
+++ b/src/services/sdk/auth/clients/index.ts
@@ -0,0 +1,4 @@
+export * from './authFlowClient';
+export * from './passwordFlowClient';
+export * from './refreshTokenFlowClient';
+export * from './anonymousFlowClient';
diff --git a/src/services/sdk/auth/clients/passwordFlowClient.ts b/src/services/sdk/auth/clients/passwordFlowClient.ts
new file mode 100644
index 0000000..dbc53d3
--- /dev/null
+++ b/src/services/sdk/auth/clients/passwordFlowClient.ts
@@ -0,0 +1,8 @@
+import { Client, ClientBuilder, UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { httpMiddlewareOptions, passwordMiddlewareOptions } from '../middlewares';
+
+export const passwordFlowClient = (user: UserAuthOptions): Client =>
+ new ClientBuilder()
+ .withPasswordFlow(passwordMiddlewareOptions(user))
+ .withHttpMiddleware(httpMiddlewareOptions)
+ .build();
diff --git a/src/services/sdk/auth/clients/refreshTokenFlowClient.ts b/src/services/sdk/auth/clients/refreshTokenFlowClient.ts
new file mode 100644
index 0000000..81d6258
--- /dev/null
+++ b/src/services/sdk/auth/clients/refreshTokenFlowClient.ts
@@ -0,0 +1,8 @@
+import { Client, ClientBuilder } from '@commercetools/sdk-client-v2';
+import { httpMiddlewareOptions, refreshTokenMiddleware } from '../middlewares';
+
+export const refreshTokenFlowClient = (token: string): Client =>
+ new ClientBuilder()
+ .withRefreshTokenFlow(refreshTokenMiddleware(token))
+ .withHttpMiddleware(httpMiddlewareOptions)
+ .build();
diff --git a/src/services/sdk/auth/methods/anonLoginCustomer.ts b/src/services/sdk/auth/methods/anonLoginCustomer.ts
new file mode 100644
index 0000000..0bceefb
--- /dev/null
+++ b/src/services/sdk/auth/methods/anonLoginCustomer.ts
@@ -0,0 +1,19 @@
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { ClientResponse, CustomerSignInResult } from '@commercetools/platform-sdk';
+import { anonymousRoot } from '../roots';
+import { projectKey } from '../../index';
+
+export const anonLoginCustomer = (user: UserAuthOptions): Promise> => {
+ return anonymousRoot
+ .withProjectKey({ projectKey })
+ .me()
+ .login()
+ .post({
+ body: {
+ email: user.username,
+ password: user.password,
+ activeCartSignInMode: 'MergeWithExistingCustomerCart',
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/auth/methods/index.ts b/src/services/sdk/auth/methods/index.ts
new file mode 100644
index 0000000..f6807ce
--- /dev/null
+++ b/src/services/sdk/auth/methods/index.ts
@@ -0,0 +1,3 @@
+export * from './loginCustomer';
+export * from './registerCustomer';
+export * from './anonLoginCustomer';
diff --git a/src/services/sdk/auth/methods/loginCustomer.ts b/src/services/sdk/auth/methods/loginCustomer.ts
new file mode 100644
index 0000000..7c29c5f
--- /dev/null
+++ b/src/services/sdk/auth/methods/loginCustomer.ts
@@ -0,0 +1,17 @@
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { passwordRoot } from '../roots';
+import { projectKey } from '../../index.ts';
+import { ClientResponse, CustomerSignInResult } from '@commercetools/platform-sdk';
+
+export const loginCustomer = (user: UserAuthOptions): Promise> => {
+ return passwordRoot(user)
+ .withProjectKey({ projectKey })
+ .login()
+ .post({
+ body: {
+ email: user.username,
+ password: user.password,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/auth/methods/registerCustomer.ts b/src/services/sdk/auth/methods/registerCustomer.ts
new file mode 100644
index 0000000..2e3dab9
--- /dev/null
+++ b/src/services/sdk/auth/methods/registerCustomer.ts
@@ -0,0 +1,14 @@
+import { ClientResponse, CustomerSignInResult } from '@commercetools/platform-sdk';
+import { authRoot } from '../roots';
+import { projectKey } from '../../index.ts';
+import { INewUser } from '../../../../types/interfaces';
+
+export const registerCustomer = (userData: INewUser): Promise> => {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .post({
+ body: userData,
+ })
+ .execute();
+};
diff --git a/src/services/sdk/auth/middlewares/anonymousMiddleware.ts b/src/services/sdk/auth/middlewares/anonymousMiddleware.ts
new file mode 100644
index 0000000..e4aab99
--- /dev/null
+++ b/src/services/sdk/auth/middlewares/anonymousMiddleware.ts
@@ -0,0 +1,15 @@
+import { AnonymousAuthMiddlewareOptions } from '@commercetools/sdk-client-v2';
+import { VITE_CTP_AUTH_URL, VITE_CTP_CLIENT_ID, VITE_CTP_CLIENT_SECRET } from '../../../../constant';
+import { projectKey } from '../../index.ts';
+import { tokenData } from '../token';
+
+export const anonymousMiddleware: AnonymousAuthMiddlewareOptions = {
+ host: VITE_CTP_AUTH_URL,
+ projectKey: projectKey,
+ credentials: {
+ clientId: VITE_CTP_CLIENT_ID,
+ clientSecret: VITE_CTP_CLIENT_SECRET,
+ },
+ tokenCache: tokenData,
+ fetch,
+};
diff --git a/src/services/sdk/auth/middlewares/authMiddleware.ts b/src/services/sdk/auth/middlewares/authMiddleware.ts
new file mode 100644
index 0000000..f92f3dd
--- /dev/null
+++ b/src/services/sdk/auth/middlewares/authMiddleware.ts
@@ -0,0 +1,15 @@
+import { type AuthMiddlewareOptions } from '@commercetools/sdk-client-v2';
+import { projectKey } from '../../index';
+import { tokenData } from '../token';
+import { VITE_CTP_AUTH_URL, VITE_CTP_CLIENT_ID, VITE_CTP_CLIENT_SECRET } from '../../../../constant';
+
+export const authMiddlewareOptions: AuthMiddlewareOptions = {
+ host: VITE_CTP_AUTH_URL,
+ projectKey: projectKey,
+ credentials: {
+ clientId: VITE_CTP_CLIENT_ID,
+ clientSecret: VITE_CTP_CLIENT_SECRET,
+ },
+ tokenCache: tokenData,
+ fetch,
+};
diff --git a/src/services/sdk/auth/middlewares/httpMiddleware.ts b/src/services/sdk/auth/middlewares/httpMiddleware.ts
new file mode 100644
index 0000000..b6ce46a
--- /dev/null
+++ b/src/services/sdk/auth/middlewares/httpMiddleware.ts
@@ -0,0 +1,7 @@
+import { type HttpMiddlewareOptions } from '@commercetools/sdk-client-v2';
+import { VITE_CTP_API_URL } from '../../../../constant';
+
+export const httpMiddlewareOptions: HttpMiddlewareOptions = {
+ host: VITE_CTP_API_URL,
+ fetch,
+};
diff --git a/src/services/sdk/auth/middlewares/index.ts b/src/services/sdk/auth/middlewares/index.ts
new file mode 100644
index 0000000..f38d22d
--- /dev/null
+++ b/src/services/sdk/auth/middlewares/index.ts
@@ -0,0 +1,5 @@
+export * from './authMiddleware';
+export * from './httpMiddleware';
+export * from './passwordMiddleware';
+export * from './refreshTokenMiddleware';
+export * from './anonymousMiddleware';
diff --git a/src/services/sdk/auth/middlewares/passwordMiddleware.ts b/src/services/sdk/auth/middlewares/passwordMiddleware.ts
new file mode 100644
index 0000000..c880984
--- /dev/null
+++ b/src/services/sdk/auth/middlewares/passwordMiddleware.ts
@@ -0,0 +1,16 @@
+import { type PasswordAuthMiddlewareOptions, UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { projectKey } from '../../index';
+import { tokenData } from '../token';
+import { VITE_CTP_AUTH_URL, VITE_CTP_CLIENT_ID, VITE_CTP_CLIENT_SECRET } from '../../../../constant';
+
+export const passwordMiddlewareOptions = (user: UserAuthOptions): PasswordAuthMiddlewareOptions => ({
+ host: VITE_CTP_AUTH_URL,
+ projectKey: projectKey,
+ credentials: {
+ clientId: VITE_CTP_CLIENT_ID,
+ clientSecret: VITE_CTP_CLIENT_SECRET,
+ user,
+ },
+ tokenCache: tokenData,
+ fetch,
+});
diff --git a/src/services/sdk/auth/middlewares/refreshTokenMiddleware.ts b/src/services/sdk/auth/middlewares/refreshTokenMiddleware.ts
new file mode 100644
index 0000000..e93e75e
--- /dev/null
+++ b/src/services/sdk/auth/middlewares/refreshTokenMiddleware.ts
@@ -0,0 +1,16 @@
+import { RefreshAuthMiddlewareOptions } from '@commercetools/sdk-client-v2';
+import { VITE_CTP_AUTH_URL, VITE_CTP_CLIENT_ID, VITE_CTP_CLIENT_SECRET } from '../../../../constant';
+import { projectKey } from '../../index';
+import { tokenData } from '../token';
+
+export const refreshTokenMiddleware = (token: string): RefreshAuthMiddlewareOptions => ({
+ host: VITE_CTP_AUTH_URL,
+ projectKey: projectKey,
+ credentials: {
+ clientId: VITE_CTP_CLIENT_ID,
+ clientSecret: VITE_CTP_CLIENT_SECRET,
+ },
+ refreshToken: token,
+ tokenCache: tokenData,
+ fetch,
+});
diff --git a/src/services/sdk/auth/roots/index.ts b/src/services/sdk/auth/roots/index.ts
new file mode 100644
index 0000000..1b05a40
--- /dev/null
+++ b/src/services/sdk/auth/roots/index.ts
@@ -0,0 +1,13 @@
+import { ApiRoot, createApiBuilderFromCtpClient } from '@commercetools/platform-sdk';
+import { anonymousFlowClient, authFlowClient, passwordFlowClient, refreshTokenFlowClient } from '../clients';
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+
+export const authRoot = createApiBuilderFromCtpClient(authFlowClient);
+export const passwordRoot = (user: UserAuthOptions): ApiRoot => createApiBuilderFromCtpClient(passwordFlowClient(user));
+export const refreshTokenRoot = (): ApiRoot => {
+ const token = localStorage.getItem('art-token') || localStorage.getItem('art-anon-token') || '';
+
+ return createApiBuilderFromCtpClient(refreshTokenFlowClient(token));
+};
+
+export const anonymousRoot = createApiBuilderFromCtpClient(anonymousFlowClient);
diff --git a/src/services/sdk/auth/token/index.ts b/src/services/sdk/auth/token/index.ts
new file mode 100644
index 0000000..ab316a9
--- /dev/null
+++ b/src/services/sdk/auth/token/index.ts
@@ -0,0 +1,21 @@
+import { TokenStore } from '@commercetools/sdk-client-v2';
+
+export const initialTokenInfo: TokenStore = {
+ token: '',
+ expirationTime: 0,
+ refreshToken: '',
+};
+
+class TokenInfo {
+ private store: TokenStore = initialTokenInfo;
+
+ public get(): TokenStore {
+ return this.store;
+ }
+
+ public set(data: TokenStore): void {
+ this.store = data;
+ }
+}
+
+export const tokenData = new TokenInfo();
diff --git a/src/services/sdk/cart/methods/createCart.ts b/src/services/sdk/cart/methods/createCart.ts
new file mode 100644
index 0000000..7d2c40f
--- /dev/null
+++ b/src/services/sdk/cart/methods/createCart.ts
@@ -0,0 +1,17 @@
+import { Cart, ClientResponse } from '@commercetools/platform-sdk';
+import { refreshTokenRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export const createCart = (): Promise> => {
+ return refreshTokenRoot()
+ .withProjectKey({ projectKey })
+ .me()
+ .carts()
+ .post({
+ body: {
+ currency: 'USD',
+ country: 'US',
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/cart/methods/deleteCartById.ts b/src/services/sdk/cart/methods/deleteCartById.ts
new file mode 100644
index 0000000..78f31ba
--- /dev/null
+++ b/src/services/sdk/cart/methods/deleteCartById.ts
@@ -0,0 +1,21 @@
+import { Cart, ClientResponse } from '@commercetools/platform-sdk';
+import { refreshTokenRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export interface IDeleteCart {
+ id: string;
+ version: number;
+}
+
+export const deleteCartById = ({ id, version }: IDeleteCart): Promise> => {
+ return refreshTokenRoot()
+ .withProjectKey({ projectKey })
+ .carts()
+ .withId({ ID: id })
+ .delete({
+ queryArgs: {
+ version,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/cart/methods/getAnonCart.ts b/src/services/sdk/cart/methods/getAnonCart.ts
new file mode 100644
index 0000000..39ca8a6
--- /dev/null
+++ b/src/services/sdk/cart/methods/getAnonCart.ts
@@ -0,0 +1,7 @@
+import { CartPagedQueryResponse, ClientResponse } from '@commercetools/platform-sdk';
+import { anonymousRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export const getAnonCart = (): Promise> => {
+ return anonymousRoot.withProjectKey({ projectKey }).me().carts().get().execute();
+};
diff --git a/src/services/sdk/cart/methods/getCart.ts b/src/services/sdk/cart/methods/getCart.ts
new file mode 100644
index 0000000..cba6819
--- /dev/null
+++ b/src/services/sdk/cart/methods/getCart.ts
@@ -0,0 +1,7 @@
+import { CartPagedQueryResponse, ClientResponse } from '@commercetools/platform-sdk';
+import { refreshTokenRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export const getCart = (): Promise> => {
+ return refreshTokenRoot().withProjectKey({ projectKey }).me().carts().get().execute();
+};
diff --git a/src/services/sdk/cart/methods/getDiscountCodes.ts b/src/services/sdk/cart/methods/getDiscountCodes.ts
new file mode 100644
index 0000000..5561016
--- /dev/null
+++ b/src/services/sdk/cart/methods/getDiscountCodes.ts
@@ -0,0 +1,7 @@
+import { ClientResponse, DiscountCodePagedQueryResponse } from '@commercetools/platform-sdk';
+import { refreshTokenRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export const getDiscountCodes = (): Promise> => {
+ return refreshTokenRoot().withProjectKey({ projectKey }).discountCodes().get().execute();
+};
diff --git a/src/services/sdk/cart/methods/index.ts b/src/services/sdk/cart/methods/index.ts
new file mode 100644
index 0000000..6d170ab
--- /dev/null
+++ b/src/services/sdk/cart/methods/index.ts
@@ -0,0 +1,6 @@
+export * from './createCart';
+export * from './getCart';
+export * from './updateCart';
+export * from './deleteCartById';
+export * from './getDiscountCodes';
+export * from './getAnonCart';
diff --git a/src/services/sdk/cart/methods/updateCart.ts b/src/services/sdk/cart/methods/updateCart.ts
new file mode 100644
index 0000000..38e46d7
--- /dev/null
+++ b/src/services/sdk/cart/methods/updateCart.ts
@@ -0,0 +1,23 @@
+import { Cart, CartUpdateAction, ClientResponse } from '@commercetools/platform-sdk';
+import { refreshTokenRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export interface IUpdateCart {
+ id: string;
+ version: number;
+ actions: CartUpdateAction[];
+}
+
+export const updateCart = ({ id, version, actions }: IUpdateCart): Promise> => {
+ return refreshTokenRoot()
+ .withProjectKey({ projectKey })
+ .carts()
+ .withId({ ID: id })
+ .post({
+ body: {
+ version,
+ actions,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/catalog/methods/getCategories.ts b/src/services/sdk/catalog/methods/getCategories.ts
new file mode 100644
index 0000000..f8292eb
--- /dev/null
+++ b/src/services/sdk/catalog/methods/getCategories.ts
@@ -0,0 +1,7 @@
+import { CategoryPagedQueryResponse, ClientResponse } from '@commercetools/platform-sdk';
+import { authRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+
+export const getCategories = (): Promise> => {
+ return authRoot.withProjectKey({ projectKey }).categories().get().execute();
+};
diff --git a/src/services/sdk/catalog/methods/getProductList.ts b/src/services/sdk/catalog/methods/getProductList.ts
new file mode 100644
index 0000000..0359e5c
--- /dev/null
+++ b/src/services/sdk/catalog/methods/getProductList.ts
@@ -0,0 +1,16 @@
+import { ClientResponse, ProductProjectionPagedQueryResponse, QueryParam } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { authRoot } from '../../auth/roots';
+
+export function getProductList(params: {
+ [p: string]: QueryParam;
+}): Promise> {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .productProjections()
+ .search()
+ .get({
+ queryArgs: params,
+ })
+ .execute();
+}
diff --git a/src/services/sdk/catalog/methods/index.ts b/src/services/sdk/catalog/methods/index.ts
new file mode 100644
index 0000000..b4e2b5e
--- /dev/null
+++ b/src/services/sdk/catalog/methods/index.ts
@@ -0,0 +1,2 @@
+export * from './getCategories';
+export * from './getProductList';
diff --git a/src/services/sdk/customer/methods/addAddress.ts b/src/services/sdk/customer/methods/addAddress.ts
new file mode 100644
index 0000000..4224b9e
--- /dev/null
+++ b/src/services/sdk/customer/methods/addAddress.ts
@@ -0,0 +1,18 @@
+import { authRoot } from '../../auth/roots/index.ts';
+import { ClientResponse, Customer } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { IUpdateUser } from '../types/index.ts';
+
+export const addAddress = ({ id, version, action }: IUpdateUser): Promise> => {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .withId({ ID: id })
+ .post({
+ body: {
+ version,
+ actions: action,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/customer/methods/changeAddress.ts b/src/services/sdk/customer/methods/changeAddress.ts
new file mode 100644
index 0000000..7fae11d
--- /dev/null
+++ b/src/services/sdk/customer/methods/changeAddress.ts
@@ -0,0 +1,18 @@
+import { authRoot } from '../../auth/roots/index.ts';
+import { ClientResponse, Customer } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { IUpdateUser } from '../types/index.ts';
+
+export const changeAddress = ({ id, version, action }: IUpdateUser): Promise> => {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .withId({ ID: id })
+ .post({
+ body: {
+ version,
+ actions: action,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/customer/methods/changeCustomerPassword.ts b/src/services/sdk/customer/methods/changeCustomerPassword.ts
new file mode 100644
index 0000000..b38c8c3
--- /dev/null
+++ b/src/services/sdk/customer/methods/changeCustomerPassword.ts
@@ -0,0 +1,20 @@
+import { authRoot } from '../../auth/roots/index.ts';
+import { ClientResponse, CustomerChangePassword } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+
+export const changeCustomerPassword = (values: CustomerChangePassword): Promise => {
+ const { id, version, currentPassword, newPassword } = values;
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .password()
+ .post({
+ body: {
+ id,
+ version,
+ currentPassword,
+ newPassword,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/customer/methods/getCustomer.ts b/src/services/sdk/customer/methods/getCustomer.ts
new file mode 100644
index 0000000..3b001ea
--- /dev/null
+++ b/src/services/sdk/customer/methods/getCustomer.ts
@@ -0,0 +1,7 @@
+import { refreshTokenRoot } from '../../auth/roots';
+import { projectKey } from '../../index';
+import { ClientResponse, Customer } from '@commercetools/platform-sdk';
+
+export const getCustomer = (): Promise> => {
+ return refreshTokenRoot().withProjectKey({ projectKey }).me().get().execute();
+};
diff --git a/src/services/sdk/customer/methods/index.ts b/src/services/sdk/customer/methods/index.ts
new file mode 100644
index 0000000..91d48e3
--- /dev/null
+++ b/src/services/sdk/customer/methods/index.ts
@@ -0,0 +1,7 @@
+export * from './addAddress';
+export * from './changeAddress';
+export * from './changeCustomerPassword';
+export * from './getCustomer';
+export * from './removeAddress';
+export * from './setDefaultAddressId';
+export * from './updateUserInfo';
diff --git a/src/services/sdk/customer/methods/removeAddress.ts b/src/services/sdk/customer/methods/removeAddress.ts
new file mode 100644
index 0000000..7ce6acb
--- /dev/null
+++ b/src/services/sdk/customer/methods/removeAddress.ts
@@ -0,0 +1,22 @@
+import { authRoot } from '../../auth/roots/index.ts';
+import { ClientResponse, Customer } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { IRemoveAddress } from '../types/index.ts';
+
+export const removeAddress = ({
+ customerId,
+ addressId,
+ version,
+}: IRemoveAddress): Promise> => {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .withId({ ID: customerId })
+ .post({
+ body: {
+ version,
+ actions: [{ action: 'removeAddress', addressId: addressId }],
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/customer/methods/setDefaultAddressId.ts b/src/services/sdk/customer/methods/setDefaultAddressId.ts
new file mode 100644
index 0000000..65558ec
--- /dev/null
+++ b/src/services/sdk/customer/methods/setDefaultAddressId.ts
@@ -0,0 +1,25 @@
+import { authRoot } from '../../auth/roots/index.ts';
+import { ClientResponse, Customer } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { ISetDefaultAddress } from '../types/index.ts';
+
+export const setDefaultAddressId = ({
+ version,
+ addressId,
+ isShipping,
+ customerId,
+}: ISetDefaultAddress): Promise> => {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .withId({ ID: customerId })
+ .post({
+ body: {
+ version,
+ actions: [
+ { action: isShipping ? 'setDefaultShippingAddress' : 'setDefaultBillingAddress', addressId: addressId },
+ ],
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/customer/methods/updateUserInfo.ts b/src/services/sdk/customer/methods/updateUserInfo.ts
new file mode 100644
index 0000000..c4f11dd
--- /dev/null
+++ b/src/services/sdk/customer/methods/updateUserInfo.ts
@@ -0,0 +1,20 @@
+import { authRoot } from '../../auth/roots/index.ts';
+import { ClientResponse, Customer } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { IUpdateUser } from '../types/index.ts';
+
+// FirstName, LastName, Email, DateOfBirth
+
+export const updateUserInfo = ({ id, version, action }: IUpdateUser): Promise> => {
+ return authRoot
+ .withProjectKey({ projectKey })
+ .customers()
+ .withId({ ID: id })
+ .post({
+ body: {
+ version,
+ actions: action,
+ },
+ })
+ .execute();
+};
diff --git a/src/services/sdk/customer/types/index.ts b/src/services/sdk/customer/types/index.ts
new file mode 100644
index 0000000..bd13968
--- /dev/null
+++ b/src/services/sdk/customer/types/index.ts
@@ -0,0 +1,22 @@
+import { CustomerUpdateAction } from '@commercetools/platform-sdk';
+
+export interface IUpdateUser {
+ id: string;
+ version: number;
+ action: CustomerUpdateAction[];
+}
+
+export interface IAddNewAddress extends IUpdateUser {
+ isDefault: boolean;
+ isShipping: boolean;
+}
+
+export interface IRemoveAddress {
+ customerId: string;
+ addressId: string;
+ version: number;
+}
+
+export interface ISetDefaultAddress extends IRemoveAddress {
+ isShipping: boolean;
+}
diff --git a/src/services/sdk/index.ts b/src/services/sdk/index.ts
new file mode 100644
index 0000000..34bb8d4
--- /dev/null
+++ b/src/services/sdk/index.ts
@@ -0,0 +1,3 @@
+import { VITE_CTP_PROJECT_KEY } from '../../constant';
+
+export const projectKey = VITE_CTP_PROJECT_KEY;
diff --git a/src/services/sdk/product/methods/fetchProductData.ts b/src/services/sdk/product/methods/fetchProductData.ts
new file mode 100644
index 0000000..88c6593
--- /dev/null
+++ b/src/services/sdk/product/methods/fetchProductData.ts
@@ -0,0 +1,9 @@
+import { ClientResponse, Product } from '@commercetools/platform-sdk';
+import { projectKey } from '../../index.ts';
+import { authRoot } from '../../auth/roots/index.ts';
+
+function fetchProductData(productId: string): Promise> {
+ return authRoot.withProjectKey({ projectKey }).products().withId({ ID: productId }).get().execute();
+}
+
+export default fetchProductData;
diff --git a/src/services/sdk/product/methods/index.ts b/src/services/sdk/product/methods/index.ts
new file mode 100644
index 0000000..0f7dc28
--- /dev/null
+++ b/src/services/sdk/product/methods/index.ts
@@ -0,0 +1,2 @@
+import fetchProductData from './fetchProductData';
+export { fetchProductData };
diff --git a/src/store/auth/selectors/index.ts b/src/store/auth/selectors/index.ts
new file mode 100644
index 0000000..28a2bcd
--- /dev/null
+++ b/src/store/auth/selectors/index.ts
@@ -0,0 +1,17 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { RootState } from '../../store';
+import { IAuthSlice } from '../types';
+import { TStatus } from '../../../types/types';
+
+export const selectIsAuthorized = (state: RootState): boolean => state.auth.isAuthorized;
+export const selectAuthLoadingStatus = (state: RootState): TStatus => state.auth.status;
+export const selectAuthError = (state: RootState): string => state.auth.error;
+export const selectIsNewUser = (status: RootState): boolean => status.auth.isNewUser;
+export const selectAuthLoadingInfo = createSelector(
+ selectAuthLoadingStatus,
+ selectAuthError,
+ (status, error): Pick => ({
+ status,
+ error,
+ }),
+);
diff --git a/src/store/auth/slice/index.ts b/src/store/auth/slice/index.ts
new file mode 100644
index 0000000..9d2d030
--- /dev/null
+++ b/src/store/auth/slice/index.ts
@@ -0,0 +1,72 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { loginThunk, registerThunk, logoutThunk, anonLoginThunk } from '../thunks';
+import { initialAuthState } from '../../../constant';
+
+const authSlice = createSlice({
+ name: 'auth',
+ initialState: initialAuthState,
+ reducers: {
+ deleteNotice: (state): void => {
+ state.isNewUser = false;
+ },
+ resetError: (state): void => {
+ state.status = 'initial';
+ state.error = '';
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(loginThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(loginThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.isAuthorized = true;
+ state.userId = payload;
+ })
+ .addCase(loginThunk.rejected, (state, { payload }) => {
+ state.isAuthorized = false;
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ })
+ .addCase(registerThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(registerThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.isAuthorized = true;
+ state.isNewUser = true;
+ state.userId = payload;
+ })
+ .addCase(registerThunk.rejected, (state, { payload }) => {
+ state.isAuthorized = false;
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ })
+ .addCase(logoutThunk.pending, (state) => {
+ state.isAuthorized = false;
+ state.userId = '';
+ state.status = 'initial';
+ state.error = '';
+ })
+ .addCase(anonLoginThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(anonLoginThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.isAuthorized = true;
+ state.userId = payload;
+ })
+ .addCase(anonLoginThunk.rejected, (state, { payload }) => {
+ state.isAuthorized = false;
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ });
+ },
+});
+
+export const authReducer = authSlice.reducer;
+export const { deleteNotice, resetError } = authSlice.actions;
diff --git a/src/store/auth/thunks/anonLoginThunk.ts b/src/store/auth/thunks/anonLoginThunk.ts
new file mode 100644
index 0000000..c308873
--- /dev/null
+++ b/src/store/auth/thunks/anonLoginThunk.ts
@@ -0,0 +1,43 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { anonLoginCustomer, loginCustomer } from '../../../services/sdk/auth/methods';
+import { checkError } from '../../../utils';
+import { IAuthSlice } from '../types';
+import { initialTokenInfo, tokenData } from '../../../services/sdk/auth/token';
+
+export const anonLoginThunk = createAsyncThunk<
+ string,
+ UserAuthOptions,
+ {
+ state: { auth: IAuthSlice };
+ rejectValue: string;
+ }
+>(
+ 'auth/anonLoginThunk',
+ async (body, { rejectWithValue }) => {
+ try {
+ await anonLoginCustomer(body);
+ tokenData.set(initialTokenInfo);
+ localStorage.removeItem('art-anon-token');
+
+ const user = await loginCustomer(body);
+ const token = tokenData.get().refreshToken;
+ if (token) {
+ localStorage.setItem('art-token', token);
+ }
+
+ return user.body.customer.id;
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ auth: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/auth/thunks/index.ts b/src/store/auth/thunks/index.ts
new file mode 100644
index 0000000..83d3adf
--- /dev/null
+++ b/src/store/auth/thunks/index.ts
@@ -0,0 +1,4 @@
+export * from './loginThunk';
+export * from './registerThunk';
+export * from './logoutThunk';
+export * from './anonLoginThunk';
diff --git a/src/store/auth/thunks/loginThunk.ts b/src/store/auth/thunks/loginThunk.ts
new file mode 100644
index 0000000..1d4f410
--- /dev/null
+++ b/src/store/auth/thunks/loginThunk.ts
@@ -0,0 +1,40 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { loginCustomer } from '../../../services/sdk/auth/methods';
+import { checkError } from '../../../utils';
+import { IAuthSlice } from '../types';
+import { initialTokenInfo, tokenData } from '../../../services/sdk/auth/token';
+
+export const loginThunk = createAsyncThunk<
+ string,
+ UserAuthOptions,
+ {
+ state: { auth: IAuthSlice };
+ rejectValue: string;
+ }
+>(
+ 'auth/loginThunk',
+ async (body, { rejectWithValue }) => {
+ try {
+ tokenData.set(initialTokenInfo);
+ const user = await loginCustomer(body);
+ const token = tokenData.get().refreshToken;
+ if (token) {
+ localStorage.setItem('art-token', token);
+ }
+
+ return user.body.customer.id;
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ auth: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/auth/thunks/logoutThunk.ts b/src/store/auth/thunks/logoutThunk.ts
new file mode 100644
index 0000000..358e8ab
--- /dev/null
+++ b/src/store/auth/thunks/logoutThunk.ts
@@ -0,0 +1,7 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { initialTokenInfo, tokenData } from '../../../services/sdk/auth/token';
+
+export const logoutThunk = createAsyncThunk('auth/logoutThunk', (): void => {
+ localStorage.removeItem('art-token');
+ tokenData.set(initialTokenInfo);
+});
diff --git a/src/store/auth/thunks/registerThunk.ts b/src/store/auth/thunks/registerThunk.ts
new file mode 100644
index 0000000..a9fa9d6
--- /dev/null
+++ b/src/store/auth/thunks/registerThunk.ts
@@ -0,0 +1,42 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { loginCustomer, registerCustomer } from '../../../services/sdk/auth/methods';
+import { checkError } from '../../../utils';
+import { INewUser } from '../../../types/interfaces';
+import { IAuthSlice } from '../types';
+import { initialTokenInfo, tokenData } from '../../../services/sdk/auth/token';
+
+export const registerThunk = createAsyncThunk<
+ string,
+ INewUser,
+ {
+ state: { auth: IAuthSlice };
+ rejectValue: string;
+ }
+>(
+ 'auth/signupThunk',
+ async (body, { rejectWithValue }) => {
+ try {
+ tokenData.set(initialTokenInfo);
+ await registerCustomer(body);
+ tokenData.set(initialTokenInfo);
+ const user = await loginCustomer({ username: body.email, password: body.password });
+ const token = tokenData.get().refreshToken;
+ if (token) {
+ localStorage.setItem('art-token', token);
+ }
+
+ return user.body.customer.id;
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ auth: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/auth/types/index.ts b/src/store/auth/types/index.ts
new file mode 100644
index 0000000..925a280
--- /dev/null
+++ b/src/store/auth/types/index.ts
@@ -0,0 +1,9 @@
+import { TStatus } from '../../../types/types';
+
+export interface IAuthSlice {
+ isAuthorized: boolean;
+ isNewUser: boolean;
+ status: TStatus;
+ error: string;
+ userId: string;
+}
diff --git a/src/store/cart/selectors/index.ts b/src/store/cart/selectors/index.ts
new file mode 100644
index 0000000..e1078a8
--- /dev/null
+++ b/src/store/cart/selectors/index.ts
@@ -0,0 +1,25 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { RootState } from '../../store';
+import { TStatus } from '../../../types/types';
+import { ICart, ICartSlice, IDiscount } from '../types';
+
+export const selectCart = (state: RootState): ICart => state.cart.basket;
+export const selectCartLoadingStatus = (state: RootState): TStatus => state.cart.status;
+export const selectCartError = (state: RootState): string => state.cart.error;
+export const selectDiscountsCodes = (state: RootState): IDiscount[] => state.cart.discounts;
+export const selectCartLoadingInfo = createSelector(
+ selectCartLoadingStatus,
+ selectCartError,
+ (status, error): Pick => ({
+ status,
+ error,
+ }),
+);
+export const selectCartData = createSelector(
+ selectCart,
+ selectDiscountsCodes,
+ (basket, discounts): Pick => ({
+ basket,
+ discounts,
+ }),
+);
diff --git a/src/store/cart/slice/index.ts b/src/store/cart/slice/index.ts
new file mode 100644
index 0000000..db12099
--- /dev/null
+++ b/src/store/cart/slice/index.ts
@@ -0,0 +1,71 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { deleteCartThunk, getCartThunk, getDiscountsThunk, updateCartThunk } from '../thunks';
+import { initialBasket, initialCartSlice } from '../../../constant';
+
+const cartSlice = createSlice({
+ name: 'cart',
+ initialState: initialCartSlice,
+ reducers: {
+ resetCart: (state) => {
+ state.basket = initialBasket;
+ },
+ resetCartError: (state) => {
+ state.status = 'initial';
+ state.error = '';
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getCartThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(getCartThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.basket = payload;
+ })
+ .addCase(getCartThunk.rejected, (state, { payload }) => {
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ })
+ .addCase(updateCartThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(updateCartThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.basket = payload;
+ })
+ .addCase(updateCartThunk.rejected, (state, { payload }) => {
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ })
+ .addCase(deleteCartThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(deleteCartThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.basket = payload;
+ })
+ .addCase(deleteCartThunk.rejected, (state, { payload }) => {
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ })
+ .addCase(getDiscountsThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(getDiscountsThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.discounts = payload;
+ })
+ .addCase(getDiscountsThunk.rejected, (state, { payload }) => {
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ });
+ },
+});
+
+export const cartReducer = cartSlice.reducer;
+export const { resetCart, resetCartError } = cartSlice.actions;
diff --git a/src/store/cart/thunks/deleteCartThunk.ts b/src/store/cart/thunks/deleteCartThunk.ts
new file mode 100644
index 0000000..0029ac5
--- /dev/null
+++ b/src/store/cart/thunks/deleteCartThunk.ts
@@ -0,0 +1,33 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { ICart, ICartSlice } from '../types';
+import { checkError } from '../../../utils';
+import { deleteCartById, IDeleteCart } from '../../../services/sdk/cart/methods';
+import { initialBasket } from '../../../constant';
+
+export const deleteCartThunk = createAsyncThunk<
+ ICart,
+ IDeleteCart,
+ {
+ state: { cart: ICartSlice };
+ rejectValue: string;
+ }
+>(
+ 'cart/deleteCartThunk',
+ async (body, { rejectWithValue }) => {
+ try {
+ await deleteCartById(body);
+ return initialBasket;
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ cart: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/cart/thunks/getCartThunk.ts b/src/store/cart/thunks/getCartThunk.ts
new file mode 100644
index 0000000..c721881
--- /dev/null
+++ b/src/store/cart/thunks/getCartThunk.ts
@@ -0,0 +1,42 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { ICart } from '../types';
+import { checkError } from '../../../utils';
+import { getAnonCart, getCart } from '../../../services/sdk/cart/methods';
+import { extractLocalCart } from '../../../utils/extractLocalCart.ts';
+import { initialBasket } from '../../../constant';
+import { tokenData } from '../../../services/sdk/auth/token';
+import { CartPagedQueryResponse, ClientResponse } from '@commercetools/platform-sdk';
+
+export const getCartThunk = createAsyncThunk<
+ ICart,
+ void,
+ {
+ rejectValue: string;
+ }
+>('cart/getCartThunk', async (_, { rejectWithValue }) => {
+ try {
+ const isExistToken = !!localStorage.getItem('art-anon-token') || !!localStorage.getItem('art-token');
+
+ let response: ClientResponse;
+
+ if (isExistToken) {
+ response = await getCart();
+ } else {
+ response = await getAnonCart();
+ const token = tokenData.get().refreshToken;
+ if (token) {
+ localStorage.setItem('art-anon-token', token);
+ }
+ }
+
+ const cart = response.body.results.find((item) => item.cartState === 'Active');
+
+ if (cart) {
+ return extractLocalCart(cart);
+ }
+
+ return initialBasket;
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+});
diff --git a/src/store/cart/thunks/getDiscountsThunk.ts b/src/store/cart/thunks/getDiscountsThunk.ts
new file mode 100644
index 0000000..8dd613f
--- /dev/null
+++ b/src/store/cart/thunks/getDiscountsThunk.ts
@@ -0,0 +1,27 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { IDiscount } from '../types';
+import { checkError } from '../../../utils';
+import { getDiscountCodes } from '../../../services/sdk/cart/methods';
+
+export const getDiscountsThunk = createAsyncThunk<
+ IDiscount[],
+ void,
+ {
+ rejectValue: string;
+ }
+>('cart/getDiscountsThunk', async (_, { rejectWithValue }) => {
+ try {
+ const response = await getDiscountCodes();
+ const codes: IDiscount[] = [];
+ response.body.results.forEach((item) => {
+ codes.push({
+ id: item.id,
+ code: item.code,
+ });
+ });
+
+ return codes;
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+});
diff --git a/src/store/cart/thunks/index.ts b/src/store/cart/thunks/index.ts
new file mode 100644
index 0000000..53bf3fc
--- /dev/null
+++ b/src/store/cart/thunks/index.ts
@@ -0,0 +1,4 @@
+export * from './deleteCartThunk';
+export * from './getCartThunk';
+export * from './updateCartThunk';
+export * from './getDiscountsThunk';
diff --git a/src/store/cart/thunks/updateCartThunk.ts b/src/store/cart/thunks/updateCartThunk.ts
new file mode 100644
index 0000000..59eb89d
--- /dev/null
+++ b/src/store/cart/thunks/updateCartThunk.ts
@@ -0,0 +1,43 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { ICart, ICartSlice } from '../types';
+import { checkError } from '../../../utils';
+import { createCart, IUpdateCart, updateCart } from '../../../services/sdk/cart/methods';
+import { extractLocalCart } from '../../../utils/extractLocalCart.ts';
+
+export const updateCartThunk = createAsyncThunk<
+ ICart,
+ IUpdateCart,
+ {
+ state: { cart: ICartSlice };
+ rejectValue: string;
+ }
+>(
+ 'cart/updateCartThunk',
+ async (body, { rejectWithValue }) => {
+ try {
+ if (!body.id) {
+ const cart = await createCart();
+ const res = await updateCart({
+ id: cart.body.id,
+ version: cart.body.version,
+ actions: body.actions,
+ });
+ return extractLocalCart(res.body);
+ } else {
+ const res = await updateCart(body);
+ return extractLocalCart(res.body);
+ }
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ cart: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/cart/types/index.ts b/src/store/cart/types/index.ts
new file mode 100644
index 0000000..c5b157a
--- /dev/null
+++ b/src/store/cart/types/index.ts
@@ -0,0 +1,39 @@
+import { TStatus } from '../../../types/types';
+
+export interface ICartSlice {
+ status: TStatus;
+ error: string;
+ basket: ICart;
+ discounts: IDiscount[];
+}
+
+export interface IPromoCode {
+ type: string;
+ id: string;
+}
+
+export interface IDiscount {
+ id: string;
+ code: string;
+}
+
+export interface ICart {
+ id: string;
+ version: number;
+ lineItems: IItemCart[];
+ totalPrice: number;
+ totalQuantity: number;
+ codes: IPromoCode[];
+}
+
+export interface IItemCart {
+ itemId: string;
+ productId: string;
+ name: string;
+ artist: string;
+ image: string;
+ quantity: number;
+ price: number;
+ discountedPrice: number;
+ totalPrice: number;
+}
diff --git a/src/store/catalog/selectors/index.ts b/src/store/catalog/selectors/index.ts
new file mode 100644
index 0000000..b5dcedf
--- /dev/null
+++ b/src/store/catalog/selectors/index.ts
@@ -0,0 +1,29 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { RootState } from '../../store';
+import { IProduct } from '../../../types/interfaces';
+import { TStatus } from '../../../types/types';
+import { ICategoryFilterItem } from '../../../components/Filters/types';
+import { ICatalogSlice } from '../types';
+
+export const selectCategories = (state: RootState): ICategoryFilterItem[] => state.catalog.categories;
+export const selectCatalogLoadingStatus = (state: RootState): TStatus => state.catalog.status;
+export const selectCatalogError = (state: RootState): string => state.catalog.error;
+export const selectProductlist = (status: RootState): IProduct[] => status.catalog.productList;
+export const selectTotalCount = (status: RootState): number => status.catalog.totalProducts;
+export const selectCatalogLoadingInfo = createSelector(
+ selectCatalogLoadingStatus,
+ selectCatalogError,
+ (status, error): Pick => ({
+ status,
+ error,
+ }),
+);
+
+export const selectProductsInfo = createSelector(
+ selectProductlist,
+ selectTotalCount,
+ (productList, totalProducts): Pick => ({
+ productList,
+ totalProducts,
+ }),
+);
diff --git a/src/store/catalog/slice/index.ts b/src/store/catalog/slice/index.ts
new file mode 100644
index 0000000..9d27430
--- /dev/null
+++ b/src/store/catalog/slice/index.ts
@@ -0,0 +1,45 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { categoriesThunk, productListThunk } from '../thunks';
+import { initialProductListSlice } from '../../../constant';
+
+const catalogSlice = createSlice({
+ name: 'catalog',
+ initialState: initialProductListSlice,
+ reducers: {
+ resetProductList: (state) => {
+ state.productList = [];
+ state.totalProducts = 0;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(productListThunk.pending, (state) => {
+ state.error = '';
+ state.status = 'loading';
+ })
+ .addCase(productListThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.productList = payload.list;
+ state.totalProducts = payload.count;
+ })
+ .addCase(productListThunk.rejected, (state, action) => {
+ state.status = 'error';
+ state.error = action.payload || 'Something went wrong';
+ })
+ .addCase(categoriesThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = 'success';
+ })
+ .addCase(categoriesThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.categories = payload;
+ })
+ .addCase(categoriesThunk.rejected, (state, { payload }) => {
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ });
+ },
+});
+
+export const { resetProductList } = catalogSlice.actions;
+export const catalogReducer = catalogSlice.reducer;
diff --git a/src/store/catalog/thunks/categoriesThunk.ts b/src/store/catalog/thunks/categoriesThunk.ts
new file mode 100644
index 0000000..42caeb2
--- /dev/null
+++ b/src/store/catalog/thunks/categoriesThunk.ts
@@ -0,0 +1,19 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, getCategoryList } from '../../../utils';
+import { getCategories } from '../../../services/sdk/catalog/methods';
+import { ICategoryFilterItem } from '../../../components/Filters/types';
+
+export const categoriesThunk = createAsyncThunk<
+ ICategoryFilterItem[],
+ void,
+ {
+ rejectValue: string;
+ }
+>('catalog/categoriesThunk', async (_, thunkAPI) => {
+ try {
+ const response = await getCategories();
+ return getCategoryList(response.body.results);
+ } catch (error: unknown) {
+ return thunkAPI.rejectWithValue(checkError(error));
+ }
+});
diff --git a/src/store/catalog/thunks/index.ts b/src/store/catalog/thunks/index.ts
new file mode 100644
index 0000000..4654bc0
--- /dev/null
+++ b/src/store/catalog/thunks/index.ts
@@ -0,0 +1,2 @@
+export * from './productListThunk';
+export * from './categoriesThunk';
diff --git a/src/store/catalog/thunks/productListThunk.ts b/src/store/catalog/thunks/productListThunk.ts
new file mode 100644
index 0000000..40b4c62
--- /dev/null
+++ b/src/store/catalog/thunks/productListThunk.ts
@@ -0,0 +1,33 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { QueryParam } from '@commercetools/sdk-client-v2';
+import { IProduct } from '../../../types/interfaces.ts';
+import { checkError } from '../../../utils';
+import { parseProductListData } from '../../../utils';
+import { getProductList } from '../../../services/sdk/catalog/methods';
+
+interface IProductsListData {
+ list: IProduct[];
+ count: number;
+}
+
+interface IProductListThunk {
+ params: {
+ [p: string]: QueryParam;
+ };
+ list: IProduct[];
+}
+
+export const productListThunk = createAsyncThunk(
+ 'catalog/productListThunk',
+ async ({ params, list }, { rejectWithValue }) => {
+ try {
+ const res = await getProductList(params);
+ return {
+ list: [...list, ...parseProductListData(res.body.results)],
+ count: res.body.total || 0,
+ };
+ } catch (err: unknown) {
+ return rejectWithValue(checkError(err));
+ }
+ },
+);
diff --git a/src/store/catalog/types/index.ts b/src/store/catalog/types/index.ts
new file mode 100644
index 0000000..9b82403
--- /dev/null
+++ b/src/store/catalog/types/index.ts
@@ -0,0 +1,11 @@
+import { IProduct } from '../../../types/interfaces';
+import { TStatus } from '../../../types/types';
+import { ICategoryFilterItem } from '../../../components/Filters/types';
+
+export interface ICatalogSlice {
+ status: TStatus;
+ error: string;
+ productList: IProduct[];
+ categories: ICategoryFilterItem[];
+ totalProducts: number;
+}
diff --git a/src/store/product/slice/index.ts b/src/store/product/slice/index.ts
new file mode 100644
index 0000000..57d4749
--- /dev/null
+++ b/src/store/product/slice/index.ts
@@ -0,0 +1,26 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { initialProductSlice } from '../../../constant';
+import { productThunk } from '../thunks';
+
+export const productSlice = createSlice({
+ name: 'product',
+ initialState: initialProductSlice,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder
+ .addCase(productThunk.pending, (state) => {
+ state.error = '';
+ state.status = 'loading';
+ })
+ .addCase(productThunk.fulfilled, (state, action) => {
+ state.status = 'success';
+ state.product = action.payload;
+ })
+ .addCase(productThunk.rejected, (state, action) => {
+ state.status = 'error';
+ state.error = action.payload || 'Something went wrong';
+ });
+ },
+});
+
+export const productReducer = productSlice.reducer;
diff --git a/src/store/product/thunks/index.ts b/src/store/product/thunks/index.ts
new file mode 100644
index 0000000..db10b33
--- /dev/null
+++ b/src/store/product/thunks/index.ts
@@ -0,0 +1,33 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { IProduct } from '../../../types/interfaces';
+import { IProductSlice } from '../types';
+import { checkError } from '../../../utils';
+
+import { fetchProductData } from '../../../services/sdk/product/methods';
+import { parseProductData } from '../../../utils';
+
+export const productThunk = createAsyncThunk<
+ IProduct,
+ string,
+ {
+ state: { product: IProductSlice };
+ rejectValue: string;
+ }
+>(
+ 'product/productThunk',
+ async (id, { rejectWithValue }) => {
+ try {
+ const res = await fetchProductData(id);
+ return parseProductData(res.body);
+ } catch (err: unknown) {
+ return rejectWithValue(checkError(err));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const status = getState().product.status;
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/product/types/index.ts b/src/store/product/types/index.ts
new file mode 100644
index 0000000..d3254ce
--- /dev/null
+++ b/src/store/product/types/index.ts
@@ -0,0 +1,8 @@
+import { IProduct } from '../../../types/interfaces';
+import { TStatus } from '../../../types/types';
+
+export interface IProductSlice {
+ status: TStatus;
+ error: string;
+ product: IProduct;
+}
diff --git a/src/store/store.ts b/src/store/store.ts
new file mode 100644
index 0000000..825d7eb
--- /dev/null
+++ b/src/store/store.ts
@@ -0,0 +1,22 @@
+import { configureStore } from '@reduxjs/toolkit';
+import { useDispatch } from 'react-redux';
+import { authReducer } from './auth/slice';
+import { productReducer } from './product/slice';
+import { catalogReducer } from './catalog/slice';
+import { userReducer } from './user/slice';
+import { cartReducer } from './cart/slice';
+
+export const store = configureStore({
+ reducer: {
+ auth: authReducer,
+ product: productReducer,
+ catalog: catalogReducer,
+ user: userReducer,
+ cart: cartReducer,
+ },
+});
+
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
+
+export const useAppDispatch: () => AppDispatch = useDispatch;
diff --git a/src/store/user/selectors/index.ts b/src/store/user/selectors/index.ts
new file mode 100644
index 0000000..a789319
--- /dev/null
+++ b/src/store/user/selectors/index.ts
@@ -0,0 +1,30 @@
+import { RootState } from '../../store';
+import { TStatus } from '../../../types/types';
+import { createSelector } from '@reduxjs/toolkit';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+
+export const selectUserLoadingStatus = (state: RootState): TStatus => state.user.status;
+export const selectUserError = (state: RootState): string => state.user.error;
+export const selectIsSuccess = (state: RootState): boolean => state.user.isSuccess;
+export const selectEditError = (state: RootState): string => state.user.editError;
+export const selectEditStatus = (state: RootState): TStatus => state.user.editStatus;
+export const selectUserLoadingInfo = createSelector(
+ selectUserLoadingStatus,
+ selectUserError,
+ (status, error): Pick => ({
+ status,
+ error,
+ }),
+);
+export const selectEditUserInfo = createSelector(
+ selectEditStatus,
+ selectEditError,
+ selectIsSuccess,
+ (editStatus, editError, isSuccess): Pick => ({
+ editStatus,
+ editError,
+ isSuccess,
+ }),
+);
+export const selectUserData = (state: RootState): IUser => state.user.user;
diff --git a/src/store/user/slice/index.ts b/src/store/user/slice/index.ts
new file mode 100644
index 0000000..ae1995e
--- /dev/null
+++ b/src/store/user/slice/index.ts
@@ -0,0 +1,122 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { initialUserState } from '../../../constant';
+import { getUserThunk, setDefaultAddressIdThunk, updPasswordThunk, updUserThunk } from '../thunks';
+import { removeAddressThunk } from '../thunks';
+import { addNewAddressThunk } from '../thunks';
+import { changeAddressThunk } from '../thunks';
+
+const userSlice = createSlice({
+ name: 'user',
+ initialState: initialUserState,
+ reducers: {
+ resetEditError: (state): void => {
+ state.editStatus = 'initial';
+ state.editError = '';
+ },
+ deleteSuccessState: (state): void => {
+ state.isSuccess = false;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getUserThunk.pending, (state) => {
+ state.status = 'loading';
+ state.error = '';
+ })
+ .addCase(getUserThunk.fulfilled, (state, { payload }) => {
+ state.status = 'success';
+ state.user = payload;
+ })
+ .addCase(getUserThunk.rejected, (state, { payload }) => {
+ state.status = 'error';
+ state.error = payload || 'Something was wrong';
+ })
+ .addCase(updUserThunk.pending, (state) => {
+ state.editStatus = 'loading';
+ state.editError = '';
+ state.isSuccess = false;
+ })
+ .addCase(updUserThunk.fulfilled, (state, { payload }) => {
+ state.editStatus = 'success';
+ state.user = payload;
+ state.isSuccess = true;
+ })
+ .addCase(updUserThunk.rejected, (state, { payload }) => {
+ state.editStatus = 'error';
+ state.editError = payload || 'Something was wrong';
+ })
+ .addCase(updPasswordThunk.pending, (state) => {
+ state.editStatus = 'loading';
+ state.editError = '';
+ state.isSuccess = false;
+ })
+ .addCase(updPasswordThunk.fulfilled, (state, { payload }) => {
+ state.editStatus = 'success';
+ state.user = payload;
+ state.isSuccess = true;
+ })
+ .addCase(updPasswordThunk.rejected, (state, { payload }) => {
+ state.editStatus = 'error';
+ state.editError = payload || 'Something was wrong';
+ })
+ .addCase(removeAddressThunk.pending, (state) => {
+ state.editStatus = 'loading';
+ state.editError = '';
+ state.isSuccess = false;
+ })
+ .addCase(removeAddressThunk.fulfilled, (state, { payload }) => {
+ state.editStatus = 'success';
+ state.user = payload;
+ state.isSuccess = true;
+ })
+ .addCase(removeAddressThunk.rejected, (state, { payload }) => {
+ state.editStatus = 'error';
+ state.editError = payload || 'Something was wrong';
+ })
+ .addCase(addNewAddressThunk.pending, (state) => {
+ state.editStatus = 'loading';
+ state.editError = '';
+ state.isSuccess = false;
+ })
+ .addCase(addNewAddressThunk.fulfilled, (state, { payload }) => {
+ state.editStatus = 'success';
+ state.user = payload;
+ state.isSuccess = true;
+ })
+ .addCase(addNewAddressThunk.rejected, (state, { payload }) => {
+ state.editStatus = 'error';
+ state.editError = payload || 'Something was wrong';
+ })
+ .addCase(changeAddressThunk.pending, (state) => {
+ state.editStatus = 'loading';
+ state.editError = '';
+ state.isSuccess = false;
+ })
+ .addCase(changeAddressThunk.fulfilled, (state, { payload }) => {
+ state.editStatus = 'success';
+ state.user = payload;
+ state.isSuccess = true;
+ })
+ .addCase(changeAddressThunk.rejected, (state, { payload }) => {
+ state.editStatus = 'error';
+ state.editError = payload || 'Something was wrong';
+ })
+ .addCase(setDefaultAddressIdThunk.pending, (state) => {
+ state.editStatus = 'loading';
+ state.editError = '';
+ state.isSuccess = false;
+ })
+ .addCase(setDefaultAddressIdThunk.fulfilled, (state, { payload }) => {
+ state.editStatus = 'success';
+ state.user = payload;
+ state.isSuccess = true;
+ })
+ .addCase(setDefaultAddressIdThunk.rejected, (state, { payload }) => {
+ state.editStatus = 'error';
+ state.editError = payload || 'Something was wrong';
+ });
+ },
+});
+
+export const userReducer = userSlice.reducer;
+export const { resetEditError, deleteSuccessState } = userSlice.actions;
diff --git a/src/store/user/thunks/addNewAddressThunk.ts b/src/store/user/thunks/addNewAddressThunk.ts
new file mode 100644
index 0000000..0428ebb
--- /dev/null
+++ b/src/store/user/thunks/addNewAddressThunk.ts
@@ -0,0 +1,45 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+import { addAddress, setDefaultAddressId } from '../../../services/sdk/customer/methods';
+import { IAddNewAddress, ISetDefaultAddress } from '../../../services/sdk/customer/types';
+
+export const addNewAddressThunk = createAsyncThunk<
+ IUser,
+ IAddNewAddress,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'user/addNewAddressThunk',
+ async (objects, { rejectWithValue }) => {
+ try {
+ const results = await addAddress(objects);
+ if (objects.isDefault) {
+ const setDefaultData: ISetDefaultAddress = {
+ version: results.body.version,
+ addressId: results.body.addresses[results.body.addresses.length - 1].id || '',
+ isShipping: objects.isShipping,
+ customerId: objects.id,
+ };
+ const address = await setDefaultAddressId(setDefaultData);
+ return extractLocalUser(address.body);
+ }
+
+ return extractLocalUser(results.body);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/thunks/changeAddressThunk.ts b/src/store/user/thunks/changeAddressThunk.ts
new file mode 100644
index 0000000..428cec6
--- /dev/null
+++ b/src/store/user/thunks/changeAddressThunk.ts
@@ -0,0 +1,34 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+import { IUpdateUser } from '../../../services/sdk/customer/types';
+import { changeAddress } from '../../../services/sdk/customer/methods';
+
+export const changeAddressThunk = createAsyncThunk<
+ IUser,
+ IUpdateUser,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'user/changeAddressThunk',
+ async (objects, { rejectWithValue }) => {
+ try {
+ const results = await changeAddress(objects);
+ return extractLocalUser(results.body);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/thunks/getUserThunk.ts b/src/store/user/thunks/getUserThunk.ts
new file mode 100644
index 0000000..09dfa5e
--- /dev/null
+++ b/src/store/user/thunks/getUserThunk.ts
@@ -0,0 +1,33 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+import { getCustomer } from '../../../services/sdk/customer/methods';
+
+export const getUserThunk = createAsyncThunk<
+ IUser,
+ void,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'auth/getUserThunk',
+ async (_, { rejectWithValue }) => {
+ try {
+ const user = await getCustomer();
+ return extractLocalUser(user.body);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/thunks/index.ts b/src/store/user/thunks/index.ts
new file mode 100644
index 0000000..0e2cd71
--- /dev/null
+++ b/src/store/user/thunks/index.ts
@@ -0,0 +1,7 @@
+export * from './addNewAddressThunk';
+export * from './changeAddressThunk';
+export * from './getUserThunk';
+export * from './removeAddressThunk';
+export * from './setDefaultAddressIdThunk';
+export * from './updPasswordThunk';
+export * from './updUserThunk';
diff --git a/src/store/user/thunks/removeAddressThunk.ts b/src/store/user/thunks/removeAddressThunk.ts
new file mode 100644
index 0000000..dde9bc9
--- /dev/null
+++ b/src/store/user/thunks/removeAddressThunk.ts
@@ -0,0 +1,34 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+import { IRemoveAddress } from '../../../services/sdk/customer/types';
+import { removeAddress } from '../../../services/sdk/customer/methods';
+
+export const removeAddressThunk = createAsyncThunk<
+ IUser,
+ IRemoveAddress,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'user/removeAddressThunk',
+ async (removeData, { rejectWithValue }) => {
+ try {
+ const user = await removeAddress(removeData);
+ return extractLocalUser(user.body);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/thunks/setDefaultAddressIdThunk.ts b/src/store/user/thunks/setDefaultAddressIdThunk.ts
new file mode 100644
index 0000000..bfe8355
--- /dev/null
+++ b/src/store/user/thunks/setDefaultAddressIdThunk.ts
@@ -0,0 +1,35 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+import { ISetDefaultAddress } from '../../../services/sdk/customer/types';
+import { setDefaultAddressId } from '../../../services/sdk/customer/methods';
+
+export const setDefaultAddressIdThunk = createAsyncThunk<
+ IUser,
+ ISetDefaultAddress,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'user/setDefaultAddressIdThunk',
+ async (defaultAddressData, { rejectWithValue }) => {
+ try {
+ const user = await setDefaultAddressId(defaultAddressData);
+
+ return extractLocalUser(user.body);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/thunks/updPasswordThunk.ts b/src/store/user/thunks/updPasswordThunk.ts
new file mode 100644
index 0000000..888e7fc
--- /dev/null
+++ b/src/store/user/thunks/updPasswordThunk.ts
@@ -0,0 +1,46 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUpdPasswordData, IUserSlice } from '../types';
+import { changeCustomerPassword } from '../../../services/sdk/customer/methods';
+import { loginCustomer } from '../../../services/sdk/auth/methods';
+import { UserAuthOptions } from '@commercetools/sdk-client-v2';
+import { initialTokenInfo, tokenData } from '../../../services/sdk/auth/token';
+
+export const updPasswordThunk = createAsyncThunk<
+ IUser,
+ IUpdPasswordData,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'user/updPasswordThunk',
+ async (passwordsData, { rejectWithValue }) => {
+ try {
+ await changeCustomerPassword(passwordsData.passwordData);
+ const loginData: UserAuthOptions = {
+ username: passwordsData.email,
+ password: passwordsData.passwordData.newPassword,
+ };
+ tokenData.set(initialTokenInfo);
+ const user = await loginCustomer(loginData);
+ const token = tokenData.get().refreshToken;
+ if (token) {
+ localStorage.setItem('art-token', token);
+ }
+ return extractLocalUser(user.body.customer);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/thunks/updUserThunk.ts b/src/store/user/thunks/updUserThunk.ts
new file mode 100644
index 0000000..d4f95ff
--- /dev/null
+++ b/src/store/user/thunks/updUserThunk.ts
@@ -0,0 +1,35 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { checkError, extractLocalUser } from '../../../utils';
+import { IUser } from '../../../types/interfaces';
+import { IUserSlice } from '../types';
+import { updateUserInfo } from '../../../services/sdk/customer/methods';
+import { IUpdateUser } from '../../../services/sdk/customer/types';
+
+export const updUserThunk = createAsyncThunk<
+ IUser,
+ IUpdateUser,
+ {
+ state: { user: IUserSlice };
+ rejectValue: string;
+ }
+>(
+ 'user/updUserThunk',
+ async (objects, { rejectWithValue }) => {
+ try {
+ const results = await updateUserInfo(objects);
+
+ return extractLocalUser(results.body);
+ } catch (error: unknown) {
+ return rejectWithValue(checkError(error));
+ }
+ },
+ {
+ condition: (_, { getState }): boolean => {
+ const {
+ user: { status },
+ } = getState();
+
+ return !(status === 'loading');
+ },
+ },
+);
diff --git a/src/store/user/types/index.ts b/src/store/user/types/index.ts
new file mode 100644
index 0000000..0c385f7
--- /dev/null
+++ b/src/store/user/types/index.ts
@@ -0,0 +1,17 @@
+import { TStatus } from '../../../types/types';
+import { IUser } from '../../../types/interfaces';
+import { CustomerChangePassword } from '@commercetools/platform-sdk';
+
+export interface IUserSlice {
+ status: TStatus;
+ error: string;
+ user: IUser;
+ isSuccess: boolean;
+ editStatus: TStatus;
+ editError: string;
+}
+
+export interface IUpdPasswordData {
+ email: string;
+ passwordData: CustomerChangePassword;
+}
diff --git a/src/styles/common/_common.scss b/src/styles/common/_common.scss
new file mode 100644
index 0000000..6e36a15
--- /dev/null
+++ b/src/styles/common/_common.scss
@@ -0,0 +1,136 @@
+@import './variables';
+@import './mixin';
+
+*,
+*::before,
+*::after {
+ box-sizing: inherit;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+html {
+ box-sizing: border-box;
+ scroll-behavior: smooth;
+ font-size: 10px;
+}
+
+body {
+ // min-width: 375px;
+ font-family: 'Inter', Arial, Helvetica, sans-serif;
+ @include font(regular, p1, $dark);
+ background-color: $light;
+ overflow-x: hidden;
+}
+
+body.modal-open {
+ overflow: hidden;
+}
+
+@media all and (max-width: 480px) {
+
+ body,
+ input,
+ select,
+ button,
+ a {
+ @include font(regular, p1-mob, $dark);
+ }
+}
+
+#root {
+ width: 100%;
+ min-height: 100vh;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+main {
+ flex-grow: 1;
+ padding: 0;
+}
+
+:active,
+:hover,
+:focus,
+:focus-visible {
+ outline: none;
+ outline-offset: 0;
+}
+
+button {
+ padding: 0;
+ background-color: transparent;
+ border: none;
+ user-select: none;
+ cursor: pointer;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+h1,
+h2,
+h3,
+h4 {
+ margin: 0;
+ font-weight: 600;
+ line-height: 1.1;
+}
+
+p {
+ margin: 0;
+}
+
+a {
+ user-select: none;
+}
+
+img {
+ max-width: 100%;
+ user-select: none;
+}
+
+.container {
+ max-width: 1440px;
+ padding: 0 2rem;
+ margin: 0 auto;
+
+ @media all and (max-width: 480px) {
+ padding: 0 1rem;
+ }
+}
+
+.visually-hidden {
+ visibility: hidden;
+ pointer-events: none;
+ position: absolute;
+}
+
+.stop-scroll {
+ overflow: hidden;
+}
+
+.active {
+ color: $primary;
+
+ svg > * {
+ fill: $primary;
+ }
+
+ &::after {
+ background-color: $primary;
+ opacity: 1;
+ }
+}
+
+.infinite-scroll-component__outerdiv {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/styles/common/_fonts.scss b/src/styles/common/_fonts.scss
new file mode 100644
index 0000000..fcf22c6
--- /dev/null
+++ b/src/styles/common/_fonts.scss
@@ -0,0 +1,16 @@
+@mixin font-face($font-family, $url) {
+ @font-face {
+ font-family: '#{$font-family}';
+ src: local('#{$font-family}'),
+ url("../fonts/#{$url}.woff2") format('woff2'),
+ url("../fonts/#{$url}.woff") format('woff'),
+ url("../fonts/#{$url}.ttf") format('ttf');
+ font-display: block;
+ font-style: normal;
+ }
+}
+
+@include font-face('Inter', 'Inter-Medium');
+@include font-face('Inter', 'Inter-SemiBold');
+@include font-face('Inter', 'Inter-Bold');
+@include font-face('Inter', 'Inter-Regular');
diff --git a/src/styles/common/_mixin.scss b/src/styles/common/_mixin.scss
new file mode 100644
index 0000000..2334603
--- /dev/null
+++ b/src/styles/common/_mixin.scss
@@ -0,0 +1,237 @@
+@use 'variables';
+@import 'variables';
+
+@mixin font($weight: regular, $type, $color: $dark) {
+ $font-type: map-get(variables.$font-type-map, $type);
+
+ font-weight: map-get(variables.$font-weight-map, $weight);
+ font-size: nth($font-type, 1);
+ line-height: nth($font-type, 2);
+ color: $color;
+}
+
+@mixin input-error {
+ @include font(regular, p2, $error-color);
+ padding-top: 0.3rem;
+ display: block;
+ text-align: right;
+}
+
+@mixin link {
+ color: inherit;
+ text-decoration: none;
+ position: relative;
+
+ transition: all 0.2s ease-in;
+
+ &::after {
+ content: '';
+ width: 100%;
+ height: 0.15rem;
+ position: absolute;
+ left: 0;
+ bottom: 0.1rem;
+ background-color: $dark;
+
+ transition: all 0.2s ease-in;
+ }
+
+ &:hover {
+ color: $primary;
+
+ &::after {
+ background-color: $primary;
+ }
+ }
+}
+
+@mixin check-field {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+@mixin checkbox {
+ -webkit-appearance: none;
+ appearance: none;
+ margin: 0;
+ width: 2.4rem;
+ height: 2.4rem;
+ background-color: transparent;
+ border: 0.1rem solid $dark;
+ border-radius: 0.5rem;
+
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 0.2rem;
+
+ &::before {
+ content: '';
+ position: absolute;
+ width: 1.5rem;
+ height: 1.5rem;
+ background-color: $green;
+ border-radius: 0.3rem;
+ transform: scale(0);
+ transition: all 0.1s ease-in;
+ }
+
+ &:checked {
+ background-color: rgba($green, 0.3);
+ border: 1px solid rgba($green, 0.3);
+
+ &::before {
+ transform: scale(1);
+ }
+ }
+}
+
+@mixin btn-primary {
+ @include font(medium, h2, $light);
+ height: 80px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: $border-radius-btn;
+ background-color: $primary;
+ color: $light;
+ text-decoration: none;
+
+ box-shadow: 0 5px 12px rgba($primary, 0.5);
+ transition: all 0.2s ease-in;
+
+ &:hover {
+ box-shadow: 0 0 12px rgba($primary, 0);
+ background-color: $primary-hover;
+ color: $light;
+ }
+
+ &::after {
+ display: none;
+ }
+}
+
+@mixin btn-secondary {
+ @include font(semibold, h2, $dark);
+ width: 100%;
+ height: 8rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: $border-radius-btn;
+ background-color: $btn-grey;
+ box-shadow: none;
+ transition: all 0.3s ease-in-out;
+
+ &:focus,
+ &:hover {
+ background-color: $btn-dark-grey;
+ color: $dark;
+ }
+}
+
+@mixin filter {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+}
+
+@mixin accordion-icon {
+ display: block;
+ width: 2rem;
+ height: 2rem;
+ line-height: 0;
+ background-position: center;
+ background-size: contain;
+ background-repeat: no-repeat;
+ transition: transform 0.3s ease-in-out;
+}
+
+@mixin accordion-header {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 2rem 0;
+ cursor: pointer;
+}
+
+@mixin close-btn {
+ @include font(regular, p1, $dark);
+ position: absolute;
+ right: 4rem;
+ top: 8rem;
+ width: auto;
+ height: unset;
+ box-shadow: none;
+ background-color: inherit;
+ opacity: 0.3;
+ transition:
+ color $transition-set,
+ opacity $transition-set;
+
+ &:hover {
+ box-shadow: unset;
+ background-color: inherit;
+ color: $primary;
+ opacity: 1;
+ }
+
+ @media (max-width: 720px) {
+ right: 2rem;
+ top: 8rem;
+ }
+
+ @media (max-width: 480px) {
+ right: 1rem;
+ font-size: 1.8rem;
+ top: 7rem;
+ }
+}
+
+@mixin responseMessage($color, $background-color) {
+ position: fixed;
+ display: flex;
+ bottom: 7rem;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 85vw;
+ max-width: 64rem;
+ padding: 1.2rem 0;
+ justify-content: center;
+ align-items: center;
+ @include font(semibold, p1, $color);
+ background-color: $background-color;
+ border-radius: 4rem;
+ user-select: none;
+ pointer-events: none;
+ text-align: center;
+ z-index: 1000;
+
+ @media (max-width: 800px) {
+ padding: 1.5rem 2.5rem;
+ bottom: 8rem;
+ border-radius: 2rem;
+ width: 95vw;
+ }
+}
+
+@mixin bottom-page-message($color, $background-color) {
+ position: fixed;
+ transform: translateX(-50%);
+ left: 50%;
+ bottom: 2rem;
+ max-width: 37rem;
+ width: 100%;
+ height: 4rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: $background-color;
+ border-radius: 2rem;
+ color: $color;
+ z-index: 100;
+}
diff --git a/src/styles/common/_variables.scss b/src/styles/common/_variables.scss
new file mode 100644
index 0000000..6249af3
--- /dev/null
+++ b/src/styles/common/_variables.scss
@@ -0,0 +1,58 @@
+// common colors
+$dark: #111;
+$light: #fafafa;
+$placeholder-grey: #888888;
+$light-primary: #fff;
+$error-color: #f42222;
+$primary: #0047ff;
+$primary-hover: #0044ee;
+$green: #38bf70;
+$dark-green: #21693f;
+$bullet-grey: #d9d9d9;
+$text-grey: #c0c0c0;
+$btn-grey: #eaeaea;
+$btn-dark-grey: #e0e0e0;
+$background-gray: #f3f3f3;
+
+// effects
+$opacity-eye-catching-primary: 0.15;
+$background-shadow: 0px 9px 12px 0px rgba(0, 71, 255, 0.5);
+
+$transition-set: 0.3s ease-in-out;
+
+// font
+$font-weight-map: (
+ 'regular': 400,
+ 'medium': 500,
+ 'semibold': 600,
+ 'bold': 700,
+);
+
+// (FontSize, LineHeight)
+
+$title1: 4rem, 100%;
+$title2: 3rem, 100%;
+$title3: 2.4rem, 100%;
+
+$text1: 2rem, 120%;
+$text2: 1.5rem, 130%;
+$text3: 1.6rem, 130%;
+
+$text1-mob: 1.8rem 120%;
+
+$font-type-map: (
+ 'h1': $title1,
+ 'h2': $title2,
+ 'h3': $title3,
+ 'p1': $text1,
+ 'p1-mob': $text1-mob,
+ 'p2': $text2,
+ 'p3': $text3,
+);
+
+// other
+$border-radius-btn: 2rem;
+$border-radius-btn-2x: 4rem;
+
+$slider-height: calc(100vh - 6rem);
+$cart-tablet-width: 1010px;
\ No newline at end of file
diff --git a/src/styles/common/index.scss b/src/styles/common/index.scss
new file mode 100644
index 0000000..36efac8
--- /dev/null
+++ b/src/styles/common/index.scss
@@ -0,0 +1 @@
+@import "variables", "mixin";
\ No newline at end of file
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 0000000..134b778
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,6 @@
+@import "vendors/normalize";
+
+@import "common/fonts";
+@import "common/variables";
+@import "common/mixin";
+@import "common/common";
diff --git a/src/styles/vendors/_normalize.scss b/src/styles/vendors/_normalize.scss
new file mode 100644
index 0000000..ab30648
--- /dev/null
+++ b/src/styles/vendors/_normalize.scss
@@ -0,0 +1,350 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+ html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 4rem;
+ line-height: 110%;
+ margin: 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+ border-style: none;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Misc
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+ display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
\ No newline at end of file
diff --git a/src/types/enums.ts b/src/types/enums.ts
new file mode 100644
index 0000000..af8e238
--- /dev/null
+++ b/src/types/enums.ts
@@ -0,0 +1,27 @@
+export enum Input {
+ Email = 'email',
+ Password = 'password',
+ NewPassword = 'newPassword',
+ FirstName = 'firstName',
+ LastName = 'lastName',
+ Street = 'streetName',
+ Country = 'country',
+ City = 'city',
+ PostalCode = 'postalCode',
+ Date = 'date',
+ Month = 'month',
+ Year = 'year',
+ IsDefault = 'isDefault',
+}
+
+export enum SearchParams {
+ Search = 'search',
+ Color = 'color',
+ Brand = 'artist',
+ Size = 'size',
+ PriceFrom = 'priceFrom',
+ PriceTo = 'priceTo',
+ Category = 'category',
+ Sort = 'sort',
+ Page = 'page',
+}
diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts
new file mode 100644
index 0000000..8ae91ab
--- /dev/null
+++ b/src/types/interfaces.ts
@@ -0,0 +1,78 @@
+import { Address } from '@commercetools/platform-sdk';
+import { ReactElement } from 'react';
+
+export interface INewUser {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ dateOfBirth: string;
+ addresses: Address[];
+ defaultShippingAddress: number | undefined;
+ shippingAddresses: number[];
+ defaultBillingAddress: number | undefined;
+ billingAddresses: number[];
+}
+
+export interface IUser
+ extends Omit<
+ INewUser,
+ | 'defaultBillingAddress'
+ | 'defaultShippingAddress'
+ | 'shippingAddresses'
+ | 'billingAddresses'
+ | 'dateOfBirth'
+ | 'addresses'
+ > {
+ id: string;
+ version: number;
+ date: string;
+ month: string;
+ year: string;
+ defaultShippingAddressId: string;
+ defaultBillingAddressId: string;
+ shippingAddressIds: string[];
+ billingAddressIds: string[];
+ addresses: Address[];
+}
+
+export interface INewAddress {
+ streetName: string;
+ city: string;
+ postalCode: string;
+ country: string;
+ id?: string;
+}
+
+export interface IAuthAddress extends INewAddress {}
+export interface ITabsList {
+ label: string;
+ content: string | ReactElement;
+ title: string;
+}
+
+export interface IProduct {
+ productId: string;
+ artist: string;
+ title: string;
+ year: string;
+ description: string;
+ dimensions: string;
+ medium: string;
+ size: string;
+ images?: string[];
+ price: number;
+ discountPrice?: number;
+ color: string;
+}
+
+export interface IAboutDataTabs {
+ profilePicture: string;
+ stName: string;
+ role: string;
+ github: string;
+ body: {
+ textAbout: string;
+ recommendations: string[];
+ };
+}
diff --git a/src/types/types.ts b/src/types/types.ts
new file mode 100644
index 0000000..d2a6d38
--- /dev/null
+++ b/src/types/types.ts
@@ -0,0 +1 @@
+export type TStatus = 'loading' | 'error' | 'success' | 'initial';
diff --git a/src/utils/changeParams.ts b/src/utils/changeParams.ts
new file mode 100644
index 0000000..4883b85
--- /dev/null
+++ b/src/utils/changeParams.ts
@@ -0,0 +1,15 @@
+import { SetURLSearchParams } from 'react-router-dom';
+import { SearchParams } from '../types/enums';
+export function changeParams(setSearchParams: SetURLSearchParams, value: string, field: SearchParams): void {
+ if (value) {
+ setSearchParams((searchParams) => {
+ searchParams.set(field, value);
+ return searchParams;
+ });
+ } else {
+ setSearchParams((searchParams) => {
+ searchParams.delete(field);
+ return searchParams;
+ });
+ }
+}
diff --git a/src/utils/checkError.ts b/src/utils/checkError.ts
new file mode 100644
index 0000000..365dfa3
--- /dev/null
+++ b/src/utils/checkError.ts
@@ -0,0 +1,6 @@
+export function checkError(error: unknown): string {
+ if (error instanceof Error) {
+ return error.message;
+ }
+ return 'Something was wrong';
+}
diff --git a/src/utils/extractLocalCart.ts b/src/utils/extractLocalCart.ts
new file mode 100644
index 0000000..7943b46
--- /dev/null
+++ b/src/utils/extractLocalCart.ts
@@ -0,0 +1,28 @@
+import { ICart, IItemCart, IPromoCode } from '../store/cart/types';
+import { Cart } from '@commercetools/platform-sdk';
+import { LANG_CODE } from '../constant';
+
+export function extractLocalCart(cart: Cart): ICart {
+ const items: IItemCart[] = cart.lineItems.map((item) => ({
+ itemId: item.id,
+ productId: item.productId,
+ name: item.name[LANG_CODE],
+ artist: item.variant.attributes?.find((item) => item.name === 'artist')?.value[LANG_CODE] || '',
+ image: item.variant.images?.[0].url || '',
+ quantity: item.quantity,
+ price: item.price.value.centAmount,
+ discountedPrice: item.price.discounted?.value.centAmount || 0,
+ totalPrice: item.totalPrice.centAmount,
+ }));
+
+ return {
+ id: cart.id,
+ version: cart.version,
+ lineItems: items,
+ totalPrice: cart.totalPrice.centAmount,
+ totalQuantity: cart.totalLineItemQuantity || 0,
+ codes:
+ cart.discountCodes.map((item): IPromoCode => ({ type: item.discountCode.typeId, id: item.discountCode.id })) ||
+ [],
+ };
+}
diff --git a/src/utils/extractLocalUser.ts b/src/utils/extractLocalUser.ts
new file mode 100644
index 0000000..1c4f5c1
--- /dev/null
+++ b/src/utils/extractLocalUser.ts
@@ -0,0 +1,29 @@
+import { Customer } from '@commercetools/platform-sdk';
+import { IUser } from '../types/interfaces';
+
+export function extractLocalUser(customer: Customer): IUser {
+ const dateOfBirth = customer.dateOfBirth;
+ let year, month, date;
+
+ if (dateOfBirth) {
+ const dateArr = dateOfBirth.split('-');
+ [year, month, date] = dateArr;
+ }
+
+ return {
+ id: customer.id,
+ version: customer.version,
+ email: customer.email,
+ firstName: customer.firstName || '',
+ lastName: customer.lastName || '',
+ password: customer.password || '',
+ date: date || '',
+ month: month || '',
+ year: year || '',
+ defaultShippingAddressId: customer.defaultShippingAddressId || '',
+ defaultBillingAddressId: customer.defaultBillingAddressId || '',
+ shippingAddressIds: customer.shippingAddressIds || [],
+ billingAddressIds: customer.billingAddressIds || [],
+ addresses: customer.addresses,
+ };
+}
diff --git a/src/utils/formatPrice.ts b/src/utils/formatPrice.ts
new file mode 100644
index 0000000..2f0a6d9
--- /dev/null
+++ b/src/utils/formatPrice.ts
@@ -0,0 +1,7 @@
+export function formatPrice(price: number): string {
+ return price.toLocaleString('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ });
+}
diff --git a/src/utils/getCategoryList.ts b/src/utils/getCategoryList.ts
new file mode 100644
index 0000000..f41a73a
--- /dev/null
+++ b/src/utils/getCategoryList.ts
@@ -0,0 +1,17 @@
+import { Category } from '@commercetools/platform-sdk';
+import { ICategoryFilterItem } from '../components/Filters/types';
+import { LANG_CODE } from '../constant';
+
+export function getCategoryList(results: Category[]): ICategoryFilterItem[] {
+ const categories: ICategoryFilterItem[] = [];
+ if (results.length > 0) {
+ results.forEach((item) => {
+ categories.push({
+ label: item.name[LANG_CODE],
+ value: item.id,
+ parent: item.parent ? item.parent.id : null,
+ });
+ });
+ }
+ return categories;
+}
diff --git a/src/utils/getCurrentDate.ts b/src/utils/getCurrentDate.ts
new file mode 100644
index 0000000..ae796a2
--- /dev/null
+++ b/src/utils/getCurrentDate.ts
@@ -0,0 +1,6 @@
+export function getCurrentDate(): string {
+ const today = new Date();
+ const options: Intl.DateTimeFormatOptions = { weekday: 'long', month: 'short', day: '2-digit', year: 'numeric' };
+
+ return today.toLocaleDateString('en-US', options);
+}
diff --git a/src/utils/getMonthIndex.ts b/src/utils/getMonthIndex.ts
new file mode 100644
index 0000000..0a6225b
--- /dev/null
+++ b/src/utils/getMonthIndex.ts
@@ -0,0 +1,4 @@
+export function getMonthIndex(month: string): number {
+ const date = new Date(`${month} 1, 2023`);
+ return date.getMonth() + 1;
+}
diff --git a/src/utils/getSearchParams.ts b/src/utils/getSearchParams.ts
new file mode 100644
index 0000000..f8a6a6f
--- /dev/null
+++ b/src/utils/getSearchParams.ts
@@ -0,0 +1,50 @@
+import { QueryParam } from '@commercetools/platform-sdk';
+import { SearchParams } from '../types/enums';
+import { PAGE_LIMIT } from '../constant';
+
+export function getSearchParams(search: URLSearchParams): { [p: string]: QueryParam } {
+ const queryArgs: { [p: string]: QueryParam } = {};
+
+ queryArgs.fuzzy = true;
+
+ const filters: string[] = [];
+ if (search.get(SearchParams.Category)) {
+ const value = search.get(SearchParams.Category);
+ filters.push(`categories.id: subtree("${value}")`);
+ }
+ if (search.get(SearchParams.Color)) {
+ const value = search.get(SearchParams.Color);
+ filters.push(`variants.attributes.${SearchParams.Color}.en-US:"${value?.split(',').join('","')}"`);
+ }
+ if (search.get(SearchParams.Brand)) {
+ const value = search.get(SearchParams.Brand);
+ filters.push(`variants.attributes.${SearchParams.Brand}.en-US:"${value?.split(',').join('","')}"`);
+ }
+ if (search.get(SearchParams.Size)) {
+ const value = search.get(SearchParams.Size);
+ filters.push(`variants.attributes.${SearchParams.Size}.en-US:"${value?.split(',').join('","')}"`);
+ }
+ if (search.get(SearchParams.PriceFrom) || search.get(SearchParams.PriceTo)) {
+ const from = search.get(SearchParams.PriceFrom);
+ const to = search.get(SearchParams.PriceTo);
+ filters.push(`variants.price.centAmount:range (${Number(from) * 100 || '*'} to ${Number(to) * 100 || '*'})`);
+ }
+ if (filters.length > 0) {
+ queryArgs.filter = filters;
+ }
+
+ queryArgs.limit = PAGE_LIMIT;
+ queryArgs.offset = PAGE_LIMIT * (Number(search.get(SearchParams.Page)) - 1 || 0);
+
+ if (search.get(SearchParams.Sort)) {
+ const value = search.get(SearchParams.Sort);
+ queryArgs.sort = [value || ''];
+ }
+
+ if (search.get(SearchParams.Search)) {
+ const value = search.get(SearchParams.Search);
+ queryArgs['text.en-US'] = `'${value}'`;
+ }
+
+ return queryArgs;
+}
diff --git a/src/utils/getUserAge.ts b/src/utils/getUserAge.ts
new file mode 100644
index 0000000..1c1808d
--- /dev/null
+++ b/src/utils/getUserAge.ts
@@ -0,0 +1,18 @@
+import { getMonthIndex } from './getMonthIndex.ts';
+
+export function getUserAge(now: Date, date: string, month: string, year: string): number {
+ const currentYear = now.getFullYear();
+ const currentMonth = now.getMonth();
+ const currentDate = now.getDate();
+ const birthYear = Number(year);
+ const birthMonth = getMonthIndex(month) - 1;
+ const birthDate = Number(date);
+
+ let userAge = currentYear - birthYear;
+
+ if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDate < birthDate)) {
+ userAge -= 1;
+ }
+
+ return userAge;
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..502dd0f
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,10 @@
+export * from './extractLocalUser';
+export * from './checkError';
+export * from './getMonthIndex';
+export * from './getCurrentDate';
+export * from './parseProductData';
+export * from './getSearchParams';
+export * from './changeParams';
+export * from './getCategoryList';
+export * from './formatPrice';
+export * from './splitToParagraphs';
diff --git a/src/utils/parseProductData.ts b/src/utils/parseProductData.ts
new file mode 100644
index 0000000..52344e0
--- /dev/null
+++ b/src/utils/parseProductData.ts
@@ -0,0 +1,72 @@
+import { Product, ProductProjection } from '@commercetools/platform-sdk';
+import { IProduct } from '../types/interfaces';
+import { LANG_CODE } from '../constant';
+
+export function parseProductData(body: Product): IProduct {
+ const product = body.masterData.current;
+
+ const productId = body.id;
+
+ const productAttr = product.masterVariant.attributes;
+
+ if (!productAttr) {
+ throw new Error('Failed to get product attributes.');
+ }
+
+ const attrArray: [string, string][] | undefined = productAttr?.map((attr) => [attr.name, attr.value[LANG_CODE]]);
+ const { artist, year, medium, dimensions, color, size } = Object.fromEntries(attrArray ? attrArray : []);
+
+ const title = product.name[LANG_CODE];
+ const description = product.description?.[LANG_CODE];
+ const images = product.masterVariant.images?.map((item) => item.url);
+
+ const price = Number(product.masterVariant.prices?.[0].value.centAmount) / 100 || 0;
+ const discountPrice = Number(product.masterVariant.prices?.[0].discounted?.value.centAmount) / 100 || 0;
+
+ if (!description || !images) {
+ throw new Error('Failed to get product information.');
+ }
+
+ return { artist, title, year, medium, dimensions, color, size, description, images, price, discountPrice, productId };
+}
+
+export function parseProductListData(dataArray: ProductProjection[]): IProduct[] {
+ return dataArray.map((product) => {
+ const productId = product.id;
+
+ const productAttr = product.masterVariant.attributes;
+
+ if (!productAttr) {
+ throw new Error('Failed to get product attributes.');
+ }
+
+ const attrArray: [string, string][] | undefined = productAttr?.map((attr) => [attr.name, attr.value[LANG_CODE]]);
+ const { artist, year, medium, dimensions, color, size } = Object.fromEntries(attrArray ? attrArray : []);
+
+ const title = product.name[LANG_CODE];
+ const description = product.description?.[LANG_CODE] || '';
+ const images = product.masterVariant.images?.map((item) => item.url) || [];
+
+ const price = Number(product.masterVariant.prices?.[0]?.value.centAmount) / 100 || 0;
+ const discountPrice = Number(product.masterVariant.prices?.[0]?.discounted?.value.centAmount) / 100 || 0;
+
+ if (!description || !images) {
+ throw new Error('Failed to get product information.');
+ }
+
+ return {
+ artist,
+ title,
+ year,
+ medium,
+ dimensions,
+ color,
+ size,
+ description,
+ images,
+ price,
+ discountPrice,
+ productId,
+ };
+ });
+}
diff --git a/src/utils/splitToParagraphs.tsx b/src/utils/splitToParagraphs.tsx
new file mode 100644
index 0000000..06ceb57
--- /dev/null
+++ b/src/utils/splitToParagraphs.tsx
@@ -0,0 +1,13 @@
+import { ReactElement } from 'react';
+
+export function splitToParagraphs(text: string, className?: string): ReactElement[] | ReactElement {
+ const spans = text
+ .replace(/\\n\n/g, '\n')
+ .split('\n')
+ .map((item, index) => (
+
+ {item}
+
+ ));
+ return spans.length > 1 ? spans : {text};
+}
diff --git a/src/utils/validation/cityValidate.ts b/src/utils/validation/cityValidate.ts
new file mode 100644
index 0000000..cea8c75
--- /dev/null
+++ b/src/utils/validation/cityValidate.ts
@@ -0,0 +1,14 @@
+import { errorMsg } from '../../constant';
+
+export function cityValidate(value: string): string {
+ let error: string = '';
+ const isValidValue = /^[\p{L}\s]+$/u.test(value.trim());
+
+ if (!value) {
+ error = errorMsg.city.empty;
+ } else if (!isValidValue) {
+ error = errorMsg.city.invalid;
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/countryValidate.ts b/src/utils/validation/countryValidate.ts
new file mode 100644
index 0000000..4593ec4
--- /dev/null
+++ b/src/utils/validation/countryValidate.ts
@@ -0,0 +1,11 @@
+import { errorMsg } from '../../constant';
+
+export function countryValidate(value: string): string {
+ let error: string = '';
+
+ if (!value) {
+ error = errorMsg.country.empty;
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/dateMYValidate.ts b/src/utils/validation/dateMYValidate.ts
new file mode 100644
index 0000000..005e563
--- /dev/null
+++ b/src/utils/validation/dateMYValidate.ts
@@ -0,0 +1,26 @@
+import { errorMsg } from '../../constant';
+import { getUserAge } from '../getUserAge';
+
+export function dateMYValidate(value: string): string {
+ let error: string = '';
+ const trimValue = value.trim();
+ const regex = /^\d{1,2}\p{L}+\d{4}$/u;
+ const isValid = regex.test(trimValue);
+
+ if (!isValid) {
+ error = errorMsg.date.empty;
+ } else if (isValid) {
+ const parts = trimValue.match(/\d+|[a-zA-Z]+/g);
+ if (parts) {
+ const [date, month, year] = parts;
+ const MIN_AGE = 13;
+ const userAge = getUserAge(new Date(), date, month, year);
+
+ if (userAge < MIN_AGE) {
+ error = errorMsg.date.invalid;
+ }
+ }
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/emailValidate.ts b/src/utils/validation/emailValidate.ts
new file mode 100644
index 0000000..38f22b4
--- /dev/null
+++ b/src/utils/validation/emailValidate.ts
@@ -0,0 +1,15 @@
+import { errorMsg } from '../../constant';
+
+export function emailValidate(value: string): string {
+ let error: string = '';
+ const isValidEmail = /^[\w]+@([\w]+\.)+[\w]{2,}$/.test(value);
+ const hasSpaces = /\s/.test(value);
+
+ if (!value) {
+ error = errorMsg.email.empty;
+ } else if (!isValidEmail || hasSpaces) {
+ error = errorMsg.email.invalid;
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/index.ts b/src/utils/validation/index.ts
new file mode 100644
index 0000000..4272a23
--- /dev/null
+++ b/src/utils/validation/index.ts
@@ -0,0 +1,9 @@
+export * from './cityValidate';
+export * from './dateMYValidate';
+export * from './emailValidate';
+export * from './lastNameValidate';
+export * from './nameValidate';
+export * from './passwordValidate';
+export * from './streetValidate';
+export * from './zipCodeValidate';
+export * from './countryValidate';
diff --git a/src/utils/validation/lastNameValidate.ts b/src/utils/validation/lastNameValidate.ts
new file mode 100644
index 0000000..18cbf72
--- /dev/null
+++ b/src/utils/validation/lastNameValidate.ts
@@ -0,0 +1,14 @@
+import { errorMsg } from '../../constant';
+
+export function lastNameValidate(value: string): string {
+ let error: string = '';
+ const isValidValue = /^[\p{L}\s]+$/u.test(value.trim());
+
+ if (!value) {
+ error = errorMsg.lastName.empty;
+ } else if (!isValidValue) {
+ error = errorMsg.lastName.invalid;
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/nameValidate.ts b/src/utils/validation/nameValidate.ts
new file mode 100644
index 0000000..626bebf
--- /dev/null
+++ b/src/utils/validation/nameValidate.ts
@@ -0,0 +1,14 @@
+import { errorMsg } from '../../constant';
+
+export function nameValidate(value: string): string {
+ let error: string = '';
+ const isValidValue = /^[\p{L}\s]+$/u.test(value.trim());
+
+ if (!value) {
+ error = errorMsg.firstName.empty;
+ } else if (!isValidValue) {
+ error = errorMsg.firstName.invalid;
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/passwordValidate.ts b/src/utils/validation/passwordValidate.ts
new file mode 100644
index 0000000..255659f
--- /dev/null
+++ b/src/utils/validation/passwordValidate.ts
@@ -0,0 +1,30 @@
+import { errorMsg } from '../../constant';
+
+export function passwordValidate(value: string, isShort: boolean): string {
+ let error: string = '';
+ const MIN_PASSWORD_LENGTH = 8;
+ const hasUppercase = /\p{Lu}/u.test(value);
+ const hasLowercase = /\p{Ll}/u.test(value);
+ const hasDigit = /\d/.test(value);
+ const hasSpecialCharacter = /[!@#$%^&*]/.test(value);
+ const hasSpaces = /\s/.test(value);
+
+ if (!value) {
+ error = errorMsg.password.empty;
+ } else if (
+ value.length < MIN_PASSWORD_LENGTH ||
+ !hasUppercase ||
+ !hasLowercase ||
+ !hasDigit ||
+ !hasSpecialCharacter ||
+ hasSpaces
+ ) {
+ if (isShort) {
+ error = errorMsg.password.invalidCheck;
+ } else {
+ error = errorMsg.password.invalid;
+ }
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/streetValidate.ts b/src/utils/validation/streetValidate.ts
new file mode 100644
index 0000000..df769c3
--- /dev/null
+++ b/src/utils/validation/streetValidate.ts
@@ -0,0 +1,11 @@
+import { errorMsg } from '../../constant';
+
+export function streetValidate(value: string): string {
+ let error: string = '';
+
+ if (!value) {
+ error = errorMsg.street.empty;
+ }
+
+ return error;
+}
diff --git a/src/utils/validation/zipCodeValidate.ts b/src/utils/validation/zipCodeValidate.ts
new file mode 100644
index 0000000..388e8f9
--- /dev/null
+++ b/src/utils/validation/zipCodeValidate.ts
@@ -0,0 +1,14 @@
+import { errorMsg } from '../../constant';
+
+export function zipCodeValidate(value: string): string {
+ let error: string = '';
+ const isValidZipCode = /^\d{5}$/.test(value);
+
+ if (!value) {
+ error = errorMsg.zipCode.empty;
+ } else if (!isValidZipCode) {
+ error = errorMsg.zipCode.invalid;
+ }
+
+ return error;
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..0af29fe
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "esModuleInterop": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..a012ce4
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,43 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
+import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
+import rollupNodePolyFill from 'rollup-plugin-node-polyfills';
+import checker from 'vite-plugin-checker';
+import svgr from 'vite-plugin-svgr';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ checker({
+ typescript: true,
+ }),
+ svgr({
+ exportAsDefault: true,
+ }),
+ ],
+ resolve: {
+ alias: {
+ stream: 'stream-browserify',
+ },
+ },
+ optimizeDeps: {
+ esbuildOptions: {
+ define: {
+ global: 'globalThis',
+ },
+ plugins: [
+ NodeGlobalsPolyfillPlugin({
+ process: true,
+ buffer: true,
+ }),
+ NodeModulesPolyfillPlugin(),
+ ],
+ },
+ },
+ build: {
+ rollupOptions: {
+ plugins: [rollupNodePolyFill()],
+ },
+ },
+});