diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..94f4a482
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.{ts, tsx}]
+charset = utf-8
+indent_style = tab
+indent_size = 2
+rulers = 80, 100, 120
+
+[{package.json}]
+indent_style = space
+indent_size = 2
diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..95fc16e9
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+VITE_PROJECT_KEY=PROJECT_KEY
+VITE_CLIENT_SECRET=CLIENT_SECRET
+VITE_CLIENT_ID=CLIENT_ID
+VITE_AUTH_SERVICE_URL=AUTH_SERVICE_URL
+VITE_API_HOST_URL=API_HOST_URL
+VITE_DEFAULT_CUSTOMER_SCOPE=DEFAULT_CUSTOMER_SCOPE
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 00000000..c6e52700
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,76 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'airbnb',
+ 'airbnb-typescript',
+ 'airbnb/hooks',
+ 'plugin:react/recommended',
+ 'plugin:react-hooks/recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:prettier/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts', 'tailwind.config.js', 'postcss.config.js', 'prettier.config.js'],
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ project: ['tsconfig.json'],
+ tsconfigRootDir: __dirname,
+ ecmaVersion: 2021,
+ sourceType: 'module',
+ },
+ plugins: ['react', 'import', 'react-refresh', '@typescript-eslint', 'simple-import-sort', 'import', 'prettier'],
+ rules: {
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+ 'react/react-in-jsx-scope': 0,
+ 'import/no-extraneous-dependencies': 0,
+ 'import/extensions': 0,
+ 'import/no-cycle': 0,
+ 'no-param-reassign': 0,
+ "jsx-a11y/label-has-associated-control": 0,
+ "jsx-a11y/label-has-for": 0,
+ "react/jsx-no-bind": 0,
+ "@typescript-eslint/no-unused-expressions": 0,
+ 'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}],
+ 'import/order': [
+ 'error',
+ {
+ groups: [
+ ['external', 'builtin'],
+ 'internal',
+ ['sibling', 'parent'],
+ 'index',
+ ],
+ pathGroups: [
+ {
+ pattern: '@(react|react-native)',
+ group: 'external',
+ position: 'before',
+ },
+ {
+ pattern: '@src/**',
+ group: 'internal',
+ },
+ ],
+ pathGroupsExcludedImportTypes: ['internal', 'react'],
+ 'newlines-between': 'always',
+ alphabetize: {
+ order: 'asc',
+ caseInsensitive: true,
+ },
+ },
+ ],
+ },
+ settings: {
+ 'import/resolver': {
+ typescript: {
+ alwaysTryTypes: true,
+ },
+ react: {
+ version: 'detect',
+ },
+ },
+ },
+};
diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml
new file mode 100644
index 00000000..c72f6e4b
--- /dev/null
+++ b/.github/actions/ci-setup/action.yml
@@ -0,0 +1,21 @@
+name: "Setup Continuous Integration"
+description: "Cache Dependencies"
+runs:
+ using: "composite"
+ steps:
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: "npm"
+
+ - name: Cache NPM Dependencies
+ uses: actions/cache@v3
+ id: cache-primes
+ with:
+ path: node_modules
+ key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
+
+ - name: Install Dependencies
+ run: npm install
+ shell: bash
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..375b9a6a
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,40 @@
+name: Pull Request
+
+on:
+ pull_request:
+ types:
+ - closed
+
+permissions:
+ contents: write
+
+env:
+ NODE_VERSION: 18.16.0
+
+jobs:
+ if_merged:
+ if: github.event.pull_request.merged == true
+ name: Build And Deploy
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout๏ธ
+ uses: actions/checkout@v3
+
+ - name: Setup Continuous integration
+ uses: ./.github/actions/ci-setup
+
+ - name: Create env file
+ run: |
+ echo "${{ secrets.ENV_FILE }}" > .env
+
+ - name: Build
+ run: npm run build
+
+ - name: Delete env file
+ run: rm .env
+
+ - name: Deploy
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: dist
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..0edd70e4
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,57 @@
+name: Pull Request
+
+on:
+ pull_request:
+ types: [opened, synchronize]
+
+env:
+ NODE_VERSION: 18.16.0
+
+jobs:
+ linting:
+ name: Lint
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup Continuous integration
+ uses: ./.github/actions/ci-setup
+
+ - name: Lint Application
+ run: npm run lint
+
+ type-check:
+ name: Types
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup Continuous Integration
+ uses: ./.github/actions/ci-setup
+
+ - name: Check Application Types
+ run: npm run type-check
+
+ tests:
+ name: Test
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup Continuous integration
+ uses: ./.github/actions/ci-setup
+
+ - name: Create env file
+ run: |
+ echo "${{ secrets.ENV_FILE }}" > .env
+
+ - name: test
+ run: npm run test
+
+ - name: Delete env file
+ run: rm .env
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..4ddf1437
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+.vite
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+.env
+.env.*
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 00000000..d24fdfc6
--- /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.cjs b/.prettierrc.cjs
new file mode 100644
index 00000000..358e01f8
--- /dev/null
+++ b/.prettierrc.cjs
@@ -0,0 +1,10 @@
+module.exports = {
+ tabWidth: 2,
+ semi: true,
+ singleQuote: true,
+ printWidth: 120,
+ endOfLine: 'auto',
+ arrowParens: 'always',
+ bracketSpacing: true,
+ plugins: ['prettier-plugin-tailwindcss'],
+};
diff --git a/README.md b/README.md
index d416b843..849b8103 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,354 @@
-# eCommerce-Application
\ No newline at end of file
+# ๐ฃ **SushiSushi โ eCommerce-Application**
+
+*RS-School ๐ฆฅ eCommerce-Application. Stage 2 Final Task RS2023Q1* ๐งโ
+๏ธ***SushiSushi** - Is not just your regular food delivery; it's a culinary journey to Japan's finest flavors, delivered to your doorstep ๐.*
+
+**๐๐ฅณ One of the best RS2023Q1 [graduation projects](https://discord.com/channels/516715744646660106/1072962541812191423/1156268411123408926)!**
+
+**Task description** - [Task description](https://github.com/rolling-scopes-school/tasks/tree/master/tasks/eCommerce-Application) ๐
+**Deployed project preview** - [SushiSushi](https://quiddlee.github.io/eCommerce-Application/) ๐
+
+# Getting Started ๐
+
+To run our project locally, you would have to **download zip** file with our repository or **clone** it to your computer. โจ
+
+## Setup and Running โ ๏ธ
+
+What things do you need to do in order to run our project locally? ๐ค
+
+
+* Use node 18.x or higher. โก
+* Installed [.git](https://git-scm.com/) on your computer. โ๏ธ
+* Code Editor of your choice. ๐
+* Installed [npm](https://www.npmjs.com/). ๐ฆ
+* Created [commercetools](https://docs.commercetools.com/) account. ๐
+
+
+## Installation And Preparation โฌ๏ธ
+
+ First make sure you have all the things listed in the previous section. Then clone our repository to your computer: ๐
+
+```
+git clone git@github.com:Quiddlee/eCommerce-Application.git
+```
+
+or download zip file manually with our repository ๐.
+
+Navigate into project folder and run ๐ฆ:
+
+```
+npm install
+```
+
+Create ```.env``` file in the root of the project and add your commercetools credentials ๐ฅ.
+
+You can find ```.env.example``` as an example file in the project root or follow the lines below ๐บ:
+
+
+```dotenv
+VITE_PROJECT_KEY=PROJECT_KEY
+VITE_CLIENT_SECRET=CLIENT_SECRET
+VITE_CLIENT_ID=CLIENT_ID
+VITE_AUTH_SERVICE_URL=AUTH_SERVICE_URL
+VITE_API_HOST_URL=API_HOST_URL
+VITE_DEFAULT_CUSTOMER_SCOPE=DEFAULT_CUSTOMER_SCOPE
+```
+
+Follow the [step by step guide](#adding-products-data-to-commerce-tools-) on how to add your own products data to commercetools ๐ฆฉ.
+
+Finally run a development server: ๐คฉ
+```
+npm run dev
+```
+Aaaaand you're done! ๐๐ฅณ
+
+
+## Available Scripts ๐ฅ
+
+Here you can find all the scripts that are available in our project. ๐ฆ
+
+Linting and Prettifying happens automatically when files are staged ๐, however you can do it manually with absolutely no problems:
+
+Lint the App with **ESlint**: โ๏ธ
+
+```
+npm run lint
+```
+
+Format the App with **Prettier**: ๐งน
+
+```
+npm run format
+```
+
+Type check the App with **TypeScript**: ๐
+
+```
+npm run type-check
+```
+
+Install **Husky** to enable pre-commit hooks: ๐ฃ
+
+```
+npm run prepare
+```
+
+Run unit-tests with **Vitest**: ๐งช
+
+```
+npm run test
+```
+
+**Build** project for production: ๐๏ธ
+
+```
+npm run build
+```
+
+**Preview** the **production build** locally: ๐
+
+```
+npm run preview
+```
+
+# Features ๐
+
+* **Browse the world of royal asian flavour meals! ๐๐ฃ**
+
+
+
+* **Choose the meal you like the most ๐๐ด**
+
+
+
+* **Fill your shopping cart ๐๏ธ๐**
+
+
+
+* **Search for your lovely meals! ๐**
+
+
+
+* **Filter the results to make easier your search ๐**
+
+
+
+* **Sign up your account! ๐**
+
+
+
+* **Change your data at any time ๐**
+
+
+
+* **Beautiful Mobile App, and much much more features, try by yourself ๐!**
+
+
+
+
+
+# Technology Stack โ๏ธ
+
+### **Developing ๐**
+* [React.js](https://react.dev/) - **The web framework used** โ๏ธ
+* [Redux / RTK Query](https://redux.js.org/) - **The State Management Tool and Data Fetching Library** ๐งฐ
+* [React Router Dom](https://reactrouter.com/) - **The Router** ๐
+* [TypeScript](https://www.typescriptlang.org/) - **The Language** ๐
+* [Tailwind](https://tailwindcss.com/) - **The CSS Framework** ๐
+* [Vite](https://vitejs.dev/) - **The Bundler ๐ฆ**
+* [Postman](https://www.postman.com/) - **The API Testing Tool** ๐ฌ
+
+### **Code Quality ๐งน**
+* [Vitest](https://vitest.dev/) - **The Test Runner** ๐งช
+* [Testing Library](https://testing-library.com/) - **The Testing Framework** ๐ซ
+* [ESLint โ Air-bnb base](https://eslint.org/) - **The Linter** ๐
+* [Prettier](https://prettier.io/) - **The Code Formatter** ๐
+* [Husky](https://typicode.github.io/husky/#/) - **The Pre-commit Hooks** ๐ช
+* [Lint Staged]() - **The Pre-commit Hooks** ๐ฆ
+* [EditorConfig](https://editorconfig.org/) - **The Code Style Enforcer** ๐
+
+
+### **External Libraries ๐**
+* [Formik](https://formik.org/) - **The Form Validation Library** ๐
+* [Yup](https://github.com/jquense/yup) - **The Form Validation Schema Builder** ๐๏ธ
+* [Framer Motion](https://www.framer.com/motion/) - **The Animation Library** ๐
+* [Swiper](https://swiperjs.com/) - **The Slider Library** ๐
+* [React Infinite Scroll Component](https://github.com/ankeetmaini/react-infinite-scroll-component) - **The Infinite Scroll Library** โพ๏ธ
+* [React Zoom Pan Pinch](https://github.com/BetterTyped/react-zoom-pan-pinch) - **The Image Zoom Library** ๐
+* [Async Mutex](https://github.com/DirtyHairy/async-mutex#readme) - **The Async Mutex Library** ๐ค
+* [React Star Rating Component](https://github.com/raymon-zhang/react-star-rate) - **The Star Rating Component** โญ
+
+### **Design ๐จ**
+* [Figma](https://www.figma.com/) - **The Design Tool** ๐จ
+* [Project Design](https://www.figma.com/file/rYRBs7GD0vDQDjgjU0n972/eCommerce-Application-%F0%9F%8C%90?type=design&node-id=0%3A1&mode=design&t=asbvxijfRHlGg8Uz-1) - **The Project Design** ๐
+
+
+### **Git Methodology**
+* [Git Flow](https://datasift.github.io/gitflow/IntroducingGitFlow.html) - **The Git Flow** ๐
+
+### **Architecture ๐๏ธ**
+* [Feature Sliced Design](https://feature-sliced.design/) - **The Architecture** ๐๏ธ
+
+### **API ๐ก**
+* [Commercetools](https://docs.commercetools.com/) - **The API** ๐ฆ
+
+
+
+### **CI/CD ๐**
+* [GitHub Actions](https://pages.github.com/) - **The CI/CD** ๐๏ธ
+
+
+
+### **Project Management ๐**
+* [GitHub Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) - **The Project Management Tool** ๐
+
+
+
+
+
+### **Communication ๐ฃ**
+* [Discord](https://discord.com/) - **The 3xWeek Meeting Communication Tool** ๐ฃ๏ธ
+* [Telegram](https://web.telegram.org/) - **The Main Chatting Communication Tool** ๐ฌ
+
+### **Project planing ๐ฆ**
+* [Lucidchart](https://www.lucidchart.com/) - **The Project planing Tool** ๐ง
+
+
+# Core Development Team ๐จโ๐ป
+
+### **Bohdan Shcherbyna** - **Front-end Developer / Team Lead** ๐ฆ
+
+
+
+### Contribution ๐ช:
+* Working with the API ๐
+* Managing the Redux store ๐ช
+* Animations ๐ฆ
+* UI/UX Design ๐ญ
+* Code review ๐ซ
+* Testing ๐งช
+* Deployment ๐
+* Documentation ๐
+* CI/CD โจ
+
+### Contact ๐:
+* Email - ```bogdanscherbinadev@gmail.com``` ๐ฌ
+* Linkedin - [Bohdan Shcherbyna](https://www.linkedin.com/in/quiddle/) โ๏ธ
+* Telegram - [@Quiddle](https://t.me/quiddle) ๐ฑ
+* GitHub - [Quiddlee](https://github.com/Quiddlee) ๐ฆ
+
+#
+
+### **Oleksii Drohachov** - **Front-end Developer** ๐ฆ
+
+
+
+### Contribution ๐ช:
+* Managing the app router ๐
+* Code review ๐ซ
+* Documentation ๐
+* Working with CommerceTools data ๐
+* Managing app products ๐
+* Fully implement user profile page ๐ช
+* UI layout ๐ฆฅ
+* Project setup ๐ซ
+* Working with the API ๐ฉบ
+
+### Contact ๐:
+* Email - ```asdrogachev@gmail.com``` ๐ฌ
+* Linkedin - [Oleksii Drohachov](https://www.linkedin.com/in/oleksii-drohachov-b127a9245/) โ๏ธ
+* Telegram - [@Tedzury](https://t.me/tedzury) ๐ฑ
+* GutHub - [Tedzury](https://github.com/Tedzury) ๐ฆ
+
+#
+
+### **Harry Holubiev** - **Front-end Developer** ๐ฒ
+
+
+
+### Contribution ๐ช:
+* Designed product card ๐ด
+* Product page ๐
+* Logo and animations ๐ฃ
+* implemented some features in Cart component ๐
+* Managing the app router ๐
+* Code review ๐ซ
+* Documentation ๐
+* UI Layout ๐ฆฅ
+* Working with the API ๐ช
+
+### Contact ๐:
+* Email - ```queharambe@gmail.com``` ๐ฌ
+* GitHub - [barrydilan](https://github.com/barrydilan) ๐ฆ
+
+# Adding products data to commerce tools ๐ฆฉ:
+Here you can find detailed instructions on how to add products data to commercetools ๐ต๏ธ. To add data with products in commercetools we prepared [fully setted up repo](https://github.com/Tedzury/commercetools-sushisushi-data) for you ๐ซ.
+
+If you want exactly the same products as we have ๐:
+* You need to follow all the [Installation and Preparation](#Installation And Preparation) steps. โ
+* Aaand type a few ๐ค commands into the terminal to upload all the data into your commercetools account! ๐คฉ
+
+
+Detailed instructions will be provided in further reading.
+Nevertheless, feel free to modify data in any possible way you want ๐ค. Main steps and cornerstones will be covered in further reading. So, let's proceed into detailed instructions. โฉ
+
+# Step-by-step guide on how to add your own products data to commercetools ๐
+
+To add your own products data to commercetools you need to follow next steps ๐ฆ:
+
+* Clone [example repo](https://github.com/Tedzury/commercetools-sushisushi-data) into your computer. ๐ถโ๐ซ๏ธ
+
+* Register your account at commercetools.
+
+* Go to ```Settings > Developer settings > Create new API client > Select predefined Admin scope > Create API client```
+
+* **โ ๏ธ !important step โ ๏ธ** Scroll to bottom of web page, there will be **select input** ๐ with suggested options for downloading environment variables.
+
+* Pick option to download in format ```.env``` file. **Don't close this window ๐จ until you download all the files needed**, info is shown only **once** ๐ฑ till you won't close the window ๐.
+
+
+* After downloading ๐ฉ, insert downloaded ```.env``` file into your cloned repo from first step. Remove ๐งน suggested name from this file, so there is only left ```.env``` in file name. More detailed info with pictures is available at [original sunrisedata](https://github.com/commercetools/commercetools-sunrise-data) ๐จ
+* **Open terminal** ๐งโ๐ป inside the **root folder** of the cloned repo and type next instructions: ```npm run clean:categories && npm run import:categories && npm run clean:products && npm run import:products```.
+
+And that all! ๐ That simple, following this guide allows you to fully imitate our products data, that we used in our project. ๐
+
+# Remarks and additional info ๐ข:
+
+And here will be some clues to add your own products into commerce tools ๐งฉ.
+
+In our project we use very limited info and options for our products ๐ค. Commercetools provides much more larger management for products ๐, categories, customers, prices, taxes, supply channels and so on ๐คฏ, but in our case we ain't needed so much options ๐งโโ๏ธ, so we used as little of it, as possible. In fact, we use only 2 minimal options to clean and then upload categories and products into api ๐
.
+
+**4 main files** from cloned repo take part in this process: ```categories.csv, product-export-template.csv, product-type.json and products.csv``` ๐ซ .
+
+1. In the ```categories.csv``` file you describe the categories you want to import and their hierarchy ๐ฆ.
+
+2. Then, in ```product-export-template.csv``` you describe template for imported products ๐๏ธ, **exactly what info or fields must be in every product description**.
+
+3. ```product-type.json``` describes every non-standard field for product in details ๐ซฃ.
+
+4. And finally ๐ in ```products.csv``` **you describe each of your product, that pattern must follow the** ```product-export-template pattern```.
+
+# **Important thing** ๐จ:
+each product occupies his own line in ```.csv``` file(!) โ ๏ธ. In order to provide photos for products we created **separate repo** ๐ฆฅ to store the photos, and in your products data we provide only links to the photos ๐ธ.
+
+Also in repo you can find ```SushiSushi menu.xlsx``` file ๐ฃ. It contains exactly the same data as ```products.csv``` file, BUT it's much easier ๐ฅต to edit data in ```xlsx``` file, then in ```csv``` file ๐.
+
+Everything in this file is already set upped for comfort editing of products ๐. If you want to edit products data: feel free to do it โ๏ธ. You can change any data you want, just **make sure** it is suited for ```csv``` file ๐.
+
+# How it works ๐ค:
+Each cell in product row is concatenated and separated with comma ๐ฒ. Then this concatenated rows are concatenated into one line in one cell at the bottom of the file ๐คฏ.
+
+Just copy paste the cell content into ```products.csv``` file. Make sure, that after copying the data - you separated each product into own line in ```.csv``` file ๐ฆ, it's **critical point** ๐ฎ.
+
+One more **critical point** - make sure that after ```copy/paste``` you left the ```header``` row at ```products.csv``` file ๐ซก.
+
+That is all you need to know about editing the product data for our project! To know more you should deep dive ๐คฟ into commercetools docs, be observant ๐ญ, lucky ๐ and ready to struggle ๐ซ .
+
+# License ๐
+
+This project is licensed under the MIT License ๐ฅท.
+
+# Acknowledgments ๐
+
+We want to thank [RS-School](https://rs.school/) community ๐ฅฐ.
+And especially our mentor, [Andrej Podlubnyj](https://github.com/andron13) for his help and support โค๏ธ.
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..32c7ccf2
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ SushiSushi - Royal Asia Flavour
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..24a97bc9
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,8858 @@
+{
+ "name": "vite-project",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vite-project",
+ "version": "0.0.0",
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.9.5",
+ "@smastrom/react-rating": "^1.3.2",
+ "@types/react-redux": "^7.1.25",
+ "async-mutex": "^0.4.0",
+ "formik": "^2.4.3",
+ "framer-motion": "^10.16.0",
+ "lint": "^1.1.2",
+ "localforage": "^1.10.0",
+ "match-sorter": "^6.3.1",
+ "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.14.2",
+ "react-star-rate": "^0.2.0",
+ "react-zoom-pan-pinch": "^3.1.0",
+ "sort-by": "^0.0.2",
+ "swiper": "^10.2.0",
+ "yup": "^1.2.0"
+ },
+ "devDependencies": {
+ "@testing-library/dom": "^9.3.1",
+ "@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.15",
+ "@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",
+ "autoprefixer": "^10.4.14",
+ "editorconfig": "^2.0.0",
+ "eslint": "^8.2.0",
+ "eslint-config-airbnb": "^19.0.4",
+ "eslint-config-airbnb-typescript": "^17.1.0",
+ "eslint-config-prettier": "^8.10.0",
+ "eslint-import-resolver-typescript": "^3.5.5",
+ "eslint-plugin-import": "^2.28.0",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
+ "eslint-plugin-prettier": "^5.0.0",
+ "eslint-plugin-react": "^7.28.0",
+ "eslint-plugin-react-hooks": "^4.3.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "eslint-plugin-simple-import-sort": "^10.0.0",
+ "husky": "^8.0.0",
+ "jsdom": "^22.1.0",
+ "lint-staged": "^13.2.3",
+ "postcss": "^8.4.27",
+ "prettier": "^3.0.1",
+ "prettier-plugin-tailwindcss": "^0.4.1",
+ "tailwindcss": "^3.3.3",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5",
+ "vitest": "^0.34.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/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
+ "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz",
+ "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.22.5",
+ "@babel/generator": "^7.22.9",
+ "@babel/helper-compilation-targets": "^7.22.9",
+ "@babel/helper-module-transforms": "^7.22.9",
+ "@babel/helpers": "^7.22.6",
+ "@babel/parser": "^7.22.7",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.8",
+ "@babel/types": "^7.22.5",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.2",
+ "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==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.22.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz",
+ "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.22.5",
+ "@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.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz",
+ "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==",
+ "dev": true,
+ "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"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
+ "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.6",
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
+ "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.22.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
+ "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.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.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
+ "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.11"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime-corejs3": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.6.tgz",
+ "integrity": "sha512-M+37LLIRBTEVjktoJjbw4KVhupF0U/3PYUCbBwgAd9k17hoKhRu1n935QiG7Tuxv0LJOMrb2vuKEeYUlv0iyiw==",
+ "dev": true,
+ "dependencies": {
+ "core-js-pure": "^3.30.2",
+ "regenerator-runtime": "^0.13.11"
+ },
+ "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==",
+ "dev": true,
+ "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.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
+ "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.22.5",
+ "@babel/generator": "^7.22.7",
+ "@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.7",
+ "@babel/types": "^7.22.5",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
+ "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
+ "dev": true,
+ "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/@emotion/is-prop-valid": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+ "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+ "optional": true,
+ "dependencies": {
+ "@emotion/memoize": "0.7.4"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
+ "optional": true
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz",
+ "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz",
+ "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz",
+ "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz",
+ "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz",
+ "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz",
+ "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz",
+ "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz",
+ "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz",
+ "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz",
+ "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz",
+ "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz",
+ "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz",
+ "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz",
+ "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz",
+ "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz",
+ "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz",
+ "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz",
+ "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz",
+ "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz",
+ "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz",
+ "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz",
+ "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==",
+ "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.6.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz",
+ "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==",
+ "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/js": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz",
+ "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==",
+ "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/@jest/expect-utils": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz",
+ "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.4.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz",
+ "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz",
+ "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.0",
+ "@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/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/@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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@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==",
+ "dev": true,
+ "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.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+ "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "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/@one-ini/wasm": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+ "dev": true
+ },
+ "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.7.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
+ "integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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/@smastrom/react-rating": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@smastrom/react-rating/-/react-rating-1.3.2.tgz",
+ "integrity": "sha512-Rp3T7modHgJAnAwZxM5VQnkLPBp71/S2vNmp/eP7rVcZWQjx1F+4HfycdACAUbZVl8yV6TszyzUYXr2VVLgN6A==",
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "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/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/@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/dom/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/@testing-library/dom/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/@testing-library/dom/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/@testing-library/dom/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/@testing-library/dom/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/@testing-library/dom/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/@testing-library/dom/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/@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/jest-dom/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/@testing-library/jest-dom/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/@testing-library/jest-dom/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/@testing-library/jest-dom/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/@testing-library/jest-dom/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/@testing-library/jest-dom/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/@testing-library/jest-dom/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/@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/chai": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz",
+ "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
+ "dev": true
+ },
+ "node_modules/@types/chai-subset": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz",
+ "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "*"
+ }
+ },
+ "node_modules/@types/eslint": {
+ "version": "8.44.2",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz",
+ "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "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==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz",
+ "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.0.0",
+ "pretty-format": "^29.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/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.4.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.6.tgz",
+ "integrity": "sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==",
+ "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.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz",
+ "integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==",
+ "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==",
+ "devOptional": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-redux": {
+ "version": "7.1.25",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
+ "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
+ "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.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
+ "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/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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.1.tgz",
+ "integrity": "sha512-iZVM/ALid9kO0+I81pnp1xmYiFyqibAHzrqX4q5YvvVEyJqY+e6rfTXSCsc2jUxGNqJqTfFSSij/NFkZBiBzLw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.5.1",
+ "@typescript-eslint/scope-manager": "6.2.1",
+ "@typescript-eslint/type-utils": "6.2.1",
+ "@typescript-eslint/utils": "6.2.1",
+ "@typescript-eslint/visitor-keys": "6.2.1",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "natural-compare-lite": "^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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.2.1.tgz",
+ "integrity": "sha512-Ld+uL1kYFU8e6btqBFpsHkwQ35rw30IWpdQxgOqOh4NfxSDH6uCkah1ks8R/RgQqI5hHPXMaLy9fbFseIe+dIg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "6.2.1",
+ "@typescript-eslint/types": "6.2.1",
+ "@typescript-eslint/typescript-estree": "6.2.1",
+ "@typescript-eslint/visitor-keys": "6.2.1",
+ "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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.1.tgz",
+ "integrity": "sha512-UCqBF9WFqv64xNsIEPfBtenbfodPXsJ3nPAr55mGPkQIkiQvgoWNo+astj9ZUfJfVKiYgAZDMnM6dIpsxUMp3Q==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.2.1",
+ "@typescript-eslint/visitor-keys": "6.2.1"
+ },
+ "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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.2.1.tgz",
+ "integrity": "sha512-fTfCgomBMIgu2Dh2Or3gMYgoNAnQm3RLtRp+jP7A8fY+LJ2+9PNpi5p6QB5C4RSP+U3cjI0vDlI3mspAkpPVbQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.2.1",
+ "@typescript-eslint/utils": "6.2.1",
+ "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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.1.tgz",
+ "integrity": "sha512-528bGcoelrpw+sETlyM91k51Arl2ajbNT9L4JwoXE2dvRe1yd8Q64E4OL7vHYw31mlnVsf+BeeLyAZUEQtqahQ==",
+ "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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.1.tgz",
+ "integrity": "sha512-G+UJeQx9AKBHRQBpmvr8T/3K5bJa485eu+4tQBxFq0KoT22+jJyzo1B50JDT9QdC1DEmWQfdKsa8ybiNWYsi0Q==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.2.1",
+ "@typescript-eslint/visitor-keys": "6.2.1",
+ "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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.2.1.tgz",
+ "integrity": "sha512-eBIXQeupYmxVB6S7x+B9SdBeB6qIdXKjgQBge2J+Ouv8h9Cxm5dHf/gfAZA6dkMaag+03HdbVInuXMmqFB/lKQ==",
+ "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.2.1",
+ "@typescript-eslint/types": "6.2.1",
+ "@typescript-eslint/typescript-estree": "6.2.1",
+ "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.2.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.1.tgz",
+ "integrity": "sha512-iTN6w3k2JEZ7cyVdZJTVJx2Lv7t6zFA8DCrJEHD2mwfc16AEvvBWVhbFh34XyG2NORCd0viIgQY1+u7kPI0WpA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.2.1",
+ "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/@vitest/expect": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.1.tgz",
+ "integrity": "sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "0.34.1",
+ "@vitest/utils": "0.34.1",
+ "chai": "^4.3.7"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.1.tgz",
+ "integrity": "sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "0.34.1",
+ "p-limit": "^4.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.1.tgz",
+ "integrity": "sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ==",
+ "dev": true,
+ "dependencies": {
+ "magic-string": "^0.30.1",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.5.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.1.tgz",
+ "integrity": "sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ==",
+ "dev": true,
+ "dependencies": {
+ "tinyspy": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz",
+ "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==",
+ "dev": true,
+ "dependencies": {
+ "diff-sequences": "^29.4.3",
+ "loupe": "^2.3.6",
+ "pretty-format": "^29.5.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "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-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/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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-escapes/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/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true
+ },
+ "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/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/aria-query": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
+ "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.10.2",
+ "@babel/runtime-corejs3": "^7.10.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "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-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "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/array.prototype.findlastindex": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz",
+ "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+ "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz",
+ "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "get-intrinsic": "^1.2.1",
+ "is-array-buffer": "^3.0.2",
+ "is-shared-array-buffer": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
+ "dev": true
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async-mutex": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
+ "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "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/autoprefixer": {
+ "version": "10.4.14",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
+ "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.21.5",
+ "caniuse-lite": "^1.0.30001464",
+ "fraction.js": "^4.2.0",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "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/axe-core": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz",
+ "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
+ "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
+ "dev": true
+ },
+ "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/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==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.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/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/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001519",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz",
+ "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chai": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
+ "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
+ "dev": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.2",
+ "deep-eql": "^4.1.2",
+ "get-func-name": "^2.0.0",
+ "loupe": "^2.3.1",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "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": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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/confusing-browser-globals": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
+ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
+ "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==",
+ "dev": true
+ },
+ "node_modules/core-js-pure": {
+ "version": "3.32.0",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.0.tgz",
+ "integrity": "sha512-qsev1H+dTNYpDUEURRuOXMvpdtAnNEvQWS/FMJ2Vb5AY8ZP4rAPQldkE27joykZPJTe0+IVgHZYh1P5Xu1/i1g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "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/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz",
+ "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==",
+ "dev": true,
+ "dependencies": {
+ "rrweb-cssom": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true
+ },
+ "node_modules/data-urls": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
+ "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
+ "dev": true,
+ "dependencies": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^12.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "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/deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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==",
+ "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/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/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.4.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz",
+ "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==",
+ "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/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
+ "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/editorconfig": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-2.0.0.tgz",
+ "integrity": "sha512-s1NQ63WQ7RNXH6Efb2cwuyRlfpbtdZubvfNe4vCuoyGPewNPY7vah8JUSOFBiJ+jr99Qh8t0xKv0oITc1dclgw==",
+ "dev": true,
+ "dependencies": {
+ "@one-ini/wasm": "0.1.1",
+ "commander": "^11.0.0",
+ "minimatch": "9.0.2",
+ "semver": "^7.5.3"
+ },
+ "bin": {
+ "editorconfig": "bin/editorconfig"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/editorconfig/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/editorconfig/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/editorconfig/node_modules/minimatch": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz",
+ "integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.482",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.482.tgz",
+ "integrity": "sha512-h+UqpfmEr1Qkk0zp7ej/jid7CXoq4m4QzW6wNTb0ELJ/BZCpA4wgUylBIMGCe621tnr4l5VmoHjdoSx2lbnNJA==",
+ "dev": true
+ },
+ "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/enhanced-resolve": {
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/enquirer": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
+ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-colors": "^4.1.1",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz",
+ "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "arraybuffer.prototype.slice": "^1.0.1",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.1",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.0",
+ "safe-array-concat": "^1.0.0",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-buffer": "^1.0.0",
+ "typed-array-byte-length": "^1.0.0",
+ "typed-array-byte-offset": "^1.0.0",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz",
+ "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.17",
+ "@esbuild/android-arm64": "0.18.17",
+ "@esbuild/android-x64": "0.18.17",
+ "@esbuild/darwin-arm64": "0.18.17",
+ "@esbuild/darwin-x64": "0.18.17",
+ "@esbuild/freebsd-arm64": "0.18.17",
+ "@esbuild/freebsd-x64": "0.18.17",
+ "@esbuild/linux-arm": "0.18.17",
+ "@esbuild/linux-arm64": "0.18.17",
+ "@esbuild/linux-ia32": "0.18.17",
+ "@esbuild/linux-loong64": "0.18.17",
+ "@esbuild/linux-mips64el": "0.18.17",
+ "@esbuild/linux-ppc64": "0.18.17",
+ "@esbuild/linux-riscv64": "0.18.17",
+ "@esbuild/linux-s390x": "0.18.17",
+ "@esbuild/linux-x64": "0.18.17",
+ "@esbuild/netbsd-x64": "0.18.17",
+ "@esbuild/openbsd-x64": "0.18.17",
+ "@esbuild/sunos-x64": "0.18.17",
+ "@esbuild/win32-arm64": "0.18.17",
+ "@esbuild/win32-ia32": "0.18.17",
+ "@esbuild/win32-x64": "0.18.17"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz",
+ "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.2",
+ "@eslint/js": "^8.47.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-airbnb": {
+ "version": "19.0.4",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz",
+ "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==",
+ "dev": true,
+ "dependencies": {
+ "eslint-config-airbnb-base": "^15.0.0",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5"
+ },
+ "engines": {
+ "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^7.32.0 || ^8.2.0",
+ "eslint-plugin-import": "^2.25.3",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
+ "eslint-plugin-react": "^7.28.0",
+ "eslint-plugin-react-hooks": "^4.3.0"
+ }
+ },
+ "node_modules/eslint-config-airbnb-base": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
+ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
+ "dev": true,
+ "dependencies": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^7.32.0 || ^8.2.0",
+ "eslint-plugin-import": "^2.25.2"
+ }
+ },
+ "node_modules/eslint-config-airbnb-base/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/eslint-config-airbnb-typescript": {
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz",
+ "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==",
+ "dev": true,
+ "dependencies": {
+ "eslint-config-airbnb-base": "^15.0.0"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0",
+ "@typescript-eslint/parser": "^5.0.0 || ^6.0.0",
+ "eslint": "^7.32.0 || ^8.2.0",
+ "eslint-plugin-import": "^2.25.3"
+ }
+ },
+ "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-import-resolver-node": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
+ "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.11.0",
+ "resolve": "^1.22.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript": {
+ "version": "3.5.5",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz",
+ "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4",
+ "enhanced-resolve": "^5.12.0",
+ "eslint-module-utils": "^2.7.4",
+ "get-tsconfig": "^4.5.0",
+ "globby": "^13.1.3",
+ "is-core-module": "^2.11.0",
+ "is-glob": "^4.0.3",
+ "synckit": "^0.8.5"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
+ },
+ "peerDependencies": {
+ "eslint": "*",
+ "eslint-plugin-import": "*"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript/node_modules/globby": {
+ "version": "13.2.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
+ "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
+ "dev": true,
+ "dependencies": {
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.3.0",
+ "ignore": "^5.2.4",
+ "merge2": "^1.4.1",
+ "slash": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript/node_modules/slash": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
+ "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
+ "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.28.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz",
+ "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.findlastindex": "^1.2.2",
+ "array.prototype.flat": "^1.3.1",
+ "array.prototype.flatmap": "^1.3.1",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.7",
+ "eslint-module-utils": "^2.8.0",
+ "has": "^1.0.3",
+ "is-core-module": "^2.12.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.6",
+ "object.groupby": "^1.0.0",
+ "object.values": "^1.1.6",
+ "resolve": "^1.22.3",
+ "semver": "^6.3.1",
+ "tsconfig-paths": "^3.14.2"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/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/eslint-plugin-jsx-a11y": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz",
+ "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.16.3",
+ "aria-query": "^4.2.2",
+ "array-includes": "^3.1.4",
+ "ast-types-flow": "^0.0.7",
+ "axe-core": "^4.3.5",
+ "axobject-query": "^2.2.0",
+ "damerau-levenshtein": "^1.0.7",
+ "emoji-regex": "^9.2.2",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^3.2.1",
+ "language-tags": "^1.0.5",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+ }
+ },
+ "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": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz",
+ "integrity": "sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.4",
+ "array.prototype.flatmap": "^1.2.5",
+ "doctrine": "^2.1.0",
+ "estraverse": "^5.3.0",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.0.4",
+ "object.entries": "^1.1.5",
+ "object.fromentries": "^2.0.5",
+ "object.hasown": "^1.1.0",
+ "object.values": "^1.1.5",
+ "prop-types": "^15.7.2",
+ "resolve": "^2.0.0-next.3",
+ "semver": "^6.3.0",
+ "string.prototype.matchall": "^4.0.6"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz",
+ "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==",
+ "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-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.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/eslint-plugin-react/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/eslint-plugin-simple-import-sort": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz",
+ "integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=5.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.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/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/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/expect": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz",
+ "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/expect-utils": "^29.6.2",
+ "@types/node": "*",
+ "jest-get-type": "^29.4.3",
+ "jest-matcher-utils": "^29.6.2",
+ "jest-message-util": "^29.6.2",
+ "jest-util": "^29.6.2"
+ },
+ "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/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.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=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==",
+ "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/fraction.js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
+ "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/infusion"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "10.16.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.0.tgz",
+ "integrity": "sha512-R+88Mkr/1dr7XHjacwptfJyrywRzQ1HZX3YSZtN4tFMBq1O8GGCbDEv31Nf/H08o0hUXLC87GkxsR/1bZgwXfw==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "optionalDependencies": {
+ "@emotion/is-prop-valid": "^0.8.2"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "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/function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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-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/get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz",
+ "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==",
+ "dev": true,
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/goober": {
+ "version": "2.1.13",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz",
+ "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
+ "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/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": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/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": "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/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/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/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "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/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/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-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.12.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
+ "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
+ "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-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-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "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": "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/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-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.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/jest-diff": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz",
+ "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.4.3",
+ "jest-get-type": "^29.4.3",
+ "pretty-format": "^29.6.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-diff/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/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/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/jest-diff/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/jest-diff/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/jest-diff/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/jest-get-type": {
+ "version": "29.4.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz",
+ "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz",
+ "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.6.2",
+ "jest-get-type": "^29.4.3",
+ "pretty-format": "^29.6.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/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/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/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/jest-matcher-utils/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/jest-matcher-utils/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/jest-matcher-utils/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/jest-message-util": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz",
+ "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.1",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.6.2",
+ "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/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/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/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-util": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz",
+ "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.1",
+ "@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/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/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-util/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/jest-util/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/jest-util/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/jest-util/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/jiti": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
+ "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
+ "dev": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
+ "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==",
+ "dev": true,
+ "dependencies": {
+ "abab": "^2.0.6",
+ "cssstyle": "^3.0.0",
+ "data-urls": "^4.0.0",
+ "decimal.js": "^10.4.3",
+ "domexception": "^4.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.4",
+ "parse5": "^7.1.2",
+ "rrweb-cssom": "^0.6.0",
+ "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": "^12.0.1",
+ "ws": "^8.13.0",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "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==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonc-parser": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
+ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
+ "dev": true
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.22",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
+ "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==",
+ "dev": true
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.8.tgz",
+ "integrity": "sha512-aWAZwgPLS8hJ20lNPm9HNVs4inexz6S2sQa3wx/+ycuutMNE5/IfYxiWYBbi+9UWCQVaXYCOPUl6gFrPR7+jGg==",
+ "dev": true,
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/lint": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/lint/-/lint-1.1.2.tgz",
+ "integrity": "sha512-kOzKdN0iSdkx57102MxZd64yn32rHaB6xqBMN3hyCRiBV2xiurM75XWvTHWNK/SJBCddHPhjDLV+kNr3Uf1MGg==",
+ "bin": {
+ "node-lint": "bin/node-lint"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/lint-staged": {
+ "version": "13.2.3",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz",
+ "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "5.2.0",
+ "cli-truncate": "^3.1.0",
+ "commander": "^10.0.0",
+ "debug": "^4.3.4",
+ "execa": "^7.0.0",
+ "lilconfig": "2.1.0",
+ "listr2": "^5.0.7",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-inspect": "^1.12.3",
+ "pidtree": "^0.6.0",
+ "string-argv": "^0.3.1",
+ "yaml": "^2.2.2"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/chalk": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
+ "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz",
+ "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==",
+ "dev": true,
+ "dependencies": {
+ "cli-truncate": "^2.1.0",
+ "colorette": "^2.0.19",
+ "log-update": "^4.0.0",
+ "p-map": "^4.0.0",
+ "rfdc": "^1.3.0",
+ "rxjs": "^7.8.0",
+ "through": "^2.3.8",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "enquirer": ">= 2.3.0 < 3"
+ },
+ "peerDependenciesMeta": {
+ "enquirer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/listr2/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/listr2/node_modules/cli-truncate": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+ "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+ "dev": true,
+ "dependencies": {
+ "slice-ansi": "^3.0.0",
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/listr2/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/listr2/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/listr2/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/listr2/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/listr2/node_modules/slice-ansi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+ "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/listr2/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/local-pkg": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz",
+ "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/localforage": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+ "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+ "dependencies": {
+ "lie": "3.1.1"
+ }
+ },
+ "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=="
+ },
+ "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=="
+ },
+ "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/log-update": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
+ "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-escapes": "^4.3.0",
+ "cli-cursor": "^3.1.0",
+ "slice-ansi": "^4.0.0",
+ "wrap-ansi": "^6.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/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/log-update/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/log-update/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/log-update/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/log-update/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/log-update/node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/log-update/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/log-update/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/loupe": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
+ "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.0"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/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.30.2",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
+ "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/match-sorter": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
+ "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "remove-accents": "0.4.2"
+ }
+ },
+ "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": "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/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/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz",
+ "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "pathe": "^1.1.1",
+ "pkg-types": "^1.0.3",
+ "ufo": "^1.1.2"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "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/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "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==",
+ "dev": true
+ },
+ "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/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/npm-run-path/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/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-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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/object.entries": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
+ "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
+ "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz",
+ "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.21.2",
+ "get-intrinsic": "^1.2.1"
+ }
+ },
+ "node_modules/object.hasown": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
+ "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "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": "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/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": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "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-locate/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/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"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "dev": true,
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
+ "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==",
+ "dev": true
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "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/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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-types": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
+ "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
+ "dev": true,
+ "dependencies": {
+ "jsonc-parser": "^3.2.0",
+ "mlly": "^1.2.0",
+ "pathe": "^1.1.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.27",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
+ "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
+ "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/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz",
+ "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==",
+ "dev": true,
+ "dependencies": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^2.1.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "dev": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.13",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz",
+ "integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==",
+ "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/prettier-plugin-tailwindcss": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.4.1.tgz",
+ "integrity": "sha512-hwn2EiJmv8M+AW4YDkbjJ6HlZCTzLyz1QlySn9sMuKV/Px0fjwldlB7tol8GzdgqtkdPtzT3iJ4UzdnYXP25Ag==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.17.0"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@shufo/prettier-plugin-blade": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "prettier": "^2.2 || ^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*",
+ "prettier-plugin-twig-melody": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@shufo/prettier-plugin-blade": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ },
+ "prettier-plugin-twig-melody": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.6.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz",
+ "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.0",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.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": "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/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/property-expr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
+ "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
+ },
+ "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/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=="
+ },
+ "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.14.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
+ "integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
+ "dependencies": {
+ "@remix-run/router": "1.7.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
+ "integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
+ "dependencies": {
+ "@remix-run/router": "1.7.2",
+ "react-router": "6.14.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-star-rate": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/react-star-rate/-/react-star-rate-0.2.0.tgz",
+ "integrity": "sha512-t5LNriqbQH8gcRdWolSkDc+Iox68UKyS3olbKDKXcg07PRmYxF96Q0sDot1M2hmDDv9iR0Cm74DMbAGvLTMdag==",
+ "dependencies": {
+ "goober": "^2.0.37"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16"
+ }
+ },
+ "node_modules/react-zoom-pan-pinch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.1.0.tgz",
+ "integrity": "sha512-a3LlP8QPgTikvteCNkZ3X6wIWC0lrg1geP5WkUJyx2MXXAhHQek3r17N1nT/esOiWGuPIECnsd9AGoK8jOeGcg==",
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "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.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ },
+ "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/remove-accents": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
+ "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.3.tgz",
+ "integrity": "sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.12.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/restore-cursor/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/restore-cursor/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/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.27.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.0.tgz",
+ "integrity": "sha512-aOltLCrYZ0FhJDm7fCqwTjIUEVjWjcydKBV/Zeid6Mn8BWgDCUBBWT5beM5ieForYNo/1ZHuGJdka26kvQ3Gzg==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
+ "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==",
+ "dev": true
+ },
+ "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-applescript/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/run-applescript/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/run-applescript/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/run-applescript/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/run-applescript/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/run-applescript/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/run-applescript/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/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/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz",
+ "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "has-symbols": "^1.0.3",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/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/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
+ "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/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/sort-by": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/sort-by/-/sort-by-0.0.2.tgz",
+ "integrity": "sha512-iOX5oHA4a0eqTMFiWrHYqv924UeRKFBLhym7iwSVG37Egg2wApgZKAjyzM9WZjMwKv6+8Zi+nIaJ7FYsO9EkoA=="
+ },
+ "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/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/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
+ "node_modules/std-env": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz",
+ "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==",
+ "dev": true
+ },
+ "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/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-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/string.prototype.matchall": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
+ "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+ "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/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/strip-literal": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz",
+ "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
+ "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "7.1.6",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/tailwindcss": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
+ "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
+ "dev": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.12",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.18.2",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true
+ },
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
+ "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=="
+ },
+ "node_modules/tinybench": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz",
+ "integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==",
+ "dev": true
+ },
+ "node_modules/tinypool": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz",
+ "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz",
+ "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "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/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==",
+ "dev": true,
+ "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/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
+ "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": "4.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
+ "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz",
+ "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.13.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
+ "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
+ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
+ },
+ "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.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
+ "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
+ "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
+ "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "has-proto": "^1.0.1",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
+ "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.2.0.tgz",
+ "integrity": "sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==",
+ "dev": true
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.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/vite": {
+ "version": "4.4.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.8.tgz",
+ "integrity": "sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.18.10",
+ "postcss": "^8.4.26",
+ "rollup": "^3.25.2"
+ },
+ "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-node": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.1.tgz",
+ "integrity": "sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==",
+ "dev": true,
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "mlly": "^1.4.0",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": ">=v14.18.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "0.34.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.1.tgz",
+ "integrity": "sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "^4.3.5",
+ "@types/chai-subset": "^1.3.3",
+ "@types/node": "*",
+ "@vitest/expect": "0.34.1",
+ "@vitest/runner": "0.34.1",
+ "@vitest/snapshot": "0.34.1",
+ "@vitest/spy": "0.34.1",
+ "@vitest/utils": "0.34.1",
+ "acorn": "^8.9.0",
+ "acorn-walk": "^8.2.0",
+ "cac": "^6.7.14",
+ "chai": "^4.3.7",
+ "debug": "^4.3.4",
+ "local-pkg": "^0.4.3",
+ "magic-string": "^0.30.1",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.3.3",
+ "strip-literal": "^1.0.1",
+ "tinybench": "^2.5.0",
+ "tinypool": "^0.7.0",
+ "vite": "^3.0.0 || ^4.0.0",
+ "vite-node": "0.34.1",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": ">=v14.18.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@vitest/browser": "*",
+ "@vitest/ui": "*",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "playwright": "*",
+ "safaridriver": "*",
+ "webdriverio": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "playwright": {
+ "optional": true
+ },
+ "safaridriver": {
+ "optional": true
+ },
+ "webdriverio": {
+ "optional": 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/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": "12.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz",
+ "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^4.1.1",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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/why-is-node-running": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
+ "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/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/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/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yaml": {
+ "version": "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/yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yup": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz",
+ "integrity": "sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ },
+ "node_modules/yup/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..08545ce5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "vite-project",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host",
+ "build": "tsc && vite build",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview",
+ "prepare": "husky install",
+ "test": "vitest",
+ "format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
+ "type-check": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.9.5",
+ "@smastrom/react-rating": "^1.3.2",
+ "@types/react-redux": "^7.1.25",
+ "async-mutex": "^0.4.0",
+ "formik": "^2.4.3",
+ "framer-motion": "^10.16.0",
+ "lint": "^1.1.2",
+ "localforage": "^1.10.0",
+ "match-sorter": "^6.3.1",
+ "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.14.2",
+ "react-star-rate": "^0.2.0",
+ "react-zoom-pan-pinch": "^3.1.0",
+ "sort-by": "^0.0.2",
+ "swiper": "^10.2.0",
+ "yup": "^1.2.0"
+ },
+ "devDependencies": {
+ "@testing-library/dom": "^9.3.1",
+ "@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.15",
+ "@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",
+ "autoprefixer": "^10.4.14",
+ "editorconfig": "^2.0.0",
+ "eslint": "^8.2.0",
+ "eslint-config-airbnb": "^19.0.4",
+ "eslint-config-airbnb-typescript": "^17.1.0",
+ "eslint-config-prettier": "^8.10.0",
+ "eslint-import-resolver-typescript": "^3.5.5",
+ "eslint-plugin-import": "^2.28.0",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
+ "eslint-plugin-prettier": "^5.0.0",
+ "eslint-plugin-react": "^7.28.0",
+ "eslint-plugin-react-hooks": "^4.3.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "eslint-plugin-simple-import-sort": "^10.0.0",
+ "husky": "^8.0.0",
+ "jsdom": "^22.1.0",
+ "lint-staged": "^13.2.3",
+ "postcss": "^8.4.27",
+ "prettier": "^3.0.1",
+ "prettier-plugin-tailwindcss": "^0.4.1",
+ "tailwindcss": "^3.3.3",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5",
+ "vitest": "^0.34.1"
+ },
+ "lint-staged": {
+ "./src/**/*.{tsx, ts}": "eslint",
+ "./src/**/*.{tsx, ts, html, css}": "prettier --write"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 00000000..2e7af2b7
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/src/app/App.tsx b/src/app/App.tsx
new file mode 100644
index 00000000..f7f79698
--- /dev/null
+++ b/src/app/App.tsx
@@ -0,0 +1,109 @@
+import { useEffect } from 'react';
+
+import { HashRouter, useLocation } from 'react-router-dom';
+
+import LocationProvider from './utils/LocationProvider.tsx';
+import RoutesWithAnimation from './utils/RoutesWithAnimation.tsx';
+import { COOKIE_ACCESS_TOKEN, userSlice } from '../entities/user';
+import { COOKIE_REFRESH_TOKEN, COOKIE_USER_ID } from '../entities/user/consts/constants.ts';
+import Cart from '../pages/Cart/Cart.tsx';
+import NavBlock from '../pages/NavBlock/NavBlock';
+import { getCookie } from '../shared/lib/helpers';
+import { useAppDispatch } from '../shared/lib/hooks';
+import Header from '../widgets/Header/Header';
+
+if (!localStorage.sushiDefThemeUsage) {
+ localStorage.sushiDefThemeUsage = 'true';
+}
+if (
+ localStorage.sushiTheme === 'dark' ||
+ (!('sushiTheme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
+) {
+ document.documentElement.classList.add('dark');
+ document.body.classList.add('dark:bg-dark-bg-primary');
+} else {
+ document.body.classList.remove('dark:bg-dark-bg-primary');
+ document.documentElement.classList.remove('dark');
+}
+
+export function App() {
+ const dispatch = useAppDispatch();
+ const { loggedIn } = userSlice.actions;
+ const location = useLocation();
+ const isCartToRender = location.pathname.includes('/categories') || location.pathname.includes('/product');
+
+ useEffect(() => {
+ const [token, userId, refreshToken] = getCookie(COOKIE_ACCESS_TOKEN, COOKIE_USER_ID, COOKIE_REFRESH_TOKEN);
+
+ if (token && userId && refreshToken) {
+ dispatch(loggedIn({ accessToken: token, userId, refreshToken, cartId: '' }));
+ }
+ }, [dispatch, loggedIn]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {isCartToRender ? (
+
+
+
+ ) : null}
+
+
+ );
+}
+
+export function WrappedApp() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/store/index.ts b/src/app/store/index.ts
new file mode 100644
index 00000000..cc9949f1
--- /dev/null
+++ b/src/app/store/index.ts
@@ -0,0 +1,2 @@
+export type { AppDispatch, AppStore, RootState } from './store.ts';
+export { setupStore } from './store.ts';
diff --git a/src/app/store/store.ts b/src/app/store/store.ts
new file mode 100644
index 00000000..7d9a10b3
--- /dev/null
+++ b/src/app/store/store.ts
@@ -0,0 +1,23 @@
+import { combineReducers, configureStore } from '@reduxjs/toolkit';
+
+import { userReducer } from '../../entities/user';
+import { rootApi, rootAuthApi } from '../../shared/api';
+
+export type RootState = ReturnType;
+export type AppStore = ReturnType;
+export type AppDispatch = AppStore['dispatch'];
+
+const rootReducer = combineReducers({
+ [rootApi.reducerPath]: rootApi.reducer,
+ [rootAuthApi.reducerPath]: rootAuthApi.reducer,
+ userReducer,
+});
+
+export const setupStore = (preloadedState?: Partial) =>
+ configureStore({
+ preloadedState,
+ reducer: rootReducer,
+ middleware: (getDefaultMiddleware) => {
+ return getDefaultMiddleware().concat(rootApi.middleware).concat(rootAuthApi.middleware);
+ },
+ });
diff --git a/src/app/utils/LocationProvider.tsx b/src/app/utils/LocationProvider.tsx
new file mode 100644
index 00000000..7fd9c7ed
--- /dev/null
+++ b/src/app/utils/LocationProvider.tsx
@@ -0,0 +1,7 @@
+import { ReactNode } from 'react';
+
+import { AnimatePresence } from 'framer-motion';
+
+export default function LocationProvider({ children }: { children: ReactNode }) {
+ return {children} ;
+}
diff --git a/src/app/utils/RoutesWithAnimation.tsx b/src/app/utils/RoutesWithAnimation.tsx
new file mode 100644
index 00000000..0668116c
--- /dev/null
+++ b/src/app/utils/RoutesWithAnimation.tsx
@@ -0,0 +1,57 @@
+import { Route, Routes, useLocation } from 'react-router-dom';
+
+import AboutUs from '../../pages/AboutUs/AboutUs.tsx';
+import Cart from '../../pages/Cart/Cart.tsx';
+import ErrorPage from '../../pages/ErrorPage/ErrorPage';
+import LoginPage from '../../pages/LoginPage/LoginPage';
+import ProductCatalogue from '../../pages/ProductCatalog/ProductCatalogue.tsx';
+import ProductPage from '../../pages/ProductPage/ProductPage.tsx';
+import ProfilePage from '../../pages/ProfilePage/ProfilePage';
+import RegPage from '../../pages/RegPage/RegPage';
+import { LoginNotRequired, LoginRequired, NavigateToCategories } from '../../shared/ui';
+
+export default function RoutesWithAnimation() {
+ const location = useLocation();
+ return (
+
+
+
+
+ }
+ />
+
+ } />
+ } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/src/assets/icons/AccentGulp.svg b/src/assets/icons/AccentGulp.svg
new file mode 100644
index 00000000..53e338b5
--- /dev/null
+++ b/src/assets/icons/AccentGulp.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/assets/icons/ArrowDown.svg b/src/assets/icons/ArrowDown.svg
new file mode 100644
index 00000000..eec6249c
--- /dev/null
+++ b/src/assets/icons/ArrowDown.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/CalendarIcon.svg b/src/assets/icons/CalendarIcon.svg
new file mode 100644
index 00000000..fae9162a
--- /dev/null
+++ b/src/assets/icons/CalendarIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/CalendarIconRed.svg b/src/assets/icons/CalendarIconRed.svg
new file mode 100644
index 00000000..3292e370
--- /dev/null
+++ b/src/assets/icons/CalendarIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/CityIcon.svg b/src/assets/icons/CityIcon.svg
new file mode 100644
index 00000000..98c7c75a
--- /dev/null
+++ b/src/assets/icons/CityIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/CityIconRed.svg b/src/assets/icons/CityIconRed.svg
new file mode 100644
index 00000000..9a8b32e0
--- /dev/null
+++ b/src/assets/icons/CityIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/CountryIcon.svg b/src/assets/icons/CountryIcon.svg
new file mode 100644
index 00000000..5b60d757
--- /dev/null
+++ b/src/assets/icons/CountryIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/CountryIconRed.svg b/src/assets/icons/CountryIconRed.svg
new file mode 100644
index 00000000..39f4524e
--- /dev/null
+++ b/src/assets/icons/CountryIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/FiltersIcon.svg b/src/assets/icons/FiltersIcon.svg
new file mode 100644
index 00000000..7253e578
--- /dev/null
+++ b/src/assets/icons/FiltersIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/LockIcon.svg b/src/assets/icons/LockIcon.svg
new file mode 100644
index 00000000..9589cfb3
--- /dev/null
+++ b/src/assets/icons/LockIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/LockIconRed.svg b/src/assets/icons/LockIconRed.svg
new file mode 100644
index 00000000..2e29bcb2
--- /dev/null
+++ b/src/assets/icons/LockIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/PrimaryGulp.tsx b/src/assets/icons/PrimaryGulp.tsx
new file mode 100644
index 00000000..03b7a068
--- /dev/null
+++ b/src/assets/icons/PrimaryGulp.tsx
@@ -0,0 +1,22 @@
+import { motion } from 'framer-motion';
+
+export default function PrimaryGulp() {
+ return (
+
+
+
+ );
+}
diff --git a/src/assets/icons/RedCross.svg b/src/assets/icons/RedCross.svg
new file mode 100644
index 00000000..0dc46037
--- /dev/null
+++ b/src/assets/icons/RedCross.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/StreetIcon.svg b/src/assets/icons/StreetIcon.svg
new file mode 100644
index 00000000..c7d4ad0b
--- /dev/null
+++ b/src/assets/icons/StreetIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/StreetIconRed.svg b/src/assets/icons/StreetIconRed.svg
new file mode 100644
index 00000000..3576aaa1
--- /dev/null
+++ b/src/assets/icons/StreetIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/UserIcon.svg b/src/assets/icons/UserIcon.svg
new file mode 100644
index 00000000..0bde6f63
--- /dev/null
+++ b/src/assets/icons/UserIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/UserIconRed.svg b/src/assets/icons/UserIconRed.svg
new file mode 100644
index 00000000..d4748bed
--- /dev/null
+++ b/src/assets/icons/UserIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/arrowLeft.svg b/src/assets/icons/arrowLeft.svg
new file mode 100644
index 00000000..47988582
--- /dev/null
+++ b/src/assets/icons/arrowLeft.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/arrowLeftBlack.svg b/src/assets/icons/arrowLeftBlack.svg
new file mode 100644
index 00000000..2a74ebb7
--- /dev/null
+++ b/src/assets/icons/arrowLeftBlack.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/arrowRight.svg b/src/assets/icons/arrowRight.svg
new file mode 100644
index 00000000..b94e2060
--- /dev/null
+++ b/src/assets/icons/arrowRight.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/cart.svg b/src/assets/icons/cart.svg
new file mode 100644
index 00000000..e0a4bfaf
--- /dev/null
+++ b/src/assets/icons/cart.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/assets/icons/check-icon.svg b/src/assets/icons/check-icon.svg
new file mode 100644
index 00000000..68ffa16c
--- /dev/null
+++ b/src/assets/icons/check-icon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/credit-card.svg b/src/assets/icons/credit-card.svg
new file mode 100644
index 00000000..e4b194ee
--- /dev/null
+++ b/src/assets/icons/credit-card.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/assets/icons/cross.svg b/src/assets/icons/cross.svg
new file mode 100644
index 00000000..4e6652fc
--- /dev/null
+++ b/src/assets/icons/cross.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/assets/icons/delivery.svg b/src/assets/icons/delivery.svg
new file mode 100644
index 00000000..26af06e4
--- /dev/null
+++ b/src/assets/icons/delivery.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/emailIcon.svg b/src/assets/icons/emailIcon.svg
new file mode 100644
index 00000000..6cda6b80
--- /dev/null
+++ b/src/assets/icons/emailIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/emailIconRed.svg b/src/assets/icons/emailIconRed.svg
new file mode 100644
index 00000000..3574812a
--- /dev/null
+++ b/src/assets/icons/emailIconRed.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/empty-cart-icon.svg b/src/assets/icons/empty-cart-icon.svg
new file mode 100644
index 00000000..92dafc74
--- /dev/null
+++ b/src/assets/icons/empty-cart-icon.svg
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/icons/empty-cart.svg b/src/assets/icons/empty-cart.svg
new file mode 100644
index 00000000..f0bfc523
--- /dev/null
+++ b/src/assets/icons/empty-cart.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/emptyStar.svg b/src/assets/icons/emptyStar.svg
new file mode 100644
index 00000000..2cf6388a
--- /dev/null
+++ b/src/assets/icons/emptyStar.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/favicon_sushi.png b/src/assets/icons/favicon_sushi.png
new file mode 100644
index 00000000..4f5ab0ac
Binary files /dev/null and b/src/assets/icons/favicon_sushi.png differ
diff --git a/src/assets/icons/github.svg b/src/assets/icons/github.svg
new file mode 100644
index 00000000..97a33e03
--- /dev/null
+++ b/src/assets/icons/github.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/heart-accent.svg b/src/assets/icons/heart-accent.svg
new file mode 100644
index 00000000..682b9124
--- /dev/null
+++ b/src/assets/icons/heart-accent.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/heart.svg b/src/assets/icons/heart.svg
new file mode 100644
index 00000000..442025e6
--- /dev/null
+++ b/src/assets/icons/heart.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/assets/icons/knife-and-fork.svg b/src/assets/icons/knife-and-fork.svg
new file mode 100644
index 00000000..336bf5f0
--- /dev/null
+++ b/src/assets/icons/knife-and-fork.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/log-out.svg b/src/assets/icons/log-out.svg
new file mode 100644
index 00000000..8b66afaa
--- /dev/null
+++ b/src/assets/icons/log-out.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/assets/icons/logo.svg b/src/assets/icons/logo.svg
new file mode 100644
index 00000000..2d9d9738
--- /dev/null
+++ b/src/assets/icons/logo.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/icons/menu.svg b/src/assets/icons/menu.svg
new file mode 100644
index 00000000..e97f0388
--- /dev/null
+++ b/src/assets/icons/menu.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/assets/icons/minus.svg b/src/assets/icons/minus.svg
new file mode 100644
index 00000000..df8e5ef0
--- /dev/null
+++ b/src/assets/icons/minus.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/moon.svg b/src/assets/icons/moon.svg
new file mode 100644
index 00000000..ed4d0fdd
--- /dev/null
+++ b/src/assets/icons/moon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/pencilIcon.svg b/src/assets/icons/pencilIcon.svg
new file mode 100644
index 00000000..2f0c9a20
--- /dev/null
+++ b/src/assets/icons/pencilIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/pencilIconGreen.svg b/src/assets/icons/pencilIconGreen.svg
new file mode 100644
index 00000000..6d8e3014
--- /dev/null
+++ b/src/assets/icons/pencilIconGreen.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/phone.svg b/src/assets/icons/phone.svg
new file mode 100644
index 00000000..c671ac0a
--- /dev/null
+++ b/src/assets/icons/phone.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/plus.svg b/src/assets/icons/plus.svg
new file mode 100644
index 00000000..d1624013
--- /dev/null
+++ b/src/assets/icons/plus.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/assets/icons/postalCodeIcon.svg b/src/assets/icons/postalCodeIcon.svg
new file mode 100644
index 00000000..7e4a757e
--- /dev/null
+++ b/src/assets/icons/postalCodeIcon.svg
@@ -0,0 +1,7 @@
+
+
+
+
+ Svg Vector Icons : http://www.onlinewebfonts.com/icon
+
+
diff --git a/src/assets/icons/postalCodeIconRed.svg b/src/assets/icons/postalCodeIconRed.svg
new file mode 100644
index 00000000..1ed9953f
--- /dev/null
+++ b/src/assets/icons/postalCodeIconRed.svg
@@ -0,0 +1,7 @@
+
+
+
+
+ Svg Vector Icons : http://www.onlinewebfonts.com/icon
+
+
diff --git a/src/assets/icons/rs_school_logo.svg b/src/assets/icons/rs_school_logo.svg
new file mode 100644
index 00000000..4692e0eb
--- /dev/null
+++ b/src/assets/icons/rs_school_logo.svg
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/icons/search.svg b/src/assets/icons/search.svg
new file mode 100644
index 00000000..e5eb1522
--- /dev/null
+++ b/src/assets/icons/search.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/shopping-cart-accent.svg b/src/assets/icons/shopping-cart-accent.svg
new file mode 100644
index 00000000..c1507b3a
--- /dev/null
+++ b/src/assets/icons/shopping-cart-accent.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/icons/shopping-cart.svg b/src/assets/icons/shopping-cart.svg
new file mode 100644
index 00000000..5ead8e09
--- /dev/null
+++ b/src/assets/icons/shopping-cart.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/assets/icons/sliderRight.svg b/src/assets/icons/sliderRight.svg
new file mode 100644
index 00000000..b94e2060
--- /dev/null
+++ b/src/assets/icons/sliderRight.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/slidetPrev.svg b/src/assets/icons/slidetPrev.svg
new file mode 100644
index 00000000..a14b7129
--- /dev/null
+++ b/src/assets/icons/slidetPrev.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/spicy.svg b/src/assets/icons/spicy.svg
new file mode 100644
index 00000000..c20b9f7d
--- /dev/null
+++ b/src/assets/icons/spicy.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/icons/star.svg b/src/assets/icons/star.svg
new file mode 100644
index 00000000..f0b7aba2
--- /dev/null
+++ b/src/assets/icons/star.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/sun.svg b/src/assets/icons/sun.svg
new file mode 100644
index 00000000..e41a269e
--- /dev/null
+++ b/src/assets/icons/sun.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/icons/vegan.svg b/src/assets/icons/vegan.svg
new file mode 100644
index 00000000..68ff7c20
--- /dev/null
+++ b/src/assets/icons/vegan.svg
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/icons/xIcon.svg b/src/assets/icons/xIcon.svg
new file mode 100644
index 00000000..14eb5630
--- /dev/null
+++ b/src/assets/icons/xIcon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/assets/img/Bogdan.jpg b/src/assets/img/Bogdan.jpg
new file mode 100644
index 00000000..b6e07fe4
Binary files /dev/null and b/src/assets/img/Bogdan.jpg differ
diff --git a/src/assets/img/Harry.jpg b/src/assets/img/Harry.jpg
new file mode 100644
index 00000000..d6eb7c7e
Binary files /dev/null and b/src/assets/img/Harry.jpg differ
diff --git a/src/assets/img/Oleksii.jpg b/src/assets/img/Oleksii.jpg
new file mode 100644
index 00000000..2a901487
Binary files /dev/null and b/src/assets/img/Oleksii.jpg differ
diff --git a/src/assets/img/Sad_Keanu.png b/src/assets/img/Sad_Keanu.png
new file mode 100644
index 00000000..2767b15c
Binary files /dev/null and b/src/assets/img/Sad_Keanu.png differ
diff --git a/src/assets/img/UserImg.svg b/src/assets/img/UserImg.svg
new file mode 100644
index 00000000..25f55653
--- /dev/null
+++ b/src/assets/img/UserImg.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/img/about_bg_desk.svg b/src/assets/img/about_bg_desk.svg
new file mode 100644
index 00000000..245257e9
--- /dev/null
+++ b/src/assets/img/about_bg_desk.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/img/about_bg_mob.svg b/src/assets/img/about_bg_mob.svg
new file mode 100644
index 00000000..e5b71c43
--- /dev/null
+++ b/src/assets/img/about_bg_mob.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/img/cart-img.png b/src/assets/img/cart-img.png
new file mode 100644
index 00000000..68257bd6
Binary files /dev/null and b/src/assets/img/cart-img.png differ
diff --git a/src/assets/img/productImg.png b/src/assets/img/productImg.png
new file mode 100644
index 00000000..126ccaea
Binary files /dev/null and b/src/assets/img/productImg.png differ
diff --git a/src/assets/img/sushi.png b/src/assets/img/sushi.png
new file mode 100644
index 00000000..971917a3
Binary files /dev/null and b/src/assets/img/sushi.png differ
diff --git a/src/assets/img/teammate_pic_1.jpg b/src/assets/img/teammate_pic_1.jpg
new file mode 100644
index 00000000..1c7ee89d
Binary files /dev/null and b/src/assets/img/teammate_pic_1.jpg differ
diff --git a/src/assets/img/teammate_pic_2.jpg b/src/assets/img/teammate_pic_2.jpg
new file mode 100644
index 00000000..2db193dc
Binary files /dev/null and b/src/assets/img/teammate_pic_2.jpg differ
diff --git a/src/assets/img/teammate_pic_3.jpg b/src/assets/img/teammate_pic_3.jpg
new file mode 100644
index 00000000..55dee971
Binary files /dev/null and b/src/assets/img/teammate_pic_3.jpg differ
diff --git a/src/entities/cart/api/readCartApi.ts b/src/entities/cart/api/readCartApi.ts
new file mode 100644
index 00000000..c07e8d92
--- /dev/null
+++ b/src/entities/cart/api/readCartApi.ts
@@ -0,0 +1,32 @@
+import { rootApi } from '../../../shared/api';
+import { CartResponse } from '../../../shared/types';
+import { CartListResponse } from '../types/types.ts';
+
+export const readCartApi = rootApi.injectEndpoints({
+ endpoints: (build) => ({
+ getCartList: build.query({
+ query: () => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/me/carts`,
+ params: {
+ withTotal: false,
+ },
+ }),
+ }),
+
+ getCartById: build.query({
+ query: (id) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/me/carts/${id}`,
+ }),
+ providesTags: (result) =>
+ result?.lineItems
+ ? [
+ ...result.lineItems.map(({ id }) => ({ type: 'CartItems' as const, id })),
+ { type: 'CartItems', id: 'LIST' },
+ ]
+ : [{ type: 'CartItems', id: 'LIST' }],
+ }),
+ }),
+});
+
+export const { useGetCartListQuery, useLazyGetCartListQuery, useGetCartByIdQuery, useLazyGetCartByIdQuery } =
+ readCartApi;
diff --git a/src/entities/cart/api/updateCartApi.ts b/src/entities/cart/api/updateCartApi.ts
new file mode 100644
index 00000000..16ede90b
--- /dev/null
+++ b/src/entities/cart/api/updateCartApi.ts
@@ -0,0 +1,26 @@
+import { rootApi } from '../../../shared/api';
+import { CartResponse } from '../../../shared/types';
+import { ICreateCartParams, IUpdateCartParams } from '../types/interfaces.ts';
+
+export const updateCartApi = rootApi.injectEndpoints({
+ endpoints: (build) => ({
+ createCart: build.mutation({
+ query: (body) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/me/carts`,
+ method: 'POST',
+ body,
+ }),
+ }),
+
+ updateCart: build.mutation({
+ query: ({ cartId, body }) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/me/carts/${cartId}`,
+ method: 'POST',
+ body,
+ }),
+ invalidatesTags: [{ type: 'CartItems', id: 'LIST' }],
+ }),
+ }),
+});
+
+export const { useUpdateCartMutation, useCreateCartMutation } = updateCartApi;
diff --git a/src/entities/cart/index.ts b/src/entities/cart/index.ts
new file mode 100644
index 00000000..cca8728d
--- /dev/null
+++ b/src/entities/cart/index.ts
@@ -0,0 +1,8 @@
+export {
+ readCartApi,
+ useGetCartListQuery,
+ useLazyGetCartByIdQuery,
+ useGetCartByIdQuery,
+ useLazyGetCartListQuery,
+} from './api/readCartApi.ts';
+export { updateCartApi, useCreateCartMutation, useUpdateCartMutation } from './api/updateCartApi.ts';
diff --git a/src/entities/cart/types/interfaces.ts b/src/entities/cart/types/interfaces.ts
new file mode 100644
index 00000000..d1066d63
--- /dev/null
+++ b/src/entities/cart/types/interfaces.ts
@@ -0,0 +1,15 @@
+import { AddDiscountCodeRequestBody, AddLineItemRequestBody, RemoveLineItemRequestBody } from './types.ts';
+
+export interface ICreateCartParams {
+ currency: 'USD' | 'UAH' | 'EUR';
+}
+
+export interface IUpdateCartParams {
+ cartId: string;
+ body: AddLineItemRequestBody | RemoveLineItemRequestBody | AddDiscountCodeRequestBody;
+}
+
+export interface IDeleteCartParams {
+ cartId: string;
+ version: number;
+}
diff --git a/src/entities/cart/types/types.ts b/src/entities/cart/types/types.ts
new file mode 100644
index 00000000..c3b131c5
--- /dev/null
+++ b/src/entities/cart/types/types.ts
@@ -0,0 +1,39 @@
+import { CartResponse } from '../../../shared/types';
+
+type CartListResponse = Readonly<{
+ limit: number;
+ offset: number;
+ count: number;
+ total: number;
+ results: CartResponse[];
+}>;
+
+type AddLineItemRequestBody = {
+ version: number;
+ actions: {
+ action: string;
+ productId: string;
+ variantId: number;
+ quantity: number;
+ }[];
+};
+
+type RemoveLineItemRequestBody = {
+ version: number;
+ actions: {
+ action: string;
+ lineItemId: string;
+ variantId: number;
+ quantity?: number;
+ }[];
+};
+
+type AddDiscountCodeRequestBody = {
+ version: number;
+ actions: {
+ action: string;
+ code: string;
+ }[];
+};
+
+export type { AddLineItemRequestBody, RemoveLineItemRequestBody, CartListResponse, AddDiscountCodeRequestBody };
diff --git a/src/entities/form/ui/CustomRegForm.tsx b/src/entities/form/ui/CustomRegForm.tsx
new file mode 100644
index 00000000..cda87b15
--- /dev/null
+++ b/src/entities/form/ui/CustomRegForm.tsx
@@ -0,0 +1,23 @@
+export default function CustomRegForm(props: { children: React.ReactNode }) {
+ const { children } = props;
+ return (
+
+ );
+}
diff --git a/src/entities/form/ui/ErrorModal.tsx b/src/entities/form/ui/ErrorModal.tsx
new file mode 100644
index 00000000..ef879a05
--- /dev/null
+++ b/src/entities/form/ui/ErrorModal.tsx
@@ -0,0 +1,78 @@
+import { motion } from 'framer-motion';
+import { Link } from 'react-router-dom';
+
+import redCross from '../../../assets/icons/RedCross.svg';
+import { pageVariants } from '../../../shared/ui';
+
+interface IErrorModalProps {
+ reStartForm: () => void;
+ errorMessage: string;
+ navigateTo: string;
+}
+
+export default function ErrorModal({ reStartForm, errorMessage, navigateTo }: IErrorModalProps) {
+ return (
+
+ Oh snap!
+
+ {errorMessage}
+
+ Change a few things up and try again
+
+
+
+
+ {
+ reStartForm();
+ }}
+ to={navigateTo}
+ className="
+ mb-4
+ mt-8
+ h-10
+ w-30
+ rounded-lg
+ bg-accent
+ p-2
+ text-center
+ text-primary
+ "
+ >
+ Continue
+
+
+ );
+}
diff --git a/src/entities/form/ui/SuccessModal.tsx b/src/entities/form/ui/SuccessModal.tsx
new file mode 100644
index 00000000..4d42c966
--- /dev/null
+++ b/src/entities/form/ui/SuccessModal.tsx
@@ -0,0 +1,66 @@
+import { motion } from 'framer-motion';
+import { Link } from 'react-router-dom';
+
+import accentGulp from '../../../assets/icons/AccentGulp.svg';
+import { pageVariants } from '../../../shared/ui';
+
+function SuccessModal() {
+ return (
+
+ Success!
+ Your account has been created
+
+
+
+
+ Continue
+
+
+ );
+}
+
+export default SuccessModal;
diff --git a/src/entities/form/ui/index.ts b/src/entities/form/ui/index.ts
new file mode 100644
index 00000000..6c063ec9
--- /dev/null
+++ b/src/entities/form/ui/index.ts
@@ -0,0 +1,5 @@
+import CustomRegForm from './CustomRegForm.tsx';
+import ErrorModal from './ErrorModal.tsx';
+import SuccessModal from './SuccessModal.tsx';
+
+export { CustomRegForm, SuccessModal, ErrorModal };
diff --git a/src/entities/product/api/productApi.ts b/src/entities/product/api/productApi.ts
new file mode 100644
index 00000000..6d949721
--- /dev/null
+++ b/src/entities/product/api/productApi.ts
@@ -0,0 +1,51 @@
+import rootApi from '../../../shared/api/rootApi.ts';
+import { ProductResult } from '../../../shared/types';
+import prepareFilterQuery from '../../user/model/prepareFilterQuery.ts';
+import { IGetProductListParams } from '../types/interfaces.ts';
+import { CategoriesResponse, CategoryResult, ProductResponse } from '../types/types.ts';
+
+export const productApi = rootApi.injectEndpoints({
+ endpoints: (build) => ({
+ getProductList: build.query({
+ query: ({ limit = 5, offset = 0, sort, filters, searchQuery, withTotal = true }) => ({
+ url: prepareFilterQuery(filters),
+ params: {
+ limit,
+ offset,
+ ...(sort && { sort: `${sort.field} ${sort.order}` }),
+ ...(searchQuery && { fuzzy: `true`, 'text.en': searchQuery }),
+ withTotal,
+ },
+ }),
+ }),
+
+ getProduct: build.query({
+ query: (id) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/product-projections/${id}`,
+ }),
+ }),
+
+ getCategories: build.query({
+ query: (limit = 20) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/categories`,
+ params: {
+ limit,
+ },
+ }),
+ }),
+
+ getCategory: build.query({
+ query: (key) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/categories/key=${key}`,
+ }),
+ }),
+ }),
+});
+
+export const {
+ useGetProductListQuery,
+ useLazyGetProductListQuery,
+ useGetProductQuery,
+ useGetCategoriesQuery,
+ useGetCategoryQuery,
+} = productApi;
diff --git a/src/entities/product/index.ts b/src/entities/product/index.ts
new file mode 100644
index 00000000..34bad320
--- /dev/null
+++ b/src/entities/product/index.ts
@@ -0,0 +1,16 @@
+import calcPriceDiscountPercentage from './lib/helpers/calcPriceDiscountPercentage.ts';
+import formatPrice from './lib/helpers/formatPrice.ts';
+import pennieToMoney from './lib/helpers/pennieToMoney.ts';
+
+export type { ProductAttribute } from './types/types.ts';
+export type { IGetProductAttributes } from './types/interfaces.ts';
+export {
+ productApi,
+ useGetProductListQuery,
+ useLazyGetProductListQuery,
+ useGetProductQuery,
+ useGetCategoriesQuery,
+ useGetCategoryQuery,
+} from './api/productApi.ts';
+export { formatPrice, calcPriceDiscountPercentage, pennieToMoney };
+export { ProductAttributeNames } from './types/enums.ts';
diff --git a/src/entities/product/lib/helpers/calcPriceDiscountPercentage.ts b/src/entities/product/lib/helpers/calcPriceDiscountPercentage.ts
new file mode 100644
index 00000000..ee68b9bb
--- /dev/null
+++ b/src/entities/product/lib/helpers/calcPriceDiscountPercentage.ts
@@ -0,0 +1,12 @@
+/**
+ * Calculates the percentage discount between two prices.
+ *
+ * @param {number} oldPrice - The original price.
+ * @param {number} newPrice - The new price.
+ * @return {number} The percentage discount between the two prices.
+ */
+function calcPriceDiscountPercentage(oldPrice: number, newPrice: number) {
+ return 100 - Math.floor((newPrice / oldPrice) * 100);
+}
+
+export default calcPriceDiscountPercentage;
diff --git a/src/entities/product/lib/helpers/formatPrice.ts b/src/entities/product/lib/helpers/formatPrice.ts
new file mode 100644
index 00000000..ffe25d44
--- /dev/null
+++ b/src/entities/product/lib/helpers/formatPrice.ts
@@ -0,0 +1,22 @@
+/**
+ * Formats a given number as currency using the specified currency and locale.
+ *
+ * @param {number} num - The number to be formatted as currency.
+ * @param {string} currency - The currency code to be used for formatting.
+ * @param {string} locale - The locale code to be used for formatting.
+ * @returns {string} The formatted number as currency.
+ */
+
+const US_LOCALE = 'en-US';
+const US_CURRENCY = 'USD';
+
+const formatPrice = (num: number, currency: string = US_CURRENCY, locale: string = US_LOCALE) => {
+ const numFormatOptions = new Intl.NumberFormat(locale, {
+ style: 'currency',
+ currency,
+ });
+
+ return numFormatOptions.format(num);
+};
+
+export default formatPrice;
diff --git a/src/entities/product/lib/helpers/pennieToMoney.ts b/src/entities/product/lib/helpers/pennieToMoney.ts
new file mode 100644
index 00000000..8a44ba59
--- /dev/null
+++ b/src/entities/product/lib/helpers/pennieToMoney.ts
@@ -0,0 +1,11 @@
+/**
+ * Converts the given amount of pennies to currency.
+ *
+ * @param {number} num - The amount of pennies to be converted.
+ * @return {number} - The equivalent amount in currency.
+ */
+function pennieToMoney(num: number) {
+ return Number((num / 100).toFixed(2));
+}
+
+export default pennieToMoney;
diff --git a/src/entities/product/types/enums.ts b/src/entities/product/types/enums.ts
new file mode 100644
index 00000000..656f17fc
--- /dev/null
+++ b/src/entities/product/types/enums.ts
@@ -0,0 +1,21 @@
+export enum ProductAttributeNames {
+ INGREDIENTS = 'ingredients',
+ WEIGHT = 'weight',
+ PROTEINS = 'proteins',
+ FATS = 'fats',
+ CALORIES = 'calories',
+ CARBOHYDRATES = 'carbohydrates',
+ IS_VEGAN = 'isVegan',
+ IS_SPICY = 'isSpicy',
+ ALLERGENS = 'allergens',
+}
+
+export enum ProductSortingFields {
+ name = 'name.en',
+ price = 'price',
+}
+
+export enum ProductSortOrders {
+ asc = 'asc',
+ desc = 'desc',
+}
diff --git a/src/entities/product/types/interfaces.ts b/src/entities/product/types/interfaces.ts
new file mode 100644
index 00000000..f1a3130b
--- /dev/null
+++ b/src/entities/product/types/interfaces.ts
@@ -0,0 +1,23 @@
+import { ProductSortingFields, ProductSortOrders } from './enums.ts';
+
+export interface IGetProductAttributes {
+ isVegan: boolean;
+ isSpicy: boolean;
+ isPromo: boolean;
+ price: string;
+ calories: string;
+ weight: string;
+ categoryId?: string;
+}
+
+export interface IGetProductListParams {
+ limit: number;
+ offset?: number;
+ sort?: {
+ field: ProductSortingFields;
+ order: ProductSortOrders;
+ };
+ filters?: IGetProductAttributes;
+ searchQuery?: string | null;
+ withTotal?: boolean;
+}
diff --git a/src/entities/product/types/types.ts b/src/entities/product/types/types.ts
new file mode 100644
index 00000000..ba896727
--- /dev/null
+++ b/src/entities/product/types/types.ts
@@ -0,0 +1,69 @@
+import { ProductAttributeNames } from './enums.ts';
+import { ProductResult } from '../../../shared/types';
+
+export type ProductAttribute = Readonly<{
+ name: ProductAttributeNames;
+ value: string | number;
+}>;
+
+export type ProductPrice = Readonly<{
+ id: string;
+ value: {
+ type: string;
+ fractionDigits: number;
+ centAmount: number;
+ currencyCode: string;
+ };
+ discounted?: {
+ id: string;
+ value: {
+ centAmount: number;
+ currencyCode: string;
+ };
+ };
+}>;
+
+export type ProductImage = Readonly<{
+ dimensions: {
+ h: number;
+ w: number;
+ };
+ url: string;
+}>;
+
+export type ProductResponse = Readonly<{
+ limit: number;
+ offset: number;
+ count: number;
+ total: number;
+ results: ProductResult[];
+}>;
+
+export type CategoryAncestor = Readonly<{
+ typeId: string;
+ id: string;
+}>;
+
+type CategoryParent = Readonly<{
+ typeId: string;
+ id: string;
+}>;
+
+export type CategoryResult = Readonly<{
+ id: string;
+ name: {
+ en: string;
+ de: string;
+ ua: string;
+ };
+ ancestors: CategoryAncestor[];
+ parent?: CategoryParent;
+}>;
+
+export type CategoriesResponse = Readonly<{
+ limit: number;
+ offset: number;
+ count: number;
+ total: number;
+ results: CategoryResult[];
+}>;
diff --git a/src/entities/user/api/authApi.ts b/src/entities/user/api/authApi.ts
new file mode 100644
index 00000000..345b5662
--- /dev/null
+++ b/src/entities/user/api/authApi.ts
@@ -0,0 +1,48 @@
+import { rootAuthApi } from '../../../shared/api';
+import { IAuthResponse, ILoginUserParams, TokenTypeHints } from '../../../shared/types';
+
+interface ITokenRevokeParams {
+ token: string;
+ tokenTypeHint: TokenTypeHints;
+}
+
+export const authApi = rootAuthApi.injectEndpoints({
+ endpoints: (build) => ({
+ loginToken: build.mutation({
+ query: ({ password, email, scope = import.meta.env.VITE_DEFAULT_CUSTOMER_SCOPE }) => ({
+ url: `/oauth/${import.meta.env.VITE_PROJECT_KEY}/customers/token`,
+ method: 'POST',
+ params: {
+ grant_type: 'password',
+ username: email,
+ password,
+ scope,
+ },
+ }),
+ }),
+
+ anonymousSession: build.mutation({
+ query: () => ({
+ url: `/oauth/${import.meta.env.VITE_PROJECT_KEY}/anonymous/token`,
+ method: 'POST',
+ params: {
+ grant_type: 'client_credentials',
+ scope: import.meta.env.VITE_DEFAULT_CUSTOMER_SCOPE,
+ },
+ }),
+ }),
+
+ revokeToken: build.mutation({
+ query: ({ token, tokenTypeHint }) => ({
+ url: `oauth/token/revoke`,
+ method: 'POST',
+ params: {
+ token,
+ token_type_hint: tokenTypeHint,
+ },
+ }),
+ }),
+ }),
+});
+
+export const { useLoginTokenMutation, useAnonymousSessionMutation, useRevokeTokenMutation } = authApi;
diff --git a/src/entities/user/api/signUpApi.ts b/src/entities/user/api/signUpApi.ts
new file mode 100644
index 00000000..757f3990
--- /dev/null
+++ b/src/entities/user/api/signUpApi.ts
@@ -0,0 +1,32 @@
+import rootApi from '../../../shared/api/rootApi.ts';
+import { ISignUpParams } from '../../../shared/types';
+
+type ISignUpResponse = Readonly<{
+ customer: {
+ addresses: string[];
+ email: string;
+ firstName: string;
+ id: string;
+ isEmailVerified: boolean;
+ lastName: string;
+ password: string;
+ version: number;
+ createdAt: string;
+ lastModifiedAt: string;
+ authenticationMode: string;
+ };
+}>;
+
+export const signUpApi = rootApi.injectEndpoints({
+ endpoints: (build) => ({
+ signUp: build.mutation({
+ query: (signUpData) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/me/signup`,
+ method: 'POST',
+ body: JSON.stringify(signUpData),
+ }),
+ }),
+ }),
+});
+
+export const { useSignUpMutation } = signUpApi;
diff --git a/src/entities/user/api/userDataApi.ts b/src/entities/user/api/userDataApi.ts
new file mode 100644
index 00000000..83bc1176
--- /dev/null
+++ b/src/entities/user/api/userDataApi.ts
@@ -0,0 +1,63 @@
+import { rootApi } from '../../../shared/api';
+import { ILoginUserDataResponse, ILoginUserParams, IUser } from '../../../shared/types';
+import { IUpdateUserAddressParams, IUpdateUserDataParams, IUpdateUserPassword } from '../types/interfaces.ts';
+
+export const userDataApi = rootApi.injectEndpoints({
+ endpoints: (build) => ({
+ loginUserData: build.mutation({
+ query: (loginData) => ({
+ url: `/${import.meta.env.VITE_PROJECT_KEY}/login`,
+ method: 'POST',
+ body: JSON.stringify(loginData),
+ }),
+ }),
+
+ getUser: build.query({
+ query: (id) => ({
+ url: `${import.meta.env.VITE_PROJECT_KEY}/customers/${id}`,
+ }),
+ }),
+
+ deleteUserData: build.mutation({
+ query: (body) => ({
+ url: `${import.meta.env.VITE_PROJECT_KEY}/me`,
+ method: 'POST',
+ body: JSON.stringify(body),
+ }),
+ }),
+
+ updateUserData: build.mutation({
+ query: (body) => ({
+ url: `${import.meta.env.VITE_PROJECT_KEY}/me`,
+ method: 'POST',
+ body: JSON.stringify(body),
+ }),
+ }),
+
+ updateUserAddress: build.mutation({
+ query: ({ body, id }) => ({
+ url: `${import.meta.env.VITE_PROJECT_KEY}/customers/${id}`,
+ method: 'POST',
+ body: JSON.stringify(body),
+ }),
+ }),
+
+ updateUserPassword: build.mutation({
+ query: (body) => ({
+ url: `${import.meta.env.VITE_PROJECT_KEY}/me/password`,
+ method: 'POST',
+ body: JSON.stringify(body),
+ }),
+ }),
+ }),
+});
+
+export const {
+ useLoginUserDataMutation,
+ useGetUserQuery,
+ useLazyGetUserQuery,
+ useDeleteUserDataMutation,
+ useUpdateUserDataMutation,
+ useUpdateUserAddressMutation,
+ useUpdateUserPasswordMutation,
+} = userDataApi;
diff --git a/src/entities/user/consts/constants.ts b/src/entities/user/consts/constants.ts
new file mode 100644
index 00000000..95082ec9
--- /dev/null
+++ b/src/entities/user/consts/constants.ts
@@ -0,0 +1,5 @@
+export const COOKIE_ACCESS_TOKEN = 'accessToken';
+export const COOKIE_REFRESH_TOKEN = 'refreshToken';
+export const USER_LOGGED_IN_DATA_KEY = 'userLoggedInData';
+export const COOKIE_USER_ID = 'userId';
+export const COOKIE_CART_ID = 'cartId';
diff --git a/src/entities/user/index.ts b/src/entities/user/index.ts
new file mode 100644
index 00000000..67bf514b
--- /dev/null
+++ b/src/entities/user/index.ts
@@ -0,0 +1,22 @@
+import getFullName from './lib/helpers/getFullName.ts';
+import prepareLoginCookieData from './lib/helpers/prepareLoginCookieData.ts';
+import useLoginUser from './lib/hooks/useLoginUser.ts';
+import UserUpdateActions from './types/enums.ts';
+
+export { signUpApi, useSignUpMutation } from './api/signUpApi.ts';
+export { COOKIE_ACCESS_TOKEN, USER_LOGGED_IN_DATA_KEY } from './consts/constants.ts';
+export { authApi, useAnonymousSessionMutation, useLoginTokenMutation, useRevokeTokenMutation } from './api/authApi.ts';
+export { userReducer, userSlice } from './model/slice.ts';
+export {
+ userDataApi,
+ useLoginUserDataMutation,
+ useGetUserQuery,
+ useLazyGetUserQuery,
+ useDeleteUserDataMutation,
+ useUpdateUserDataMutation,
+ useUpdateUserAddressMutation,
+ useUpdateUserPasswordMutation,
+} from './api/userDataApi.ts';
+export { getFullName, prepareLoginCookieData, useLoginUser };
+export type { IUpdateUserDataParams } from './types/interfaces.ts';
+export { UserUpdateActions };
diff --git a/src/entities/user/lib/helpers/getCookieValue.ts b/src/entities/user/lib/helpers/getCookieValue.ts
new file mode 100644
index 00000000..da4970d9
--- /dev/null
+++ b/src/entities/user/lib/helpers/getCookieValue.ts
@@ -0,0 +1,7 @@
+export default function getCookieValue(name: string) {
+ const matches = document.cookie.match(
+ // eslint-disable-next-line no-useless-escape
+ new RegExp(`(?:^|; )${name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1')}=([^;]*)`),
+ );
+ return matches ? decodeURIComponent(matches[1]) : undefined;
+}
diff --git a/src/entities/user/lib/helpers/getFullName.ts b/src/entities/user/lib/helpers/getFullName.ts
new file mode 100644
index 00000000..87390631
--- /dev/null
+++ b/src/entities/user/lib/helpers/getFullName.ts
@@ -0,0 +1,12 @@
+/**
+ * Get full name by concatenating the first name and the last name.
+ *
+ * @param {string} firstName - The first name.
+ * @param {string} lastName - The last name.
+ * @return {string} The full name.
+ */
+function getFullName(firstName: string = '', lastName: string = '') {
+ return `${firstName} ${lastName}`;
+}
+
+export default getFullName;
diff --git a/src/entities/user/lib/helpers/prepareLoginCookieData.ts b/src/entities/user/lib/helpers/prepareLoginCookieData.ts
new file mode 100644
index 00000000..26f29b37
--- /dev/null
+++ b/src/entities/user/lib/helpers/prepareLoginCookieData.ts
@@ -0,0 +1,35 @@
+import { CookieTuple } from '../../../../shared/types';
+import { COOKIE_ACCESS_TOKEN, COOKIE_CART_ID, COOKIE_REFRESH_TOKEN, COOKIE_USER_ID } from '../../consts/constants.ts';
+
+interface ICookieData {
+ accessToken: string;
+ expiresInToken: number;
+ id: string;
+ refreshToken: string;
+ cartId: string;
+}
+
+/**
+ * Prepares the login cookie data.
+ *
+ * @param {string} accessToken - The access token.
+ * @param {string} expiresInToken - The expiration time of the access token.
+ * @param {string} id - The user ID.
+ * @param {string} refreshToken - Users refresh token
+ * @param {string} cartId - Users cart ID
+ * @param {string} expiresInId - The expiration time of the user ID.
+ * @return {void}
+ */
+function prepareLoginCookieData(
+ { accessToken, expiresInToken, refreshToken, id, cartId }: ICookieData,
+ expiresInId: number = expiresInToken,
+): [CookieTuple, CookieTuple, CookieTuple, CookieTuple] {
+ const accessTokenCookie: CookieTuple = [accessToken, COOKIE_ACCESS_TOKEN, expiresInToken];
+ const idCookie: CookieTuple = [id, COOKIE_USER_ID, expiresInId];
+ const refreshTokenCookie: CookieTuple = [refreshToken, COOKIE_REFRESH_TOKEN, expiresInToken];
+ const cartIdCookie: CookieTuple = [cartId, COOKIE_CART_ID, expiresInToken];
+
+ return [accessTokenCookie, idCookie, refreshTokenCookie, cartIdCookie];
+}
+
+export default prepareLoginCookieData;
diff --git a/src/entities/user/lib/hooks/useLoginUser.ts b/src/entities/user/lib/hooks/useLoginUser.ts
new file mode 100644
index 00000000..449e56b4
--- /dev/null
+++ b/src/entities/user/lib/hooks/useLoginUser.ts
@@ -0,0 +1,58 @@
+import { setCookie } from '../../../../shared/lib/helpers';
+import { useAppDispatch, useAppSelector } from '../../../../shared/lib/hooks';
+import { prepareLoginCookieData, useLoginTokenMutation, userSlice } from '../../index.ts';
+
+/**
+ * Logs in user and stores the access token in cookies.
+ * @typedef {string} email - The email of the user
+ * @typedef {string} password - The password of the user
+ * @typedef {string} id - The id of the user
+ * @typedef {(email, password, id) => Promise} loginUser - Function to login user
+ * @typedef {{isLoading}} paramsObj - Object of params
+ * @typedef {[loginUser, paramsObj]} ParamsTuple
+ * @returns {ParamsTuple} The tuple of loginUser function and object of parameters such as isLoading.
+ */
+function useLoginUser() {
+ const [getLoginToken, params] = useLoginTokenMutation();
+ const dispatch = useAppDispatch();
+ const { loggedIn } = userSlice.actions;
+ const { cartId } = useAppSelector((state) => state.userReducer);
+
+ /**
+ * Logs in a user using the provided email, password, and id.
+ *
+ * @param {string} email - The email of the user.
+ * @param {string} password - The password of the user.
+ * @param {string} id - The ID of the user.
+ * @returns {Promise} - A promise that resolves when the user is logged in successfully.
+ * @throws {Error} - If an error occurs while logging in the user.
+ */
+ async function loginUser(email: string, password: string, id: string) {
+ try {
+ const {
+ access_token: accessToken,
+ expires_in: expiresIn,
+ refresh_token: refreshToken,
+ } = await getLoginToken({ email, password }).unwrap();
+
+ dispatch(loggedIn({ accessToken, userId: id, refreshToken, cartId: '' }));
+
+ const [accessTokenCookie, idCookie, refreshTokenCookie, cardIdCookie] = prepareLoginCookieData({
+ accessToken,
+ id,
+ refreshToken,
+ expiresInToken: expiresIn,
+ cartId,
+ });
+ setCookie(accessTokenCookie, idCookie, refreshTokenCookie, cardIdCookie);
+ } catch (e) {
+ if (e && typeof e === 'object' && 'status' in e) {
+ throw new Error(`Error occurred while logged in user ${e.status}`);
+ }
+ }
+ }
+
+ return [loginUser, params] as [typeof loginUser, typeof params];
+}
+
+export default useLoginUser;
diff --git a/src/entities/user/model/initialState.ts b/src/entities/user/model/initialState.ts
new file mode 100644
index 00000000..dd0e6a38
--- /dev/null
+++ b/src/entities/user/model/initialState.ts
@@ -0,0 +1,17 @@
+export interface IInitialState {
+ accessToken: string;
+ isLogged: boolean;
+ userId: string;
+ refreshToken: string;
+ cartId: string;
+}
+
+const initialState: IInitialState = {
+ accessToken: '',
+ isLogged: false,
+ userId: '',
+ refreshToken: '',
+ cartId: '',
+};
+
+export default initialState;
diff --git a/src/entities/user/model/prepareFilterQuery.ts b/src/entities/user/model/prepareFilterQuery.ts
new file mode 100644
index 00000000..1c01e176
--- /dev/null
+++ b/src/entities/user/model/prepareFilterQuery.ts
@@ -0,0 +1,44 @@
+import { IGetProductListParams } from '../../product/types/interfaces.ts';
+
+const IS_PROMO = 'isPromo';
+const CATEGORY_ID = 'categoryId';
+const PRICE = 'price';
+const STRING = 'string';
+const BOOLEAN = 'boolean';
+
+function prepareFilterQuery(filters: IGetProductListParams['filters']) {
+ if (!filters || !Object.values(filters).filter(Boolean).length)
+ return `/${import.meta.env.VITE_PROJECT_KEY}/product-projections/search`;
+
+ const queries = Object.entries(filters)
+ .map(([name, value]) => {
+ if (name === IS_PROMO) {
+ return value ? `variants.prices.discounted:exists` : null;
+ }
+
+ if (name === CATEGORY_ID) {
+ return value ? `categories.id:"${value}"` : null;
+ }
+
+ if (name === PRICE) {
+ const pennyPrice = value * 100;
+ return value ? `variants.price.centAmount:range (0 to ${pennyPrice})` : null;
+ }
+
+ if (typeof value === STRING) {
+ return value ? `variants.attributes.${name}:range (0 to ${value})` : null;
+ }
+
+ if (typeof value === BOOLEAN) {
+ return value ? `variants.attributes.${name}:"${value}"` : null;
+ }
+
+ return null;
+ })
+ .filter(Boolean)
+ .join('&filter=');
+
+ return `/${import.meta.env.VITE_PROJECT_KEY}/product-projections/search?filter=${queries}`;
+}
+
+export default prepareFilterQuery;
diff --git a/src/entities/user/model/slice.ts b/src/entities/user/model/slice.ts
new file mode 100644
index 00000000..909f512c
--- /dev/null
+++ b/src/entities/user/model/slice.ts
@@ -0,0 +1,44 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+import initialState from './initialState.ts';
+
+interface ILoggedInPayload {
+ accessToken: string;
+ userId: string;
+ refreshToken: string;
+ cartId: string;
+}
+
+interface IUpdateAccessTokenPayload extends Omit {
+ cartId: string;
+}
+
+export const userSlice = createSlice({
+ name: 'user',
+ initialState,
+ reducers: {
+ updateAccessToken: (state, action: PayloadAction) => {
+ const { accessToken, refreshToken } = action.payload;
+ state.accessToken = accessToken;
+ state.refreshToken = refreshToken;
+ },
+
+ loggedIn: (state, action: PayloadAction) => {
+ const { accessToken, userId, refreshToken, cartId } = action.payload;
+
+ state.isLogged = true;
+ state.accessToken = accessToken;
+ state.userId = userId;
+ state.refreshToken = refreshToken;
+ state.cartId = cartId;
+ },
+
+ loggedOut: () => initialState,
+
+ updateCartId: (state, action: PayloadAction) => {
+ state.cartId = action.payload;
+ },
+ },
+});
+
+export const userReducer = userSlice.reducer;
diff --git a/src/entities/user/types/enums.ts b/src/entities/user/types/enums.ts
new file mode 100644
index 00000000..3b2fbc0d
--- /dev/null
+++ b/src/entities/user/types/enums.ts
@@ -0,0 +1,13 @@
+enum UserUpdateActions {
+ CHANGE_EMAIL = 'changeEmail',
+ SET_FIRST_NAME = 'setFirstName',
+ SET_LAST_NAME = 'setLastName',
+ SET_BIRTH_DATE = 'setDateOfBirth',
+ SET_DEFAULT_SHIPPING_ADDRESS = 'setDefaultShippingAddress',
+ SET_DEFAULT_BILLING_ADDRESS = 'setDefaultBillingAddress',
+ REMOVE_ADDRESS = 'removeAddress',
+ CHANGE_ADDRESS = 'changeAddress',
+ ADD_ADDRESS = 'addAddress',
+}
+
+export default UserUpdateActions;
diff --git a/src/entities/user/types/interfaces.ts b/src/entities/user/types/interfaces.ts
new file mode 100644
index 00000000..d1ba8c62
--- /dev/null
+++ b/src/entities/user/types/interfaces.ts
@@ -0,0 +1,33 @@
+import UserUpdateActions from './enums.ts';
+
+interface IAddress {
+ country: string;
+ city: string;
+ streetName: string;
+ postalCode: string;
+}
+
+export interface IAction {
+ action: UserUpdateActions;
+ addressId?: string;
+ email?: string;
+ firstName?: string;
+ lastName?: string;
+ dateOfBirth?: string;
+ address?: IAddress | IAddress[];
+}
+
+export interface IUpdateUserDataParams {
+ version: number;
+ actions: IAction[];
+}
+
+export interface IUpdateUserPassword extends Pick {
+ currentPassword: string;
+ newPassword: string;
+}
+
+export interface IUpdateUserAddressParams {
+ body: IUpdateUserDataParams;
+ id: string;
+}
diff --git a/src/features/AddToCart/AddToCartBtn.tsx b/src/features/AddToCart/AddToCartBtn.tsx
new file mode 100644
index 00000000..014942df
--- /dev/null
+++ b/src/features/AddToCart/AddToCartBtn.tsx
@@ -0,0 +1,62 @@
+import { motion } from 'framer-motion';
+
+import { useGetCartByIdQuery, useUpdateCartMutation } from '../../entities/cart';
+import { AddLineItemRequestBody } from '../../entities/cart/types/types';
+import { useAppSelector } from '../../shared/lib/hooks';
+import { LineItem } from '../../shared/types';
+import { PlusIcon } from '../../shared/ui';
+import { buttonTapAnimation, buttonTransition } from '../../shared/ui/animations.tsx';
+
+export default function AddToCartBtn(props: { productId: string }) {
+ const { cartId } = useAppSelector((state) => state.userReducer);
+ const { data: cart } = useGetCartByIdQuery(cartId);
+ const [updateLineItem, { isLoading: updateIsLoading }] = useUpdateCartMutation();
+ const { productId } = props;
+
+ if (!cart) return null;
+
+ const { lineItems } = cart;
+ const currItem = lineItems?.find((item) => item?.productId === productId) as LineItem;
+ const quantity = currItem?.quantity ?? 0;
+
+ const disabled = quantity > 0;
+
+ const addToCart = async () => {
+ const body: AddLineItemRequestBody = {
+ version: cart?.version || 1,
+ actions: [
+ {
+ action: 'addLineItem',
+ productId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ };
+ try {
+ return await updateLineItem({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ return null;
+ };
+
+ return (
+ {
+ e.preventDefault();
+ e.nativeEvent.stopImmediatePropagation();
+ addToCart();
+ }}
+ className={`${
+ updateIsLoading ? 'opacity-30' : ''
+ } mt-auto flex h-8 min-w-[30px] max-w-[50px] items-center justify-center self-end rounded-md bg-accent-lightest fill-accent text-2xl transition-all hover:bg-accent/30 disabled:bg-separation-line disabled:fill-text-grey`}
+ >
+
+
+ );
+}
diff --git a/src/features/LogOutBtn/LogOutBtn.tsx b/src/features/LogOutBtn/LogOutBtn.tsx
new file mode 100644
index 00000000..0cc728c4
--- /dev/null
+++ b/src/features/LogOutBtn/LogOutBtn.tsx
@@ -0,0 +1,48 @@
+import { useNavigate } from 'react-router-dom';
+
+import logOutIcon from '../../assets/icons/log-out.svg';
+import { COOKIE_ACCESS_TOKEN, userSlice } from '../../entities/user';
+import { COOKIE_REFRESH_TOKEN, COOKIE_USER_ID } from '../../entities/user/consts/constants.ts';
+import { deleteCookie } from '../../shared/lib/helpers';
+import { useAppDispatch, useAppSelector, useRevokeAccessRefreshTokens } from '../../shared/lib/hooks';
+
+export default function LogOutBtn(props: { isHeader: boolean }) {
+ const { isHeader } = props;
+ const {
+ isLogged,
+ accessToken: oldAccessToken,
+ refreshToken: oldRefreshToken,
+ } = useAppSelector((state) => state.userReducer);
+ const revokeTokens = useRevokeAccessRefreshTokens();
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+ const { loggedOut } = userSlice.actions;
+
+ async function handleLogout() {
+ dispatch(loggedOut());
+ navigate('/');
+ deleteCookie(COOKIE_ACCESS_TOKEN, COOKIE_USER_ID, COOKIE_REFRESH_TOKEN);
+ revokeTokens(oldAccessToken, oldRefreshToken);
+ }
+
+ return isLogged ? (
+
+
+
+ Log out
+
+
+ ) : null;
+}
diff --git a/src/features/PageBackBtn/PageBackBtn.tsx b/src/features/PageBackBtn/PageBackBtn.tsx
new file mode 100644
index 00000000..4a8c61e5
--- /dev/null
+++ b/src/features/PageBackBtn/PageBackBtn.tsx
@@ -0,0 +1,20 @@
+import { useNavigate } from 'react-router-dom';
+
+import arrowBlack from '../../assets/icons/arrowLeftBlack.svg';
+
+export default function PageBackBtn(props: { title: string }) {
+ const navigation = useNavigate();
+ const { title } = props;
+ return (
+
+
navigation(-1)}
+ className="mr-5 flex h-10 w-12 items-center justify-center rounded-2xl bg-separation-line dark:bg-text-grey"
+ type="button"
+ >
+
+
+ {title}
+
+ );
+}
diff --git a/src/features/SearchInput/SearchInput.tsx b/src/features/SearchInput/SearchInput.tsx
new file mode 100644
index 00000000..4c8ac1ad
--- /dev/null
+++ b/src/features/SearchInput/SearchInput.tsx
@@ -0,0 +1,222 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+
+import { AnimatePresence, Cycle, motion, useCycle } from 'framer-motion';
+import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
+
+import {
+ BUTTON_TYPE_NAME,
+ CATEGORIES_ALL_PATH,
+ CATEGORIES_PATH,
+ ENTER_KEY,
+ ESK_KEY,
+ SEARCH_QUERY,
+} from './constants/constants.ts';
+import findMatch from './helpers/findMatch.ts';
+import replaceMatch from './helpers/replaceMatch.ts';
+import cross from '../../assets/icons/cross.svg';
+import search from '../../assets/icons/search.svg';
+import { useLazyGetProductListQuery } from '../../entities/product';
+import { IGetProductListParams } from '../../entities/product/types/interfaces.ts';
+import { capitalize } from '../../shared/lib/helpers';
+import { Blackout } from '../../shared/ui';
+
+export default function SearchInput(props: { isHeader: boolean; setIsSearchActive?: Cycle }) {
+ const [query, setQuery] = useSearchParams('');
+ const [searchValue, setSearchValue] = useState(query.get(SEARCH_QUERY) ?? '');
+ const searchInputRef = useRef(null);
+ const { pathname } = useLocation();
+ const navigate = useNavigate();
+ const [isActive, setIsActive] = useCycle(false, true);
+ const [getProductList, { data }] = useLazyGetProductListQuery();
+
+ const { isHeader, setIsSearchActive } = props;
+ const resultNames = searchValue ? data?.results.map((res) => res.name.en) : null;
+
+ const queryProductList = useCallback(
+ (args: IGetProductListParams) => {
+ return getProductList(args);
+ },
+ [getProductList],
+ );
+
+ function handleKeyDown(e: React.KeyboardEvent) {
+ if (e.key !== ENTER_KEY && e.key !== ESK_KEY) return;
+ searchInputRef?.current?.blur();
+
+ if (!pathname.includes(CATEGORIES_PATH))
+ navigate({
+ pathname: CATEGORIES_ALL_PATH,
+ search: query.toString(),
+ });
+ }
+
+ function handleSubmit(e: React.FocusEvent) {
+ const val = e.target.value;
+
+ if ((e?.relatedTarget as HTMLButtonElement)?.type !== BUTTON_TYPE_NAME) {
+ setIsSearchActive?.();
+ setIsActive();
+ }
+
+ if (val === query.get(SEARCH_QUERY)) return;
+
+ query.set(SEARCH_QUERY, val);
+ setQuery(query);
+ }
+
+ function handleResultClick(nameId: number) {
+ const val = resultNames?.[nameId] ?? '';
+
+ setIsActive();
+ setIsSearchActive?.();
+
+ if (val === query.get(SEARCH_QUERY)) return;
+
+ setSearchValue(val);
+ query.set(SEARCH_QUERY, val);
+ setQuery(query);
+ }
+
+ function handleResetQuery() {
+ setSearchValue('');
+ if (isActive) {
+ setIsActive();
+ setIsSearchActive?.();
+ }
+ }
+
+ useEffect(() => {
+ const request = searchValue ? queryProductList({ searchQuery: searchValue, withTotal: false, limit: 5 }) : null;
+
+ return () => request?.abort();
+ }, [searchValue, queryProductList]);
+
+ return (
+ <>
+ {
+ setIsSearchActive?.();
+ setIsActive();
+ }}
+ />
+
+
+ {searchValue && (
+
+
+
+ )}
+ setSearchValue(e.target.value)}
+ onFocus={() => {
+ setIsSearchActive?.();
+ setIsActive();
+ }}
+ onBlur={handleSubmit}
+ className={`
+ peer
+ h-12
+ w-full
+ rounded-3xl
+ border-1
+ border-text-grey
+ border-opacity-30
+ bg-clip-text
+ px-14
+ transition-[transform,background-color]
+ duration-300
+ focus:-translate-y-0.5
+ focus:border-none focus:bg-secondary focus:bg-gradient-to-br
+ focus:bg-clip-padding
+ focus:outline-none
+ focus:drop-shadow-md
+ dark:text-primary
+ xs:border-none
+ lg:transition-all
+ ${isHeader ? 'dark:bg-dark-bg-primary' : 'dark:bg-separation-line/30 dark:placeholder:text-primary'}
+ md:leading-10
+ lg:pl-16
+ `}
+ />
+
+
+
+
+ {isActive && !!resultNames?.length && (
+
+ {resultNames.map((res, i) => (
+
+
+ handleResultClick(i)}>
+ {capitalize(findMatch(res, searchValue))}
+ {replaceMatch(res, searchValue)}
+
+
+ ))}
+
+ )}
+
+
+ >
+ );
+}
+
+SearchInput.defaultProps = {
+ setIsSearchActive: null,
+};
diff --git a/src/features/SearchInput/constants/constants.ts b/src/features/SearchInput/constants/constants.ts
new file mode 100644
index 00000000..39d0708d
--- /dev/null
+++ b/src/features/SearchInput/constants/constants.ts
@@ -0,0 +1,6 @@
+export const ENTER_KEY = 'Enter';
+export const ESK_KEY = 'Escape';
+export const CATEGORIES_PATH = 'categories';
+export const CATEGORIES_ALL_PATH = '/categories/all';
+export const SEARCH_QUERY = 'search';
+export const BUTTON_TYPE_NAME = 'button';
diff --git a/src/features/SearchInput/helpers/findMatch.ts b/src/features/SearchInput/helpers/findMatch.ts
new file mode 100644
index 00000000..5a892673
--- /dev/null
+++ b/src/features/SearchInput/helpers/findMatch.ts
@@ -0,0 +1,14 @@
+/**
+ * Finds the first occurrence of the search value in the given string and returns it.
+ * If no match is found, an empty string is returned.
+ * The search is case-insensitive.
+ *
+ * @param {string} str - The string to search in.
+ * @param {string} searchValue - The value to search for in the string.
+ * @return {string} The first occurrence of the search value in the string, or an empty string if no match is found.
+ */
+function findMatch(str: string, searchValue: string): string {
+ return str.toLowerCase().match(searchValue.toLowerCase())?.[0] ?? '';
+}
+
+export default findMatch;
diff --git a/src/features/SearchInput/helpers/replaceMatch.ts b/src/features/SearchInput/helpers/replaceMatch.ts
new file mode 100644
index 00000000..09fe8ac4
--- /dev/null
+++ b/src/features/SearchInput/helpers/replaceMatch.ts
@@ -0,0 +1,12 @@
+/**
+ * Replaces all occurrences of the searchValue in the given string with an empty string.
+ *
+ * @param {string} str - The string to perform the replacement on.
+ * @param {string} searchValue - The value to search for and replace.
+ * @return {string} The modified string with all occurrences of searchValue removed.
+ */
+function replaceMatch(str: string, searchValue: string): string {
+ return str.toLowerCase().replace(searchValue.toLowerCase(), '');
+}
+
+export default replaceMatch;
diff --git a/src/features/UserProfileLink/UserProfileLink.tsx b/src/features/UserProfileLink/UserProfileLink.tsx
new file mode 100644
index 00000000..93b71511
--- /dev/null
+++ b/src/features/UserProfileLink/UserProfileLink.tsx
@@ -0,0 +1,95 @@
+import { Link } from 'react-router-dom';
+
+import getGreeting from './helpers/getGreeting';
+import { getFullName, useGetUserQuery } from '../../entities/user';
+import { useAppSelector } from '../../shared/lib/hooks';
+import UserIcon from '../../shared/ui/UserIcon';
+
+const headerLoggedClass = 'block lg:hidden';
+const menuLoggedClass =
+ 'hidden w-full justify-center gap-3 text-text-grey lg:mt-28 lg:flex lg:flex-col lg:items-center xl:flex-row';
+
+function UserProfileLink(props: { isHeader: boolean }) {
+ const { isLogged, userId } = useAppSelector((state) => state.userReducer);
+ const { data, isLoading } = useGetUserQuery(userId, { skip: !userId });
+
+ const { isHeader } = props;
+ const userFullName = getFullName(data?.firstName, data?.lastName);
+
+ const hours = new Date().getHours();
+
+ const greeting = getGreeting(hours);
+
+ if (isLogged) {
+ return (
+
+
+
+
+ {userFullName}
+
+
+ {greeting}
+
+
+
+ );
+ }
+ return (
+ <>
+
+ Log in
+
+
+ Sign Up
+
+ >
+ );
+}
+
+export default UserProfileLink;
diff --git a/src/features/UserProfileLink/helpers/getGreeting.ts b/src/features/UserProfileLink/helpers/getGreeting.ts
new file mode 100644
index 00000000..5b897fec
--- /dev/null
+++ b/src/features/UserProfileLink/helpers/getGreeting.ts
@@ -0,0 +1,6 @@
+export default function getGreeting(_hours: number) {
+ if (_hours >= 6 && _hours < 12) return 'Good morning!';
+ if (_hours >= 12 && _hours < 18) return 'Good day!';
+ if (_hours >= 18 && _hours < 23) return 'Good evening!';
+ return 'Good night!';
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 00000000..2ac69a11
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,204 @@
+@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,700&family=Poppins:wght@100;200;300;400;500;600;700&display=swap');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+html {
+ scroll-behavior: smooth;
+}
+
+* {
+ -webkit-tap-highlight-color: transparent;
+ -webkit-touch-callout: none;
+}
+
+::selection {
+ color: hsl(126, 55%, 47%);
+ background-color: hsl(134, 73%, 90%);
+}
+
+/* width */
+::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+}
+
+/* Track */
+::-webkit-scrollbar-track {
+ background: hsl(0deg, 0%, 95%);
+}
+
+/* Handle */
+::-webkit-scrollbar-thumb {
+ background: hsl(126deg, 55%, 47%);
+ border-radius: 5px;
+}
+
+/* Handle on hover */
+::-webkit-scrollbar-thumb:hover {
+ background: hsl(134deg, 51%, 55%);
+}
+
+@layer base {
+ .navMenuItem {
+ @apply flex min-w-[95px] flex-col items-center justify-end lg:mr-auto lg:w-full lg:flex-row lg:justify-start lg:pl-6;
+ }
+
+ .navMenuLink {
+ @apply w-full text-center lg:text-left text-base min-w-[80px] min-h-[40];
+ }
+
+ .navMenuIcon {
+ @apply m-auto w-5 lg:mr-3 lg:w-6;
+ }
+
+ .loginRegLabel {
+ @apply relative mt-6 block h-11 w-full rounded-md;
+ }
+
+ .loginRegInput {
+ @apply h-full w-full rounded-lg border-2 border-separation-line pl-11 text-xs text-text-dark outline-none transition-colors
+ placeholder:text-sm md:placeholder:text-base focus:border-text-grey md:text-base dark:bg-dark-separation-line dark:border-text-grey dark:text-primary;
+ }
+
+ .invalidInputIcon {
+ @apply absolute left-4 top-3.5 h-5 w-5 bg-contain bg-no-repeat;
+ }
+
+ .invalidInputMsg {
+ @apply absolute -top-[1.15rem] left-2 text-[0.5rem] leading-[0.5rem] text-shop-cart-red peer-invalid:block md:-top-5 md:text-xs;
+ }
+
+ .profileInputWrapper {
+ @apply pt-4 sm:flex sm:items-center sm:justify-between sm:py-5;
+ }
+
+ .addressProfileWrapper {
+ @apply mt-6 block h-10 w-full rounded-md border-2 border-separation-line pl-4 text-base leading-[35px] text-text-dark md:max-w-[47%] dark:text-primary dark:border-text-grey;
+ }
+
+ a.active {
+ @apply text-accent;
+ }
+}
+
+@layer components {
+ .hiddenCheckBox {
+ @apply mr-2 h-4 w-4 appearance-none rounded-[4px] border-1 border-accent bg-primary;
+ }
+}
+
+@layer utilities {
+ .regFormCheckGulp {
+ @apply before:absolute before:-left-5 before:top-2 before:hidden before:h-1.5 before:w-2.5 before:-rotate-45 before:border-b-3 before:border-l-3 before:border-b-accent before:border-l-accent;
+ }
+}
+
+.truncate-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.react-star-rate {
+ display: flex !important;
+}
+
+.react-star-rate__star {
+ font-size: clamp(1rem, 2vw, 1.5rem);
+ margin-right: 2px !important;
+}
+
+.swiper-button-prev-unique,
+.swiper-button-next-unique {
+ position: absolute;
+ top: 50%;
+ z-index: 80;
+ width: 0;
+ height: 0;
+ background-position: center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.swiper-button-prev-unique:hover,
+.swiper-button-next-unique:hover{
+ scale: 1.1
+}
+
+.swiper-button-next-unique:active {
+ translate: 0.5rem;
+}
+
+.swiper-button-prev-unique:active {
+ translate: -0.5rem;
+}
+
+.swiper-button-prev-unique {
+ background-image: url('assets/icons/slidetPrev.svg');
+ object-fit: cover;
+ left: 0;
+}
+
+.swiper-button-next-unique {
+ background-image: url('assets/icons/sliderRight.svg');
+ object-fit: cover;
+ right: 0;
+}
+
+.swiper {
+ padding-bottom: 150px !important;
+}
+
+#miniSwiper {
+ padding-bottom: 0 !important;
+}
+
+#miniSwiper .swiper-pagination {
+ bottom: 20px !important;
+}
+
+.swiper-pagination {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 4px;
+ bottom: 10px !important;
+ background-color: hsl(0deg, 0%, 57%);
+ border-radius: 24px;
+ max-width: 100px;
+ left: 50% !important;
+ transform: translate(-50%, 0%) !important;
+ width: fit-content !important;
+}
+
+.swiper-pagination-bullet,
+.swiper-pagination-bullet-active {
+ background-color: hsla(0, 0%, 0%, 0) !important;
+ border: 1px solid black;
+ height: 18px !important;
+ width: 18px !important;
+}
+
+.swiper-pagination-bullet-active {
+ background-color: hsl(126deg, 0%, 20%) !important;
+ border: none;
+}
+
+@media (min-width: 680px) {
+ .swiper-button-prev-unique,
+ .swiper-button-next-unique {
+ position: absolute;
+ top: 40%;
+ z-index: 80;
+ width: 50px;
+ height: 50px;
+ background-position: center;
+ background-repeat: no-repeat;
+ }
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 00000000..601c301f
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,20 @@
+import './index.css';
+import '@smastrom/react-rating/style.css';
+
+import React from 'react';
+
+import ReactDOM from 'react-dom/client';
+import { Provider } from 'react-redux';
+
+import { WrappedApp } from './app/App';
+import { setupStore } from './app/store';
+
+const store = setupStore();
+
+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 00000000..400ee446
--- /dev/null
+++ b/src/pages/AboutUs/AboutUs.tsx
@@ -0,0 +1,129 @@
+import gitHubIcon from '../../assets/icons/github.svg';
+import rsSchoolLogo from '../../assets/icons/rs_school_logo.svg';
+import firstUserPic from '../../assets/img/Bogdan.jpg';
+import secondUserPic from '../../assets/img/Harry.jpg';
+import thirdUserPic from '../../assets/img/Oleksii.jpg';
+import PageBackBtn from '../../features/PageBackBtn/PageBackBtn';
+
+export default function AboutUs() {
+ return (
+
+
+
+
+
+
Collaboration
+
+ Our team's success was the result of effective collaboration.
+ We held bi-daily “meat-ups” on messenger to discuss the project, plan collectively, and
+ assign tasks to each member. This regular communication and clear task division kept us aligned,
+ motivated, and ensured a successful project outcome!
+
+
Team members
+
+
+
+
+
+
+
+
+
+
+
+ Bohdan Shcherbyna
+
+
Frontend Developer | Team lead
+
Bio
+
+ I enjoy implementing aesthetically pleasing interfaces, and to pay extra attention to details such as
+ animations or UX.
+
+
Contributions
+
+ Managing the global state of the app, Working with API, UI/UX design, Maintaining the project's
+ vision.
+
+
+
+
+
+
+
+
+
+
+
+
Harry Holubiev
+
Frontend Developer
+
Bio
+
+ Designing and implementing contemporary and user-friendly web-applications is my passion.
+
+
Contributions
+
+ Designed Product Card, Product Page, Logo and animations, implemented some features in Cart component.
+
+
+
+
+
+
+
+
+
+
+
+
Oleksii Drohachov
+
Frontend Developer
+
Bio
+
Switcher from medical field. The old man. :)
+
Contributions
+
+ Team multitool. Set up initial repository. Implemented most of layout and front end logic.
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/Cart/Cart.tsx b/src/pages/Cart/Cart.tsx
new file mode 100644
index 00000000..ff94d9ec
--- /dev/null
+++ b/src/pages/Cart/Cart.tsx
@@ -0,0 +1,324 @@
+import React, { useEffect, useState } from 'react';
+
+import { AnimatePresence, motion, useCycle } from 'framer-motion';
+import { Link, useLocation } from 'react-router-dom';
+
+import {
+ animate,
+ initialAnimation,
+ modalAnimate,
+ modalExit,
+ modalInitial,
+ modalTransition,
+} from './constants/constants.ts';
+import emptyCart from '../../assets/icons/empty-cart-icon.svg';
+import {
+ useCreateCartMutation,
+ useGetCartByIdQuery,
+ useLazyGetCartListQuery,
+ useUpdateCartMutation,
+} from '../../entities/cart';
+import { formatPrice, pennieToMoney } from '../../entities/product';
+import { userSlice } from '../../entities/user';
+import { useAppDispatch, useAppSelector } from '../../shared/lib/hooks';
+import {
+ buttonTapAnimation,
+ buttonTransition,
+ emptyCartAnimate,
+ emptyCartInitial,
+ emptyCartTransition,
+ itemAnimation,
+ itemExit,
+ itemInitial,
+ itemTransition,
+} from '../../shared/ui/animations.tsx';
+import LoadingAnimation from '../../shared/ui/LoadingAnimation.tsx';
+import CartItem from '../../widgets/CartItem/CartItem.tsx';
+
+export default function Cart() {
+ const [promoValue, setPromoValue] = useState('');
+ const [isModalShown, setIsModalShown] = useCycle(false, true);
+ const { cartId, userId, isLogged } = useAppSelector((state) => state.userReducer);
+ const { data: cart } = useGetCartByIdQuery(cartId);
+ const [getCartList] = useLazyGetCartListQuery();
+ const dispatch = useAppDispatch();
+ const { updateCartId } = userSlice.actions;
+ const [createCart] = useCreateCartMutation();
+ const [updateCart] = useUpdateCartMutation();
+ const pathName = useLocation().pathname;
+ const isCart = pathName === '/cart';
+
+ const isCartEmpty = !cart?.totalLineItemQuantity;
+ const cartVersion = cart?.version ?? 1;
+
+ useEffect(() => {
+ async function fetchCreateCart() {
+ try {
+ const newCart = await createCart({ currency: 'USD' }).unwrap();
+
+ if (newCart?.id) dispatch(updateCartId(newCart.id));
+ } catch (e) {
+ if (e && typeof e === 'object' && 'message' in e) throw new Error(`Could not create a cart! ${e.message}`);
+ }
+ }
+
+ async function fetchCartList() {
+ try {
+ const response = await getCartList().unwrap();
+ const id = response.results.find(({ customerId }) => customerId === userId)?.id;
+
+ if (id) dispatch(updateCartId(id));
+ } catch (e) {
+ if (e && typeof e === 'object' && 'message' in e) throw new Error(`Could not query a cart! ${e.message}`);
+ }
+ }
+
+ if (isLogged) fetchCartList();
+ else if (!isLogged && !cartId) {
+ fetchCreateCart();
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [cartId, userId]);
+
+ if (!cart)
+ return (
+
+
+
+ );
+
+ const totalPriceAmount = cart?.totalPrice?.centAmount;
+ const totalPrice = formatPrice(pennieToMoney(totalPriceAmount), cart?.totalPrice?.currencyCode);
+ const discountAmount = cart?.lineItems
+ ?.at(0)
+ ?.discountedPricePerQuantity.at(0)
+ ?.discountedPrice?.includedDiscounts?.at(0)?.discountedAmount.centAmount;
+ const totalItems = cart?.totalLineItemQuantity;
+ const oldPrice = discountAmount ? formatPrice(pennieToMoney(totalPriceAmount + discountAmount * totalItems)) : null;
+
+ async function handleClearCart() {
+ try {
+ if (!cart) return;
+
+ const actions = cart.lineItems.map(({ id }) => {
+ return {
+ action: 'removeLineItem',
+ lineItemId: id,
+ variantId: 1,
+ };
+ });
+
+ updateCart({
+ cartId,
+ body: {
+ version: cartVersion,
+ actions,
+ },
+ });
+ } catch (e) {
+ if (e && typeof e === 'object' && 'message' in e) throw new Error(`Could not clear cart! ${e.message}`);
+ }
+ }
+
+ async function handleApplyPromo(e: React.SyntheticEvent) {
+ e.preventDefault();
+
+ if (!promoValue) return;
+
+ try {
+ const body = {
+ version: cartVersion,
+ actions: [
+ {
+ action: 'addDiscountCode',
+ code: promoValue,
+ },
+ ],
+ };
+
+ updateCart({ cartId, body }).unwrap();
+ } catch (err) {
+ if (err && typeof err === 'object' && 'message' in err) throw new Error(`Could not apply promo! ${err.message}`);
+ }
+ }
+
+ return (
+
+ Your Order
+ {isCartEmpty ? (
+
+
+
+ Your cart is empty!
+
+ Visit
+
+ Product catalog
+ {' '}
+ to find good meals :)
+
+
+
+ ) : (
+
+ {cart.lineItems.map(({ id, productId, quantity }, i) => (
+
+ ))}
+
+
+
+ {oldPrice ? (
+ {oldPrice}
+ ) : null}
+
+ Total Price:
+ {totalPrice}
+
+
+
+
+ CHECKOUT
+
+
+
+
setIsModalShown()}
+ >
+ CLEAR CART
+
+
+
+ {isModalShown && (
+
+ Are you sure?
+
+ setIsModalShown()} className="rounded-md px-2 text-text-grey" type="button">
+ No
+
+ {
+ setIsModalShown();
+ handleClearCart();
+ }}
+ className="rounded-md bg-accent-lightest px-2 text-accent"
+ type="button"
+ >
+ Yes
+
+
+
+ )}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/pages/Cart/constants/constants.ts b/src/pages/Cart/constants/constants.ts
new file mode 100644
index 00000000..9d641d38
--- /dev/null
+++ b/src/pages/Cart/constants/constants.ts
@@ -0,0 +1,33 @@
+export const initialAnimation = {
+ opacity: 0,
+ y: '10%',
+};
+
+export const animate = {
+ opacity: 1,
+ y: 0,
+};
+
+export const modalInitial = {
+ scale: 0,
+ opacity: 0,
+ y: '-100%',
+};
+
+export const modalTransition = {
+ type: 'spring',
+ stiffness: 760,
+ damping: 30,
+};
+
+export const modalAnimate = {
+ scale: 1,
+ opacity: 1,
+ y: '50%',
+};
+
+export const modalExit = {
+ scale: 0,
+ opacity: 0,
+ y: '-100%',
+};
diff --git a/src/pages/ErrorPage/ErrorPage.tsx b/src/pages/ErrorPage/ErrorPage.tsx
new file mode 100644
index 00000000..dd87d9d4
--- /dev/null
+++ b/src/pages/ErrorPage/ErrorPage.tsx
@@ -0,0 +1,23 @@
+import { Link } from 'react-router-dom';
+
+import keanu from '../../assets/img/Sad_Keanu.png';
+
+function ErrorPage() {
+ return (
+
+
+
+
+
404 error
+
+ Sorry, there is no such page for now, or it has never existed...
+ Maybe, you want to check out our main page?
+
+
+ Main page
+
+
+ );
+}
+
+export default ErrorPage;
diff --git a/src/pages/LoginPage/LoginPage.tsx b/src/pages/LoginPage/LoginPage.tsx
new file mode 100644
index 00000000..bf106686
--- /dev/null
+++ b/src/pages/LoginPage/LoginPage.tsx
@@ -0,0 +1,295 @@
+import { useEffect, useRef, useState } from 'react';
+
+import { useFormik } from 'formik';
+import { motion } from 'framer-motion';
+import { Link, useNavigate } from 'react-router-dom';
+import * as Yup from 'yup';
+
+import togglePassVisibility from './model/passwordToggler';
+import emailIcon from '../../assets/icons/emailIcon.svg';
+import emailIconRed from '../../assets/icons/emailIconRed.svg';
+import lockIcon from '../../assets/icons/LockIcon.svg';
+import lockIconRed from '../../assets/icons/LockIconRed.svg';
+import { ErrorModal } from '../../entities/form/ui';
+import { useLoginUser, useLoginUserDataMutation } from '../../entities/user';
+import { validEmail, validPassword } from '../../shared/const/validationSchemas';
+import { getErrorMessage } from '../../shared/lib/helpers';
+import { useAppSelector, useRevokeAccessRefreshTokens } from '../../shared/lib/hooks';
+import { ILoginUserParams } from '../../shared/types';
+import { ErrorMessage, inputAnimation, pageVariants, svgAnimation } from '../../shared/ui';
+
+function LoginPage() {
+ const [loginUser, { error: loginError, isLoading: loginIsLoading }] = useLoginUser();
+ const [getLoginUserData, { error: loginDataError, isLoading: loginDataIsLoading }] = useLoginUserDataMutation();
+ const revokeTokens = useRevokeAccessRefreshTokens();
+ const { accessToken: oldAccessToken, refreshToken: oldRefreshToken } = useAppSelector((state) => state.userReducer);
+ const navigate = useNavigate();
+
+ const passwordInput = useRef(null);
+
+ const loginErrorMessage = getErrorMessage(loginError);
+ const loginDataErrorMessage = getErrorMessage(loginDataError);
+
+ function handleNavigateBackToLogin() {
+ navigate('/login');
+ }
+
+ async function handleSubmit(userData: ILoginUserParams) {
+ try {
+ const {
+ customer: { id },
+ } = await getLoginUserData(userData).unwrap();
+
+ await loginUser(userData.email, userData.password, id);
+
+ navigate('/');
+ revokeTokens(oldAccessToken, oldRefreshToken);
+ } catch (e) {
+ // console.error(`Error occurred while logging the user! (${e.status})`);
+ }
+ }
+
+ const validationSchema = Yup.object({
+ ...validEmail(),
+ ...validPassword(),
+ });
+
+ const formik = useFormik({
+ initialValues: {
+ email: '',
+ password: '',
+ },
+ validationSchema,
+ onSubmit: handleSubmit,
+ });
+
+ const { handleChange, handleBlur, errors, touched, values, setFieldError } = formik;
+
+ const touchedAndErrorEmail = touched.email && errors.email;
+ const touchedAndErrorPassword = touched.password && errors.password;
+
+ const [enableLogin, setEnableLogin] = useState(false);
+
+ useEffect(() => {
+ if (
+ (touched.email === undefined && values.email === '') ||
+ (touched.password === undefined && values.password === '')
+ ) {
+ setEnableLogin(false);
+ return;
+ }
+ if (errors.email || errors.password) {
+ setEnableLogin(false);
+ return;
+ }
+ if (/\s/.test(values.password as string)) {
+ setFieldError('password', "Password can't contain spaces");
+ } else {
+ setFieldError('password', undefined);
+ }
+ setEnableLogin(true);
+ }, [values, errors, touched, setFieldError]);
+
+ return (
+
+ {!loginError && !loginDataError ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export default LoginPage;
diff --git a/src/pages/LoginPage/model/passwordToggler.tsx b/src/pages/LoginPage/model/passwordToggler.tsx
new file mode 100644
index 00000000..1810e60e
--- /dev/null
+++ b/src/pages/LoginPage/model/passwordToggler.tsx
@@ -0,0 +1,9 @@
+export default function togglePassVisibility(target: HTMLInputElement | null) {
+ if (target) {
+ if (target.type === 'password') {
+ (target as HTMLInputElement).type = 'text';
+ } else {
+ (target as HTMLInputElement).type = 'password';
+ }
+ }
+}
diff --git a/src/pages/NavBlock/NavBlock.tsx b/src/pages/NavBlock/NavBlock.tsx
new file mode 100644
index 00000000..ab4bac6a
--- /dev/null
+++ b/src/pages/NavBlock/NavBlock.tsx
@@ -0,0 +1,34 @@
+import UserProfileLink from '../../features/UserProfileLink/UserProfileLink';
+import NavMenu from '../../widgets/NavMenu/NavMenu';
+
+function NavBlock() {
+ return (
+
+
+
+
+ );
+}
+
+export default NavBlock;
diff --git a/src/pages/ProductCatalog/ProductCatalogue.tsx b/src/pages/ProductCatalog/ProductCatalogue.tsx
new file mode 100644
index 00000000..a969f248
--- /dev/null
+++ b/src/pages/ProductCatalog/ProductCatalogue.tsx
@@ -0,0 +1,257 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+/* eslint-disable react/no-array-index-key */
+import React, { useEffect, useState } from 'react';
+
+import { useCycle } from 'framer-motion';
+import InfiniteScroll from 'react-infinite-scroll-component';
+import { useLocation, useSearchParams } from 'react-router-dom';
+
+import { CATEGORIES, QUERY_FILTER, QUERY_SORT } from './const/constants.ts';
+import encodeQueryState from './lib/helpers/encodeQueryState.ts';
+import parseQueryState from './lib/helpers/parseQueryState.ts';
+import filterCalories from './model/filterCalories.ts';
+import FilterModal from './model/FilterModal';
+import filterWeight from './model/filterWeight.ts';
+import SortingSelector from './model/SortingSelector';
+import CategoriesList from './ui/CategoriesList.tsx';
+import CategoryItem from './ui/CategoryItem.tsx';
+import FilterButton from './ui/FilterButton.tsx';
+import MenuList from './ui/MenuList.tsx';
+import ProductNotFound from './ui/ProductNotFound.tsx';
+import ProductPageHeader from './ui/ProductPageHeader';
+import PromoBanner from './ui/PromoBanner.tsx';
+import { useGetCategoriesQuery, useGetCategoryQuery, useLazyGetProductListQuery } from '../../entities/product';
+import { ProductAttributeNames, ProductSortingFields, ProductSortOrders } from '../../entities/product/types/enums.ts';
+import { CategoryResult, ProductResponse } from '../../entities/product/types/types.ts';
+import SearchInput from '../../features/SearchInput/SearchInput.tsx';
+import { capitalize } from '../../shared/lib/helpers';
+import { useGetPath } from '../../shared/lib/hooks';
+import { Blackout } from '../../shared/ui';
+import LoadingAnimation from '../../shared/ui/LoadingAnimation.tsx';
+import MenuItem from '../../widgets/MenuItem/MenuItem.tsx';
+import getAttribute from '../ProductPage/lib/helpers/getAttribute.ts';
+
+export default function ProductCatalogue() {
+ const path = useGetPath();
+ const urlActiveCat = capitalize(decodeURIComponent(path));
+
+ const { pathname } = useLocation();
+ const [query, setQuery] = useSearchParams();
+ const [isFiltersOpen, onFilterOpen] = useCycle(false, true);
+ const [sortOrder, setSortOrder] = useState(query.get(QUERY_SORT) ?? 'price desc');
+ const [productItems, setProductItems] = useState();
+ const [getProductList, { data: rawProductListData, isSuccess: productsIsSuccess, isLoading: productsIsLoading }] =
+ useLazyGetProductListQuery();
+ const { data: categoriesList } = useGetCategoriesQuery();
+ const [activeCat, setActiveCat] = useState(urlActiveCat ?? 'All');
+ const { data: categoryData } = useGetCategoryQuery(urlActiveCat.toLowerCase().replace(' ', '-'));
+ const [filtersState, setFiltersState] = useState(parseQueryState(query));
+
+ const productListData = { ...productItems };
+ const prevCategories = pathname
+ .replace(CATEGORIES, '')
+ .split('/')
+ .map((elem) => capitalize(decodeURIComponent(elem)))
+ .filter(Boolean);
+ let categories: CategoryResult[] | null = null;
+
+ if (categoriesList && categoryData) {
+ categories = categoriesList.results.filter((res) => res?.parent?.id === categoryData.id);
+ }
+
+ if (productListData && productItems && !isFiltersOpen) {
+ if (filtersState.calories !== '') {
+ productListData.results = filterCalories(productItems, Number(filtersState.calories));
+ }
+
+ if (filtersState.weight !== '') {
+ productListData.results = filterWeight(productItems, Number(filtersState.weight));
+ }
+ }
+
+ function pushQuery(...data: string[][]) {
+ data.forEach(([name, state]) => query.set(name, state));
+ setQuery(query);
+ }
+
+ function fetchProducts(categoryId?: string, offset: number = 0) {
+ const [currField, order] = sortOrder.split(' ') as [ProductSortingFields, ProductSortOrders];
+ const field = ProductSortingFields[currField as unknown as keyof typeof ProductSortingFields];
+
+ if (categoryId) setFiltersState((prevState) => ({ ...prevState, categoryId }));
+
+ getProductList(
+ {
+ limit: 5,
+ offset,
+ sort: {
+ field,
+ order,
+ },
+ filters: { ...filtersState, ...(categoryId && { categoryId }) },
+ searchQuery: query.get('search'),
+ },
+ true,
+ );
+ }
+
+ function changeActiveCat(e: React.MouseEvent) {
+ const {
+ dataset: { userSelect },
+ } = e.target as HTMLElement;
+
+ if (userSelect && userSelect !== activeCat) {
+ setActiveCat(userSelect);
+ setProductItems(undefined);
+ }
+ }
+
+ function onSort(value: React.SetStateAction) {
+ setSortOrder(value);
+ setProductItems(undefined);
+ }
+
+ function handleNextPage() {
+ fetchProducts(undefined, (productListData?.offset ?? 0) + 5);
+ }
+
+ function onApplyFilters() {
+ const encodedState = encodeQueryState(filtersState);
+
+ onFilterOpen();
+
+ if (query.get(QUERY_FILTER) === encodedState) return;
+
+ fetchProducts();
+ setProductItems(undefined);
+ pushQuery([QUERY_FILTER, encodedState]);
+ }
+
+ useEffect(() => {
+ if (query.get(QUERY_SORT) !== sortOrder) {
+ pushQuery([QUERY_SORT, sortOrder]);
+ fetchProducts();
+ }
+ }, [sortOrder]);
+
+ useEffect(() => {
+ setProductItems((prev) => {
+ if (!rawProductListData) return prev;
+ if (!prev?.results) return rawProductListData;
+
+ return {
+ ...rawProductListData,
+ results: [...prev.results, ...rawProductListData.results],
+ };
+ });
+ }, [rawProductListData]);
+
+ useEffect(() => {
+ if (categoryData) fetchProducts(categoryData.id);
+ }, [categoryData]);
+
+ return (
+ <>
+ onFilterOpen()} />
+
+
+
+
+
+
+
+
+
+
+ {categories
+ ? [...prevCategories, ...categories].map((item, i, arr) => {
+ const isPrevCat = typeof item === 'string';
+ const name = isPrevCat ? item : item.name.en;
+ const isLast = i === arr.length - 1;
+
+ return (
+
+
+ {!isLast && (activeCat === name || isPrevCat) ? '/' : ''}
+
+ );
+ })
+ : null}
+
+
+ {productsIsLoading && (
+
+
+
+ )}
+
+ {!productListData?.results?.length && !rawProductListData?.results?.length && productsIsSuccess ? (
+
+ ) : null}
+
+ {productListData?.results?.length && productListData.total && productListData.offset !== undefined ? (
+
+
+
+ }
+ endMessage={You Reached The End!
}
+ className="grid items-center gap-5 pb-14 lg:gap-6 lg:pb-0"
+ >
+ {productListData.results?.map(({ id, name, masterVariant: { prices, images, attributes } }, i) => (
+
+ ))}
+
+ ) : null}
+
+
+ >
+ );
+}
diff --git a/src/pages/ProductCatalog/const/constants.ts b/src/pages/ProductCatalog/const/constants.ts
new file mode 100644
index 00000000..31cf4c69
--- /dev/null
+++ b/src/pages/ProductCatalog/const/constants.ts
@@ -0,0 +1,4 @@
+export const QUERY_FILTER = 'filter';
+export const QUERY_SORT = 'sort';
+export const CATEGORIES = '/categories';
+export const MOBILE_WIDTH = 1023;
diff --git a/src/pages/ProductCatalog/lib/helpers/encodeQueryState.ts b/src/pages/ProductCatalog/lib/helpers/encodeQueryState.ts
new file mode 100644
index 00000000..87b4a215
--- /dev/null
+++ b/src/pages/ProductCatalog/lib/helpers/encodeQueryState.ts
@@ -0,0 +1,16 @@
+import { FiltersFields } from '../../model/filtersInitialState.ts';
+
+/**
+ * Encodes the query state object to a string.
+ *
+ * @param {URLSearchParams} state - The query state object to be encoded.
+ * @returns {string} - The encoded query state string.
+ */
+function encodeQueryState(state: FiltersFields): string {
+ return Object.entries(state)
+ .filter(([, val]) => Boolean(val))
+ .map((el) => el.join('_'))
+ .join(',');
+}
+
+export default encodeQueryState;
diff --git a/src/pages/ProductCatalog/lib/helpers/parseQueryState.ts b/src/pages/ProductCatalog/lib/helpers/parseQueryState.ts
new file mode 100644
index 00000000..12610733
--- /dev/null
+++ b/src/pages/ProductCatalog/lib/helpers/parseQueryState.ts
@@ -0,0 +1,20 @@
+import { FiltersFields, filtersInitialState } from '../../model/filtersInitialState.ts';
+
+/**
+ * Parses the query state from a URL search params object.
+ *
+ * @param {URLSearchParams} query - The URL search params object to parse.
+ * @returns {FiltersFields} - The parsed query state object.
+ */
+function parseQueryState(query: URLSearchParams): FiltersFields {
+ const filters = query.get('filter');
+ if (!filters) return filtersInitialState;
+
+ const parsed = Object.fromEntries(
+ filters!.split(',').map((elem) => elem.split('_').map((val) => (val === 'true' ? true : val))),
+ );
+
+ return { ...filtersInitialState, ...parsed };
+}
+
+export default parseQueryState;
diff --git a/src/pages/ProductCatalog/model/FilterModal.tsx b/src/pages/ProductCatalog/model/FilterModal.tsx
new file mode 100644
index 00000000..ef498687
--- /dev/null
+++ b/src/pages/ProductCatalog/model/FilterModal.tsx
@@ -0,0 +1,193 @@
+import React, { useCallback, useEffect, useRef } from 'react';
+
+import { AnimatePresence, Cycle, motion } from 'framer-motion';
+
+import FilterModalCheckbox from './FilterModalCheckbox';
+import FilterModalNumberInput from './FilterModalNumberInput';
+import { FiltersFields, filtersInitialState } from './filtersInitialState.ts';
+import { ProductAttributeNames, useLazyGetProductListQuery } from '../../../entities/product';
+import { MOBILE_WIDTH } from '../const/constants.ts';
+
+const initialDesktop = { y: '-63%', x: '40%', scale: 0, opacity: 0 };
+const initialMobile = { y: '-60%', x: '-40%', scale: 0, opacity: 0 };
+
+export default function FilterModal(props: {
+ isFiltersOpen: boolean;
+ filtersState: FiltersFields;
+ onFilterOpen: Cycle;
+ setFiltersState: React.Dispatch>;
+ onApplyFilters: () => void;
+}) {
+ const { isFiltersOpen, onFilterOpen, filtersState, setFiltersState, onApplyFilters } = props;
+ const [getProducts, { data: productData }] = useLazyGetProductListQuery();
+ const veganResults = useRef(0);
+ const spicyResults = useRef(0);
+ const promoResults = useRef(0);
+
+ const clientWidth = window.innerWidth;
+ const isMobile = clientWidth > MOBILE_WIDTH;
+ const initial = isMobile ? initialDesktop : initialMobile;
+
+ const universalFilterChanger = (value: string | boolean, field: string) => {
+ setFiltersState((prev) => {
+ return { ...prev, [field]: value };
+ });
+ };
+
+ if (productData && (!veganResults.current || !spicyResults.current)) {
+ productData.results.forEach(({ masterVariant: { attributes, prices } }) => {
+ if (prices.at(0)?.discounted) {
+ promoResults.current += 1;
+ }
+
+ attributes.forEach(({ name, value }) => {
+ if (name === ProductAttributeNames.IS_VEGAN && value) {
+ veganResults.current += 1;
+ }
+
+ if (name === ProductAttributeNames.IS_SPICY && value) {
+ spicyResults.current += 1;
+ }
+ });
+ });
+ }
+
+ const getProductList = useCallback(() => {
+ getProducts(
+ {
+ limit: 100,
+ withTotal: false,
+ },
+ true,
+ );
+ }, [getProducts]);
+
+ useEffect(() => {
+ getProductList();
+ }, [getProductList]);
+
+ return (
+
+ {isFiltersOpen ? (
+
+ Filters
+ onFilterOpen()}
+ className="absolute right-4 top-4 cursor-pointer text-2xl font-semibold text-text-grey transition-all ease-in hover:text-text-dark"
+ >
+ ร
+
+
+
+
Show only:
+
+
+
+
+
+
Set max:
+
+
+
+
+
+
+ {
+ setFiltersState(filtersInitialState);
+ }}
+ type="button"
+ className="w-1/2 rounded-lg border-1 bg-primary px-1.5 py-2 text-text-grey transition-all
+ hover:bg-separation-line"
+ >
+ Reset
+
+
+ Apply
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/pages/ProductCatalog/model/FilterModalCheckbox.tsx b/src/pages/ProductCatalog/model/FilterModalCheckbox.tsx
new file mode 100644
index 00000000..18663879
--- /dev/null
+++ b/src/pages/ProductCatalog/model/FilterModalCheckbox.tsx
@@ -0,0 +1,70 @@
+import { AnimateCounter } from '../../../shared/ui';
+
+export default function FilterModalCheckbox(props: {
+ id: string;
+ checked: boolean;
+ universalFilterChanger: (value: string | boolean, field: string) => void;
+ text: string;
+ peer: string;
+ itemsNum: number;
+}) {
+ const { id, checked, universalFilterChanger, text, peer, itemsNum } = props;
+ return (
+
+
universalFilterChanger(!checked, id)}
+ className={`
+ peer/${id}
+ mr-5
+ h-5
+ w-5
+ cursor-pointer
+ appearance-none
+ rounded-md
+ bg-accent
+ `}
+ />
+
+ {text}
+
+
+
+ );
+}
diff --git a/src/pages/ProductCatalog/model/FilterModalNumberInput.tsx b/src/pages/ProductCatalog/model/FilterModalNumberInput.tsx
new file mode 100644
index 00000000..7c2a765e
--- /dev/null
+++ b/src/pages/ProductCatalog/model/FilterModalNumberInput.tsx
@@ -0,0 +1,22 @@
+export default function FilterModalNumberInput(props: {
+ id: string;
+ value: string;
+ universalFilterChanger: (value: string | boolean, field: string) => void;
+ text: string;
+}) {
+ const { id, value, universalFilterChanger, text } = props;
+ const placeholder = `${text}...`;
+ return (
+
+ universalFilterChanger(e.target.value, id)}
+ />
+ {text}
+
+ );
+}
diff --git a/src/pages/ProductCatalog/model/SortingSelector.tsx b/src/pages/ProductCatalog/model/SortingSelector.tsx
new file mode 100644
index 00000000..7864a02e
--- /dev/null
+++ b/src/pages/ProductCatalog/model/SortingSelector.tsx
@@ -0,0 +1,36 @@
+export default function SortingSelector(props: {
+ sortOrder: string;
+ onSort: (value: React.SetStateAction) => void;
+}) {
+ const { sortOrder, onSort } = props;
+ return (
+
+ Sort by:
+ onSort(e.target.value)}
+ className="cursor-pointer appearance-none px-1 text-text-dark dark:bg-dark-bg-primary dark:text-primary"
+ >
+ {/* */}
+ {/* Rate โผ */}
+ {/* */}
+ {/* */}
+ {/* Rate โฒ */}
+ {/* */}
+
+ Price โผ
+
+
+ Price โฒ
+
+
+ ABC โผ
+
+
+ ABC โฒ
+
+
+
+ );
+}
diff --git a/src/pages/ProductCatalog/model/filterCalories.ts b/src/pages/ProductCatalog/model/filterCalories.ts
new file mode 100644
index 00000000..1c1ef7c1
--- /dev/null
+++ b/src/pages/ProductCatalog/model/filterCalories.ts
@@ -0,0 +1,18 @@
+import { ProductAttributeNames } from '../../../entities/product';
+import { ProductResponse } from '../../../entities/product/types/types.ts';
+import getAttribute from '../../ProductPage/lib/helpers/getAttribute.ts';
+
+/**
+ * Filters the product list based on maximum calories.
+ *
+ * @param {ProductResponse} rawProductListData - The raw product list data.
+ * @param {number} maxCalories - The maximum number of calories.
+ * @returns {ProductResponse[]} - The filtered product list.
+ */
+function filterCalories(rawProductListData: ProductResponse, maxCalories: number) {
+ return rawProductListData.results.filter(
+ (product) => Number(getAttribute(product.masterVariant.attributes, ProductAttributeNames.CALORIES)) < maxCalories,
+ );
+}
+
+export default filterCalories;
diff --git a/src/pages/ProductCatalog/model/filterWeight.ts b/src/pages/ProductCatalog/model/filterWeight.ts
new file mode 100644
index 00000000..6bed2d91
--- /dev/null
+++ b/src/pages/ProductCatalog/model/filterWeight.ts
@@ -0,0 +1,18 @@
+import { ProductAttributeNames } from '../../../entities/product';
+import { ProductResponse } from '../../../entities/product/types/types.ts';
+import getAttribute from '../../ProductPage/lib/helpers/getAttribute.ts';
+
+/**
+ * Filters the raw product list data based on the maximum weight.
+ *
+ * @param {ProductResponse} rawProductListData - The raw product list data to be filtered.
+ * @param {number} maxWeight - The maximum weight to filter the products.
+ * @return {ProductResponse[]} The filtered product list based on the maximum weight.
+ */
+function filterWeight(rawProductListData: ProductResponse, maxWeight: number) {
+ return rawProductListData.results.filter(
+ (product) => Number(getAttribute(product.masterVariant.attributes, ProductAttributeNames.WEIGHT)) < maxWeight,
+ );
+}
+
+export default filterWeight;
diff --git a/src/pages/ProductCatalog/model/filtersInitialState.ts b/src/pages/ProductCatalog/model/filtersInitialState.ts
new file mode 100644
index 00000000..af3848a5
--- /dev/null
+++ b/src/pages/ProductCatalog/model/filtersInitialState.ts
@@ -0,0 +1,19 @@
+export type FiltersFields = {
+ isVegan: boolean;
+ isSpicy: boolean;
+ isPromo: boolean;
+ price: string;
+ calories: string;
+ weight: string;
+ categoryId: string;
+};
+
+export const filtersInitialState: FiltersFields = {
+ isVegan: false,
+ isSpicy: false,
+ isPromo: false,
+ price: '',
+ calories: '',
+ weight: '',
+ categoryId: '',
+};
diff --git a/src/pages/ProductCatalog/ui/CategoriesList.tsx b/src/pages/ProductCatalog/ui/CategoriesList.tsx
new file mode 100644
index 00000000..e37c7935
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/CategoriesList.tsx
@@ -0,0 +1,43 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import React from 'react';
+
+interface ICategoryList {
+ children: React.ReactNode;
+ changeActiveCat: (e: React.MouseEvent) => void;
+}
+
+function CategoriesList({ children, changeActiveCat }: ICategoryList) {
+ return (
+
+
) => changeActiveCat(e)}
+ className="
+ flex
+ h-8
+ items-center
+ gap-2
+ overflow-y-scroll
+ border-b-[2px]
+ border-separation-line
+ dark:border-dark-separation-line
+ md:h-9
+ "
+ >
+ {children}
+
+
+ );
+}
+
+export default CategoriesList;
diff --git a/src/pages/ProductCatalog/ui/CategoryItem.tsx b/src/pages/ProductCatalog/ui/CategoryItem.tsx
new file mode 100644
index 00000000..2fa6184d
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/CategoryItem.tsx
@@ -0,0 +1,91 @@
+import React from 'react';
+
+import { motion } from 'framer-motion';
+import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
+
+interface ICategoryItemProps {
+ item: string;
+ activeCat: string;
+ children?: React.ReactNode | null;
+ index: number;
+}
+
+function CategoryItem({ item, activeCat, index, children = null }: ICategoryItemProps) {
+ const [query] = useSearchParams();
+ const navigate = useNavigate();
+ const { pathname } = useLocation();
+
+ const isActive = activeCat === item;
+ const path = decodeURIComponent(pathname);
+ const name = item.toLowerCase();
+ const isPrevCategory = path.includes(name);
+
+ function goBack() {
+ const itemIndex = path.indexOf(name);
+ const prevCategory = `${path.slice(0, itemIndex)}${name}`;
+
+ navigate({
+ pathname: prevCategory,
+ search: query.toString(),
+ });
+ }
+
+ function goForward() {
+ navigate({
+ pathname: `${pathname}/${item.toLowerCase()}`,
+ search: query.toString(),
+ });
+ }
+
+ function handleClick() {
+ if (activeCat === item) return;
+
+ if (isPrevCategory) {
+ goBack();
+ } else {
+ goForward();
+ }
+ }
+
+ return (
+
+
+ {item}
+ {isActive ? (
+
+ ) : null}
+
+ {children}
+
+ );
+}
+
+CategoryItem.defaultProps = {
+ children: null,
+};
+
+export default CategoryItem;
diff --git a/src/pages/ProductCatalog/ui/FilterButton.tsx b/src/pages/ProductCatalog/ui/FilterButton.tsx
new file mode 100644
index 00000000..5e481db5
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/FilterButton.tsx
@@ -0,0 +1,40 @@
+import { Cycle } from 'framer-motion';
+
+import filterIcon from '../../../assets/icons/FiltersIcon.svg';
+
+interface IFilterButton {
+ onFilterOpen: Cycle;
+ isFiltersOpen: boolean;
+}
+
+function FilterButton({ onFilterOpen, isFiltersOpen }: IFilterButton) {
+ return (
+ {
+ onFilterOpen();
+ }}
+ className={`
+ ${isFiltersOpen ? 'z-40' : 'z-20'}
+ flex
+ items-center
+ rounded-lg
+ border-[1.5px]
+ border-text-grey
+ bg-primary
+ px-[12px]
+ py-[10px]
+ transition-all
+ hover:bg-secondary
+ dark:bg-dark-bg-primary
+ dark:text-primary
+ dark:hover:bg-text-grey
+ `}
+ >
+
+ Filters
+
+ );
+}
+
+export default FilterButton;
diff --git a/src/pages/ProductCatalog/ui/MenuList.tsx b/src/pages/ProductCatalog/ui/MenuList.tsx
new file mode 100644
index 00000000..af8350f0
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/MenuList.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+interface IMenuListProps {
+ children: React.ReactNode;
+}
+
+function MenuList({ children }: IMenuListProps) {
+ return (
+
+ );
+}
+
+export default MenuList;
diff --git a/src/pages/ProductCatalog/ui/ProductNotFound.tsx b/src/pages/ProductCatalog/ui/ProductNotFound.tsx
new file mode 100644
index 00000000..3aee2dd9
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/ProductNotFound.tsx
@@ -0,0 +1,51 @@
+import { motion } from 'framer-motion';
+
+import emptyCart from '../../../assets/icons/empty-cart.svg';
+
+function ProductNotFound() {
+ return (
+
+
+
+ No Products Found
+
+
+ Your search did not match any products.
+
+ Please try again.
+
+
+ );
+}
+
+export default ProductNotFound;
diff --git a/src/pages/ProductCatalog/ui/ProductPageHeader.tsx b/src/pages/ProductCatalog/ui/ProductPageHeader.tsx
new file mode 100644
index 00000000..7a3bfc4d
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/ProductPageHeader.tsx
@@ -0,0 +1,8 @@
+export default function ProductPageHeader() {
+ return (
+
+
Choose your ideal Meal
+ We hope our meals will improve your day :)
+
+ );
+}
diff --git a/src/pages/ProductCatalog/ui/PromoBanner.tsx b/src/pages/ProductCatalog/ui/PromoBanner.tsx
new file mode 100644
index 00000000..b5800a12
--- /dev/null
+++ b/src/pages/ProductCatalog/ui/PromoBanner.tsx
@@ -0,0 +1,70 @@
+import { useState } from 'react';
+
+import { AnimatePresence, motion } from 'framer-motion';
+
+const PROMOCODE = 'myLovelyPromoCode';
+
+export default function PromoBanner() {
+ const [showMessage, setShowMessage] = useState(false);
+
+ const handleButtonClick = () => {
+ setShowMessage(true);
+ setTimeout(() => {
+ setShowMessage(false);
+ }, 1000);
+ };
+
+ return (
+
+
+
15% flat off on all items only today!
+
+
+ {showMessage ? (
+
+
+ Text Copied!
+
+
+ ) : (
+
+ {PROMOCODE}
+
+ )}
+
+
{
+ handleButtonClick();
+ navigator.clipboard.writeText(PROMOCODE);
+ }}
+ >
+ Copy Code
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/ProductPage/ProductPage.tsx b/src/pages/ProductPage/ProductPage.tsx
new file mode 100644
index 00000000..4d653bdb
--- /dev/null
+++ b/src/pages/ProductPage/ProductPage.tsx
@@ -0,0 +1,148 @@
+import { useEffect, useState } from 'react';
+
+import getAttribute from './lib/helpers/getAttribute.ts';
+import AddWishlistMobile from './ui/AddWishlistMobile.tsx';
+import Allergens from './ui/Allergens.tsx';
+import BackButton from './ui/BackButton.tsx';
+import Description from './ui/Description.tsx';
+import Footer from './ui/Footer.tsx';
+import Header from './ui/Header.tsx';
+import HeaderMobile from './ui/HeaderMobile.tsx';
+import ImageSlider from './ui/ImageSlider';
+import Ingredient from './ui/Ingredient.tsx';
+import IngredientList from './ui/IngredientList.tsx';
+import Price from './ui/Price.tsx';
+import Rating from './ui/Rating.tsx';
+import Title from './ui/Title.tsx';
+import TitleAbout from './ui/TitleAbout.tsx';
+import { ProductAttributeNames, useGetProductQuery } from '../../entities/product';
+import 'swiper/css';
+import { ProductPrice } from '../../entities/product/types/types.ts';
+import { DEFAULT_TITLE } from '../../shared/const';
+import { useGetPath } from '../../shared/lib/hooks';
+import LoadingAnimation from '../../shared/ui/LoadingAnimation.tsx';
+
+export default function ProductPage() {
+ const [rating, setRating] = useState(4.3);
+ const [isSliderOpen, setSliderOpen] = useState(false);
+ const productId = useGetPath();
+ const { data } = useGetProductQuery(productId);
+
+ /// add to cart btn will change to remove from cart an this function will be called
+ // const removeAllFromCart = async () => {
+ // try {
+ // const targetItem = cart?.lineItems.filter((item) => item.productId === productId);
+ // const lineItemId = targetItem[0].id;
+ // const body = {
+ // version: cart?.version || 1,
+ // actions: [
+ // {
+ // action: 'removeLineItem',
+ // lineItemId,
+ // variantId: 1,
+ // },
+ // ],
+ // };
+ // const result = await updateLineItem({ cartId, body }).unwrap();
+ // console.log('updated cart', result.lineItems);
+ // } catch (error) {
+ // console.error(error);
+ // }
+ // };
+
+ const handleSliderOpen = () => {
+ setSliderOpen(true);
+ };
+
+ const handleCloseSlider = () => {
+ setSliderOpen(false);
+ };
+
+ useEffect(() => {
+ if (!data) return;
+ const title = data.name.en;
+ document.title = title;
+ // eslint-disable-next-line consistent-return
+ return () => {
+ document.title = DEFAULT_TITLE;
+ };
+ });
+
+ if (!data)
+ return (
+
+
+
+ );
+
+ const {
+ masterVariant: { attributes, prices, images },
+ name: { en },
+ } = data;
+
+ const {
+ discounted: { value: { centAmount: discountPrice = undefined } = {} } = {},
+ value: { centAmount: currPrice, currencyCode },
+ } = prices.at(0) as ProductPrice;
+
+ const centPrice = discountPrice ?? currPrice;
+ const centOldPrice = discountPrice ? currPrice : null;
+
+ const isSpicy = Boolean(getAttribute(attributes, ProductAttributeNames.IS_SPICY));
+ const isVegan = Boolean(getAttribute(attributes, ProductAttributeNames.IS_VEGAN));
+ const name = en;
+
+ const imgList = images.map((img) => img.url);
+
+ const ingredients = getAttribute(attributes, ProductAttributeNames.INGREDIENTS)?.toString()?.split(', ');
+ const calories = getAttribute(attributes, ProductAttributeNames.CALORIES);
+ const weight = getAttribute(attributes, ProductAttributeNames.WEIGHT);
+ const allergens = getAttribute(attributes, ProductAttributeNames.ALLERGENS);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {ingredients ? (
+
+ <>
+ {ingredients.map((ing) => (
+
+ ))}
+ >
+
+ ) : null}
+ {typeof allergens === 'string' ?
: null}
+
+
+
+
+
+ );
+}
diff --git a/src/pages/ProductPage/lib/helpers/getAttribute.ts b/src/pages/ProductPage/lib/helpers/getAttribute.ts
new file mode 100644
index 00000000..b0c53955
--- /dev/null
+++ b/src/pages/ProductPage/lib/helpers/getAttribute.ts
@@ -0,0 +1,14 @@
+import { ProductAttribute, ProductAttributeNames } from '../../../../entities/product';
+
+/**
+ * Returns the product attribute object with the specified name 'ingredients'.
+ *
+ * @param {ProductAttribute[]} attribute - An array of product attribute objects.
+ * @param {string} name - An attributes name
+ * @return {ProductAttribute['value']} - The product attribute object with name 'ingredients', or undefined if not found.
+ */
+function getAttribute(attribute: ProductAttribute[], name: ProductAttributeNames): ProductAttribute['value'] {
+ return attribute.find((obj) => obj.name === name)?.value as ProductAttribute['value'];
+}
+
+export default getAttribute;
diff --git a/src/pages/ProductPage/ui/AddWishlistMobile.tsx b/src/pages/ProductPage/ui/AddWishlistMobile.tsx
new file mode 100644
index 00000000..2a876244
--- /dev/null
+++ b/src/pages/ProductPage/ui/AddWishlistMobile.tsx
@@ -0,0 +1,14 @@
+import heart from '../../../assets/icons/heart.svg';
+
+function AddWishlistMobile() {
+ return (
+
+
+
+ );
+}
+
+export default AddWishlistMobile;
diff --git a/src/pages/ProductPage/ui/Allergens.tsx b/src/pages/ProductPage/ui/Allergens.tsx
new file mode 100644
index 00000000..cdc0a17c
--- /dev/null
+++ b/src/pages/ProductPage/ui/Allergens.tsx
@@ -0,0 +1,15 @@
+import { capitalize } from '../../../shared/lib/helpers';
+
+export default function Allergens(props: { allergens: string }) {
+ const { allergens } = props;
+ return (
+
+
Allergens
+
+ {allergens.split(',').map((allergen) => {
+ return {`${capitalize(allergen)}`} ;
+ })}
+
+
+ );
+}
diff --git a/src/pages/ProductPage/ui/BackButton.tsx b/src/pages/ProductPage/ui/BackButton.tsx
new file mode 100644
index 00000000..8907aa71
--- /dev/null
+++ b/src/pages/ProductPage/ui/BackButton.tsx
@@ -0,0 +1,23 @@
+import { useNavigate } from 'react-router-dom';
+
+import arrowLeft from '../../../assets/icons/arrowLeft.svg';
+
+function BackButton() {
+ const navigate = useNavigate();
+
+ function handleBack() {
+ navigate(-1);
+ }
+
+ return (
+
+
+
+ );
+}
+
+export default BackButton;
diff --git a/src/pages/ProductPage/ui/Description.tsx b/src/pages/ProductPage/ui/Description.tsx
new file mode 100644
index 00000000..f9187e9e
--- /dev/null
+++ b/src/pages/ProductPage/ui/Description.tsx
@@ -0,0 +1,28 @@
+import { ProductAttribute, ProductAttributeNames } from '../../../entities/product';
+import { capitalize } from '../../../shared/lib/helpers';
+
+interface IDescriptionProps {
+ attributes: ProductAttribute[];
+}
+
+function Description({ attributes }: IDescriptionProps) {
+ return (
+
+
Description
+
+ {attributes.map((attr, index) => {
+ if (
+ index === 0 ||
+ attr.name === ProductAttributeNames.IS_SPICY ||
+ attr.name === ProductAttributeNames.IS_VEGAN ||
+ attr.name === ProductAttributeNames.ALLERGENS
+ )
+ return null;
+ return {`${capitalize(attr.name)} - ${attr.value}`} ;
+ })}
+
+
+ );
+}
+
+export default Description;
diff --git a/src/pages/ProductPage/ui/Footer.tsx b/src/pages/ProductPage/ui/Footer.tsx
new file mode 100644
index 00000000..c35704c0
--- /dev/null
+++ b/src/pages/ProductPage/ui/Footer.tsx
@@ -0,0 +1,136 @@
+import { motion } from 'framer-motion';
+
+import MinusIcon from './MinusIcon';
+import PlusIcon from './PlusIcon';
+import heartAccent from '../../../assets/icons/heart-accent.svg';
+import shoppingCart from '../../../assets/icons/shopping-cart-accent.svg';
+import { useGetCartByIdQuery, useUpdateCartMutation } from '../../../entities/cart';
+import { AddLineItemRequestBody, RemoveLineItemRequestBody } from '../../../entities/cart/types/types.ts';
+import { padZero } from '../../../shared/lib/helpers';
+import { useAppSelector, useGetPath } from '../../../shared/lib/hooks';
+import { LineItem } from '../../../shared/types';
+import { buttonTapAnimation, buttonTransition } from '../../../shared/ui/animations.tsx';
+
+function Footer() {
+ const { cartId } = useAppSelector((state) => state.userReducer);
+ const { data: cart } = useGetCartByIdQuery(cartId);
+ const productId = useGetPath();
+ const [updateLineItem, { isLoading: updateIsLoading }] = useUpdateCartMutation();
+
+ if (!cart) return null;
+
+ const { lineItems } = cart;
+ const currItem = lineItems?.find((item) => item?.productId === productId) as LineItem;
+ const quantity = currItem?.quantity ?? 0;
+ const lineItemId = currItem?.id ?? '';
+
+ const removeOneFromCart = async () => {
+ try {
+ const body: RemoveLineItemRequestBody = {
+ version: cart?.version || 1,
+ actions: [
+ {
+ action: 'removeLineItem',
+ lineItemId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ };
+
+ updateLineItem({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ };
+
+ const removeAllFromCart = async () => {
+ try {
+ const body: RemoveLineItemRequestBody = {
+ version: cart?.version || 1,
+ actions: [
+ {
+ action: 'removeLineItem',
+ lineItemId,
+ variantId: 1,
+ },
+ ],
+ };
+ updateLineItem({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ return null;
+ };
+
+ const addToCart = async () => {
+ const body: AddLineItemRequestBody = {
+ version: cart?.version || 1,
+ actions: [
+ {
+ action: 'addLineItem',
+ productId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ };
+ try {
+ return await updateLineItem({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ return null;
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
{padZero(quantity)}
+
+
+
+
+
0 ? removeAllFromCart : addToCart}
+ type="button"
+ className="mt-3 block w-full rounded-md bg-accent-lightest px-4 py-3 text-accent transition-all dark:border-2 dark:border-text-grey dark:bg-dark-bg-primary dark:hover:bg-dark-separation-line md:mt-0 md:w-fit"
+ >
+
+
+ {quantity > 0 ? 'Remove from Cart' : 'Add to Cart'}
+
+
+
+ );
+}
+
+export default Footer;
diff --git a/src/pages/ProductPage/ui/Header.tsx b/src/pages/ProductPage/ui/Header.tsx
new file mode 100644
index 00000000..61fec68b
--- /dev/null
+++ b/src/pages/ProductPage/ui/Header.tsx
@@ -0,0 +1,9 @@
+interface IHeaderProps {
+ children: React.ReactElement;
+}
+
+function Header({ children }: IHeaderProps) {
+ return {children}
;
+}
+
+export default Header;
diff --git a/src/pages/ProductPage/ui/HeaderMobile.tsx b/src/pages/ProductPage/ui/HeaderMobile.tsx
new file mode 100644
index 00000000..78567afe
--- /dev/null
+++ b/src/pages/ProductPage/ui/HeaderMobile.tsx
@@ -0,0 +1,29 @@
+import spicyIcon from '../../../assets/icons/spicy.svg';
+import veganIcon from '../../../assets/icons/vegan.svg';
+
+interface IHeaderMobileProps {
+ name: string;
+ calories: number | string;
+ weight: number | string;
+ isSpicy: boolean;
+ isVegan: boolean;
+}
+
+function HeaderMobile({ name, calories, weight, isSpicy, isVegan }: IHeaderMobileProps) {
+ return (
+
+
+ {name}
+ {isSpicy || isVegan ? (
+
+
+
+ ) : null}
+
+
{calories} kcal
+
{weight} g
+
+ );
+}
+
+export default HeaderMobile;
diff --git a/src/pages/ProductPage/ui/ImageSlider.tsx b/src/pages/ProductPage/ui/ImageSlider.tsx
new file mode 100644
index 00000000..78086232
--- /dev/null
+++ b/src/pages/ProductPage/ui/ImageSlider.tsx
@@ -0,0 +1,84 @@
+import { motion } from 'framer-motion';
+import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
+import { Navigation, Pagination } from 'swiper/modules';
+import { Swiper, SwiperSlide } from 'swiper/react';
+
+import 'swiper/css';
+import 'swiper/css/navigation';
+import 'swiper/css/pagination';
+import { buttonTapAnimation, buttonTransition } from '../../../shared/ui/animations.tsx';
+
+interface IImageSlider {
+ isOpen: boolean;
+ imgList: string[];
+ onClose: () => void;
+}
+
+const crossSvg = (
+
+
+
+
+);
+
+export default function ImageSlider({ isOpen, onClose, imgList }: IImageSlider) {
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+
+
+ {imgList.map((img) => (
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ {crossSvg}
+
+
+
+ );
+}
diff --git a/src/pages/ProductPage/ui/Ingredient.tsx b/src/pages/ProductPage/ui/Ingredient.tsx
new file mode 100644
index 00000000..f19819da
--- /dev/null
+++ b/src/pages/ProductPage/ui/Ingredient.tsx
@@ -0,0 +1,17 @@
+import checkIcon from '../../../assets/icons/check-icon.svg';
+
+interface IIngredientProps {
+ value: string;
+}
+
+function Ingredient({ value }: IIngredientProps) {
+ return (
+
+
+ {value}
+
+
+ );
+}
+
+export default Ingredient;
diff --git a/src/pages/ProductPage/ui/IngredientList.tsx b/src/pages/ProductPage/ui/IngredientList.tsx
new file mode 100644
index 00000000..21da60d6
--- /dev/null
+++ b/src/pages/ProductPage/ui/IngredientList.tsx
@@ -0,0 +1,16 @@
+interface IIngredientListProps {
+ children: React.ReactElement;
+}
+
+function IngredientList({ children }: IIngredientListProps) {
+ return (
+
+ );
+}
+
+export default IngredientList;
diff --git a/src/pages/ProductPage/ui/MinusIcon.tsx b/src/pages/ProductPage/ui/MinusIcon.tsx
new file mode 100644
index 00000000..3b7cd1fd
--- /dev/null
+++ b/src/pages/ProductPage/ui/MinusIcon.tsx
@@ -0,0 +1,13 @@
+export default function MinusIcon() {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/ProductPage/ui/PlusIcon.tsx b/src/pages/ProductPage/ui/PlusIcon.tsx
new file mode 100644
index 00000000..795fabe0
--- /dev/null
+++ b/src/pages/ProductPage/ui/PlusIcon.tsx
@@ -0,0 +1,14 @@
+export default function PlusIcon() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/pages/ProductPage/ui/Price.tsx b/src/pages/ProductPage/ui/Price.tsx
new file mode 100644
index 00000000..bd04c048
--- /dev/null
+++ b/src/pages/ProductPage/ui/Price.tsx
@@ -0,0 +1,30 @@
+import { calcPriceDiscountPercentage } from '../../../entities/product';
+import formatPrice from '../../../entities/product/lib/helpers/formatPrice.ts';
+import pennieToMoney from '../../../entities/product/lib/helpers/pennieToMoney.ts';
+
+interface IPriceProps {
+ centPrice: number;
+ centOldPrice: number | null;
+ currencyCode: string;
+}
+
+function Price({ centPrice, centOldPrice, currencyCode }: IPriceProps) {
+ const moneyOldPrice = centOldPrice ? pennieToMoney(centOldPrice) : null;
+ const moneyPrice = pennieToMoney(centPrice);
+
+ const corePrice = formatPrice(moneyPrice, currencyCode);
+ const coreOldPrice = moneyOldPrice ? formatPrice(moneyOldPrice, currencyCode) : null;
+ const discountPercentage = moneyOldPrice ? calcPriceDiscountPercentage(moneyOldPrice, moneyPrice) : null;
+
+ return (
+
+ {coreOldPrice ? (
+
{coreOldPrice}
+ ) : null}
+
{corePrice}
+ {discountPercentage ?
You save: {discountPercentage}%
: null}
+
+ );
+}
+
+export default Price;
diff --git a/src/pages/ProductPage/ui/Rating.tsx b/src/pages/ProductPage/ui/Rating.tsx
new file mode 100644
index 00000000..42e225f0
--- /dev/null
+++ b/src/pages/ProductPage/ui/Rating.tsx
@@ -0,0 +1,25 @@
+import StarsRating from 'react-star-rate';
+
+interface IRatingProps {
+ rating: number;
+ setRating: React.Dispatch>;
+}
+
+function Rating({ rating, setRating }: IRatingProps) {
+ return (
+
+
+ {
+ setRating(newRating as number);
+ }}
+ />
+ {rating}
+
+
2,032 Reviews
+
+ );
+}
+
+export default Rating;
diff --git a/src/pages/ProductPage/ui/Title.tsx b/src/pages/ProductPage/ui/Title.tsx
new file mode 100644
index 00000000..2e3d33c8
--- /dev/null
+++ b/src/pages/ProductPage/ui/Title.tsx
@@ -0,0 +1,38 @@
+import { Pagination } from 'swiper/modules';
+import { Swiper, SwiperSlide } from 'swiper/react';
+
+import 'swiper/css';
+import 'swiper/css/pagination';
+
+interface ITitleProps {
+ imgList: string[];
+ name: string;
+ children: React.ReactElement;
+ onClick: () => void;
+}
+
+function Title({ imgList, name, children, onClick }: ITitleProps) {
+ return (
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
+
+
+ {imgList.map((image) => (
+
+
+
+ ))}
+
+ {children}
+
+ );
+}
+
+export default Title;
diff --git a/src/pages/ProductPage/ui/TitleAbout.tsx b/src/pages/ProductPage/ui/TitleAbout.tsx
new file mode 100644
index 00000000..bbb1f553
--- /dev/null
+++ b/src/pages/ProductPage/ui/TitleAbout.tsx
@@ -0,0 +1,29 @@
+import spicyIcon from '../../../assets/icons/spicy.svg';
+import veganIcon from '../../../assets/icons/vegan.svg';
+
+interface ITitleAboutProps {
+ name: string;
+ calories: number | string;
+ weight: number | string;
+ isSpicy: boolean;
+ isVegan: boolean;
+}
+
+function TitleAbout({ name, calories, weight, isSpicy, isVegan }: ITitleAboutProps) {
+ return (
+
+
+ {name}
+ {isSpicy || isVegan ? (
+
+
+
+ ) : null}
+
+
{calories} kcal pre portion
+
{weight} g
+
+ );
+}
+
+export default TitleAbout;
diff --git a/src/pages/ProfilePage/ProfilePage.tsx b/src/pages/ProfilePage/ProfilePage.tsx
new file mode 100644
index 00000000..c395d42f
--- /dev/null
+++ b/src/pages/ProfilePage/ProfilePage.tsx
@@ -0,0 +1,49 @@
+import { useCallback, useEffect, useState } from 'react';
+
+import AccountSettings from './model/AccountSettings';
+import AddressesSettings from './model/AddressesSettings';
+import TabSelector from './model/TabSelector';
+import ThemeSelector from './model/ThemeSelector.tsx';
+import UserImage from './model/UserImage';
+import ProfileHeader from './ui/ProfileHeader';
+import { useLazyGetUserQuery } from '../../entities/user';
+import PageBackBtn from '../../features/PageBackBtn/PageBackBtn.tsx';
+import { useAppSelector } from '../../shared/lib/hooks';
+import LoadingAnimation from '../../shared/ui/LoadingAnimation.tsx';
+
+export default function ProfilePage() {
+ const [isAccTabActive, setIsAccTabActive] = useState(true);
+ const { userId } = useAppSelector((state) => state.userReducer);
+ const [getUser, { data }] = useLazyGetUserQuery();
+
+ const memoizedGetUser = useCallback(
+ (_id: string) => {
+ getUser(_id);
+ },
+ [getUser],
+ );
+
+ useEffect(() => {
+ memoizedGetUser(userId);
+ }, [userId, memoizedGetUser]);
+
+ if (!data)
+ return (
+
+
+
+ );
+
+ return (
+
+
+
+
+
+
+
+ {isAccTabActive ?
:
}
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/constants/constants.ts b/src/pages/ProfilePage/constants/constants.ts
new file mode 100644
index 00000000..dd15a27e
--- /dev/null
+++ b/src/pages/ProfilePage/constants/constants.ts
@@ -0,0 +1,2 @@
+const MODAL_TIMEOUT = 1500;
+export default MODAL_TIMEOUT;
diff --git a/src/pages/ProfilePage/model/AccountSettings.tsx b/src/pages/ProfilePage/model/AccountSettings.tsx
new file mode 100644
index 00000000..720fb859
--- /dev/null
+++ b/src/pages/ProfilePage/model/AccountSettings.tsx
@@ -0,0 +1,14 @@
+import ChangePassword from './ChangePassword';
+import ChangePersonalData from './ChangePersonalData';
+import { IUser } from '../../../shared/types';
+
+export default function AccountSettings(props: { userData: IUser }) {
+ const { userData } = props;
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/AddressListItem.tsx b/src/pages/ProfilePage/model/AddressListItem.tsx
new file mode 100644
index 00000000..9a0529d5
--- /dev/null
+++ b/src/pages/ProfilePage/model/AddressListItem.tsx
@@ -0,0 +1,103 @@
+import React, { useState } from 'react';
+
+import { Cycle } from 'framer-motion';
+
+import greenPencil from '../../../assets/icons/pencilIconGreen.svg';
+import { useDeleteUserDataMutation, useLazyGetUserQuery } from '../../../entities/user';
+import UserUpdateActions from '../../../entities/user/types/enums.ts';
+import { IUser } from '../../../shared/types';
+import MODAL_TIMEOUT from '../constants/constants.ts';
+import { AddressObj } from '../types/profilePageTypes';
+import AddressView from '../ui/AddressView';
+import InfoModal from '../ui/InfoModal';
+
+export default function AddressListItem(props: {
+ index: number;
+ address: AddressObj;
+ userData: IUser;
+ setEditedAddress: React.Dispatch>;
+ setIsModalOpen: Cycle;
+}) {
+ const { index, setEditedAddress, setIsModalOpen, userData, address } = props;
+ const { version, id } = userData;
+ const addressId = address.id;
+ const [isConfirmShown, setIsConfirmShown] = useState(false);
+ const [msgModalShown, setMsgModalShown] = useState(false);
+ const [msgModalText, setMsgModalText] = useState('');
+ const [deleteUserData] = useDeleteUserDataMutation();
+ const [getUser] = useLazyGetUserQuery();
+
+ function editClickHandler() {
+ setEditedAddress(address);
+ setIsModalOpen();
+ }
+
+ async function deleteClickHandler() {
+ try {
+ await deleteUserData({
+ version,
+ actions: [
+ {
+ action: UserUpdateActions.REMOVE_ADDRESS,
+ addressId: addressId as string,
+ },
+ ],
+ });
+
+ setMsgModalText('Your addresses was deleted! :)');
+ setMsgModalShown(true);
+
+ setTimeout(() => {
+ setMsgModalShown(false);
+ getUser(id);
+ }, MODAL_TIMEOUT);
+ } catch (e) {
+ setMsgModalText('Something went wrong! :(');
+ setMsgModalShown(true);
+ setTimeout(() => setMsgModalShown(false), MODAL_TIMEOUT);
+ }
+ }
+
+ return (
+
+
+
{`Address ${(index + 1).toString().padStart(2, '0')}`}
+
+
+
+
+
setIsConfirmShown(true)}
+ className="absolute right-0 top-0 h-6 w-6 rounded-md text-3xl leading-6 text-accent hover:bg-separation-line"
+ type="button"
+ >
+ ร
+
+
+ Are you sure?
+
+ setIsConfirmShown(false)} className="rounded-md px-2 py-1" type="button">
+ No
+
+
+ Yes
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/AddressesDefault.tsx b/src/pages/ProfilePage/model/AddressesDefault.tsx
new file mode 100644
index 00000000..da106526
--- /dev/null
+++ b/src/pages/ProfilePage/model/AddressesDefault.tsx
@@ -0,0 +1,125 @@
+import { useState } from 'react';
+
+import { motion, useCycle } from 'framer-motion';
+
+import icon from '../../../assets/icons/CityIcon.svg';
+import { useLazyGetUserQuery, useUpdateUserAddressMutation } from '../../../entities/user';
+import UserUpdateActions from '../../../entities/user/types/enums.ts';
+import { IUser } from '../../../shared/types';
+import { inputAnimation, svgAnimation } from '../../../shared/ui';
+import MODAL_TIMEOUT from '../constants/constants.ts';
+import InfoModal from '../ui/InfoModal';
+
+export default function AddressesDefault(props: { userData: IUser }) {
+ const { userData } = props;
+ const { addresses, defaultBillingAddressId, defaultShippingAddressId, version, id } = userData;
+
+ const [billAddress, setBillAddress] = useState(defaultBillingAddressId);
+ const [shipAddress, setShipAddress] = useState(defaultShippingAddressId);
+ const [msgModalShown, setMsgModalShown] = useCycle(false, true);
+ const [msgModalText, setMsgModalText] = useState('');
+ const [updateAddress] = useUpdateUserAddressMutation();
+ const [getUser] = useLazyGetUserQuery();
+
+ async function saveClickHandler() {
+ try {
+ await updateAddress({
+ body: {
+ version,
+ actions: [
+ {
+ action: UserUpdateActions.SET_DEFAULT_SHIPPING_ADDRESS,
+ addressId: shipAddress,
+ },
+ {
+ action: UserUpdateActions.SET_DEFAULT_BILLING_ADDRESS,
+ addressId: billAddress,
+ },
+ ],
+ },
+ id,
+ });
+ setMsgModalText('Your addresses has been saved! :)');
+ setMsgModalShown();
+ setTimeout(() => setMsgModalShown(), MODAL_TIMEOUT);
+ getUser(id);
+ } catch (e) {
+ setMsgModalText('Something went wrong! :(');
+ setMsgModalShown();
+ setTimeout(() => setMsgModalShown(), MODAL_TIMEOUT);
+ }
+ }
+
+ const options = addresses?.map((opt) => {
+ const { id: addressId, country, city, streetName, postalCode } = opt;
+ const string = `${country}, ${city}, ${streetName}, ${postalCode}`;
+ return (
+
+ {string}
+
+ );
+ });
+
+ return (
+
+
+
+
Select default shipping address:
+
+ setBillAddress(e.target.value)}
+ >
+ {options}
+
+
+
+
+
+
Select default billing address:
+
+ setShipAddress(e.target.value)}
+ >
+ {options}
+
+
+
+
+
+ Save
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/AddressesEditModal.tsx b/src/pages/ProfilePage/model/AddressesEditModal.tsx
new file mode 100644
index 00000000..335e9677
--- /dev/null
+++ b/src/pages/ProfilePage/model/AddressesEditModal.tsx
@@ -0,0 +1,282 @@
+import { useEffect, useMemo, useState } from 'react';
+
+import { useFormik } from 'formik';
+import { Cycle, motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import cityIcon from '../../../assets/icons/CityIcon.svg';
+import cityIconRed from '../../../assets/icons/CityIconRed.svg';
+import countryIcon from '../../../assets/icons/CountryIcon.svg';
+import postalCodeIcon from '../../../assets/icons/postalCodeIcon.svg';
+import postalCodeIconRed from '../../../assets/icons/postalCodeIconRed.svg';
+import streetIcon from '../../../assets/icons/StreetIcon.svg';
+import streetIconRed from '../../../assets/icons/StreetIconRed.svg';
+import {
+ IUpdateUserDataParams,
+ useLazyGetUserQuery,
+ UserUpdateActions,
+ useUpdateUserAddressMutation,
+} from '../../../entities/user';
+import { validCity, validPostalCode, validStreet } from '../../../shared/const/validationSchemas';
+import { ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import MODAL_TIMEOUT from '../constants/constants.ts';
+import { AddressObj } from '../types/profilePageTypes';
+import InfoModal from '../ui/InfoModal';
+
+export default function AddressesEditModal(props: {
+ id: string | undefined;
+ version: number | undefined;
+ editedAddress: AddressObj;
+ setIsModalOpen: Cycle;
+}) {
+ const { editedAddress, setIsModalOpen, version, id } = props;
+ const { id: addressId, country, city, streetName, postalCode } = editedAddress;
+ const initData = useMemo(() => [country, city, streetName, postalCode], [country, city, streetName, postalCode]);
+ const [selectedCountry, setSelectedCountry] = useState(country);
+ const [isEditBtnBlocked, setIsEditBtnBlocked] = useState(true);
+ const [msgModalShown, setMsgModalShown] = useState(false);
+ const [msgModalText, setMsgModalText] = useState('');
+ const [getUser] = useLazyGetUserQuery();
+ const [updateAddress] = useUpdateUserAddressMutation();
+
+ const validationSchema = Yup.object({
+ city: validCity().city,
+ streetName: validStreet().streetName,
+ postalCode: validPostalCode(selectedCountry).postalCode,
+ });
+
+ const formik = useFormik({
+ initialValues: {
+ country,
+ city,
+ streetName,
+ postalCode,
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+
+ const { handleChange, handleBlur, errors, touched, values } = formik;
+ const touchedAndErrorCity = touched.city && errors.city;
+ const touchedAndErrorStreet = touched.streetName && errors.streetName;
+ const touchedAndErrorPostalCode = touched.postalCode && errors.postalCode;
+
+ useEffect(() => {
+ if (
+ initData.every((item) => {
+ return Object.values(values).includes(item);
+ })
+ ) {
+ setIsEditBtnBlocked(true);
+ return;
+ }
+ if (errors.city || errors.streetName || errors.postalCode) {
+ setIsEditBtnBlocked(true);
+ return;
+ }
+ setIsEditBtnBlocked(false);
+ }, [values, errors, touched, initData]);
+
+ async function handleEditBtn() {
+ const getBody = (): IUpdateUserDataParams => {
+ if (!version) throw new Error('Version is not defined!');
+
+ if (addressId) {
+ return {
+ version,
+ actions: [
+ {
+ action: UserUpdateActions.CHANGE_ADDRESS,
+ addressId,
+ address: {
+ country: values.country,
+ city: values.city,
+ streetName: values.streetName,
+ postalCode: values.postalCode,
+ },
+ },
+ ],
+ };
+ }
+
+ return {
+ version,
+ actions: [
+ {
+ action: UserUpdateActions.ADD_ADDRESS,
+ address: {
+ country: values.country,
+ city: values.city,
+ streetName: values.streetName,
+ postalCode: values.postalCode,
+ },
+ },
+ ],
+ };
+ };
+ try {
+ await updateAddress({ body: getBody(), id: id as string });
+ setMsgModalText('Your addresses saved! :)');
+ setMsgModalShown(true);
+ setTimeout(() => {
+ setMsgModalShown(false);
+ setIsModalOpen();
+ }, MODAL_TIMEOUT);
+ getUser(id as string);
+ } catch (e) {
+ setMsgModalText('Something went wrong! :(');
+ setMsgModalShown(true);
+ setTimeout(() => setMsgModalShown(false), MODAL_TIMEOUT);
+ }
+ }
+
+ window.scrollTo(0, 250);
+ return (
+
+
+
Edit your address:
+
+
+
+ {
+ setSelectedCountry(e.target.value);
+ handleChange(e);
+ }}
+ onBlur={handleBlur}
+ value={values.country}
+ >
+ USA
+ Ukraine
+ Germany
+
+
+
+
+
+
+ {touchedAndErrorCity && {errors.city} }
+
+
+
+
+ {touchedAndErrorStreet && {errors.streetName} }
+
+
+
+
+ {touchedAndErrorPostalCode && {errors.postalCode} }
+
+
+
+ setIsModalOpen()}>
+ Cancel
+
+ handleEditBtn()}
+ >
+ Edit
+
+
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/AddressesList.tsx b/src/pages/ProfilePage/model/AddressesList.tsx
new file mode 100644
index 00000000..2f7d2903
--- /dev/null
+++ b/src/pages/ProfilePage/model/AddressesList.tsx
@@ -0,0 +1,44 @@
+import { Cycle } from 'framer-motion';
+
+import AddressListItem from './AddressListItem';
+import { IUser } from '../../../shared/types';
+import { AddressObj } from '../types/profilePageTypes';
+
+export default function AddressesList(props: {
+ userData: IUser;
+ setEditedAddress: React.Dispatch>;
+ setIsModalOpen: Cycle;
+}) {
+ const { userData, setEditedAddress, setIsModalOpen } = props;
+ const { addresses } = userData;
+
+ function addAddressHandler() {
+ setEditedAddress({ country: 'US', city: '', streetName: '', postalCode: '' });
+ setIsModalOpen();
+ }
+
+ return (
+ <>
+ {addresses?.map((address, index) => {
+ const { id: addressID } = address;
+ return (
+
+ );
+ })}
+
+ + Add more
+
+ >
+ );
+}
diff --git a/src/pages/ProfilePage/model/AddressesSettings.tsx b/src/pages/ProfilePage/model/AddressesSettings.tsx
new file mode 100644
index 00000000..52b29f14
--- /dev/null
+++ b/src/pages/ProfilePage/model/AddressesSettings.tsx
@@ -0,0 +1,48 @@
+import { useState } from 'react';
+
+import { AnimatePresence, motion, useCycle } from 'framer-motion';
+
+import AddressesDefault from './AddressesDefault';
+import AddressesEditModal from './AddressesEditModal';
+import AddressesList from './AddressesList';
+import { IUser } from '../../../shared/types';
+import { Blackout } from '../../../shared/ui';
+import { AddressObj } from '../types/profilePageTypes';
+
+export default function AddressesSettings(props: { userData: IUser }) {
+ const { userData } = props;
+ const { version, id } = userData;
+ const [isModalOpen, setIsModalOpen] = useCycle(false, true);
+ const [editedAddress, setEditedAddress] = useState({} as AddressObj);
+
+ return (
+
+
+
+
+
+ {isModalOpen ? (
+
+
+
+ ) : null}
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/ChangePassword.tsx b/src/pages/ProfilePage/model/ChangePassword.tsx
new file mode 100644
index 00000000..251a5489
--- /dev/null
+++ b/src/pages/ProfilePage/model/ChangePassword.tsx
@@ -0,0 +1,158 @@
+import { useEffect, useState } from 'react';
+
+import { useFormik } from 'formik';
+import { motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import lockIcon from '../../../assets/icons/LockIcon.svg';
+import lockIconRed from '../../../assets/icons/LockIconRed.svg';
+import { useLazyGetUserQuery, useLoginUser, useUpdateUserPasswordMutation } from '../../../entities/user';
+import { validPassword } from '../../../shared/const/validationSchemas';
+import { useAppSelector, useRevokeAccessRefreshTokens } from '../../../shared/lib/hooks';
+import { IUser } from '../../../shared/types';
+import { ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import MODAL_TIMEOUT from '../constants/constants.ts';
+import InfoModal from '../ui/InfoModal';
+
+const validationSchema = Yup.object({
+ currPass: validPassword().password,
+ newPass: validPassword().password,
+});
+
+export default function ChangePassword(props: { userData: IUser }) {
+ const { userData } = props;
+ const { id, version } = userData;
+ const [isSaveBlocked, setIsSaveBlocked] = useState(true);
+ const [msgModalShown, setMsgModalShown] = useState(false);
+ const [msgModalText, setMsgModalText] = useState('Sometext');
+ const [getUser] = useLazyGetUserQuery();
+ const [updatePassword] = useUpdateUserPasswordMutation();
+ const [loginUser] = useLoginUser();
+ const revokeTokens = useRevokeAccessRefreshTokens();
+
+ const { accessToken: oldAccessToken, refreshToken: oldRefreshToken } = useAppSelector((state) => state.userReducer);
+
+ const formik = useFormik({
+ initialValues: {
+ currPass: '',
+ newPass: '',
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+
+ const { handleChange, handleBlur, errors, touched, values } = formik;
+ const touchedAndErrorCurrPassword = touched.currPass && errors.currPass;
+ const touchedAndErrorNewPassword = touched.newPass && errors.newPass;
+
+ useEffect(() => {
+ if (
+ (touched.currPass === undefined && values.currPass === '') ||
+ (touched.newPass === undefined && values.newPass === '')
+ ) {
+ setIsSaveBlocked(true);
+ return;
+ }
+ if (errors.currPass || errors.newPass) {
+ setIsSaveBlocked(true);
+ return;
+ }
+ setIsSaveBlocked(false);
+ }, [values, errors, touched, setIsSaveBlocked]);
+
+ async function saveHandler() {
+ try {
+ await updatePassword({
+ version,
+ currentPassword: values.currPass,
+ newPassword: values.newPass,
+ });
+
+ setMsgModalText(`Password was changed! :)`);
+ setMsgModalShown(true);
+ setTimeout(() => {
+ setMsgModalShown(false);
+ }, MODAL_TIMEOUT);
+ setIsSaveBlocked(true);
+
+ await loginUser(userData.email, values.newPass, id);
+ revokeTokens(oldAccessToken, oldRefreshToken);
+ getUser(id);
+ } catch (e) {
+ setMsgModalText(`Oh snap! Something went wrong :(`);
+ setMsgModalShown(true);
+ setTimeout(() => setMsgModalShown(false), MODAL_TIMEOUT);
+ }
+ }
+
+ return (
+
+
Password
+
+
+
Current password
+
+
+
+ {touchedAndErrorCurrPassword && {errors.currPass} }
+
+
+
+
New password
+
+
+
+ {touchedAndErrorNewPassword && {errors.newPass} }
+
+
+
+
+ Save
+
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/ChangePersonalData.tsx b/src/pages/ProfilePage/model/ChangePersonalData.tsx
new file mode 100644
index 00000000..da26da31
--- /dev/null
+++ b/src/pages/ProfilePage/model/ChangePersonalData.tsx
@@ -0,0 +1,236 @@
+import { useEffect, useState } from 'react';
+
+import { useFormik } from 'formik';
+import { motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import calendarIcon from '../../../assets/icons/CalendarIcon.svg';
+import calendarIconRed from '../../../assets/icons/CalendarIconRed.svg';
+import emailIcon from '../../../assets/icons/emailIcon.svg';
+import emailIconRed from '../../../assets/icons/emailIconRed.svg';
+import userIcon from '../../../assets/icons/UserIcon.svg';
+import userIconRed from '../../../assets/icons/UserIconRed.svg';
+import { useLazyGetUserQuery, useUpdateUserDataMutation } from '../../../entities/user';
+import UserUpdateActions from '../../../entities/user/types/enums.ts';
+import { validBirthDate, validEmail, validName } from '../../../shared/const/validationSchemas';
+import { IUser } from '../../../shared/types';
+import { ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import MODAL_TIMEOUT from '../constants/constants.ts';
+import InfoModal from '../ui/InfoModal';
+
+const validationSchema = Yup.object({
+ ...validEmail(),
+ ...validBirthDate(),
+ firstName: validName().name,
+ lastName: validName().name,
+});
+
+export default function ChangePersonalData(props: { userData: IUser }) {
+ const [updateUser] = useUpdateUserDataMutation();
+
+ const { userData } = props;
+ const { id, email, firstName, lastName, dateOfBirth, version } = userData;
+
+ const formik = useFormik({
+ initialValues: {
+ email,
+ firstName,
+ lastName,
+ dateOfBirth,
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+
+ const initData = Object.values([email, firstName, lastName, dateOfBirth]);
+
+ const { handleChange, handleBlur, errors, touched, values } = formik;
+ const [isSaveBlocked, setIsSaveBlocked] = useState(true);
+ const [msgModalShown, setMsgModalShown] = useState(false);
+ const [msgModalText, setMsgModalText] = useState('');
+ const [getUser] = useLazyGetUserQuery();
+
+ const touchedAndErrorEmail = touched.email && errors.email;
+ const touchedAndErrorFirstName = touched.firstName && errors.firstName;
+ const touchedAndErrorLastName = touched.lastName && errors.lastName;
+ const touchedAndErrorBirthDate = touched.dateOfBirth && errors.dateOfBirth;
+
+ async function saveClickHandler() {
+ try {
+ await updateUser({
+ version,
+ actions: [
+ {
+ action: UserUpdateActions.CHANGE_EMAIL,
+ email: values.email,
+ },
+ {
+ action: UserUpdateActions.SET_FIRST_NAME,
+ firstName: values.firstName,
+ },
+ {
+ action: UserUpdateActions.SET_LAST_NAME,
+ lastName: values.lastName,
+ },
+ {
+ action: UserUpdateActions.SET_BIRTH_DATE,
+ dateOfBirth: values.dateOfBirth,
+ },
+ ],
+ });
+
+ setMsgModalText('Your data saved! :)');
+ setMsgModalShown(true);
+ setTimeout(() => setMsgModalShown(false), MODAL_TIMEOUT);
+ getUser(id);
+ } catch (e) {
+ setMsgModalText('Something went wrong! :(');
+ setMsgModalShown(true);
+ setTimeout(() => setMsgModalShown(false), MODAL_TIMEOUT);
+ }
+ }
+
+ useEffect(() => {
+ if (
+ initData.every((item) => {
+ return Object.values(values).includes(item);
+ })
+ ) {
+ setIsSaveBlocked(true);
+ return;
+ }
+ if (errors.firstName || errors.lastName || errors.dateOfBirth || errors.email) {
+ setIsSaveBlocked(true);
+ return;
+ }
+ setIsSaveBlocked(false);
+ }, [values, errors, touched, initData]);
+
+ return (
+
+
+
Personal data
+
+
Email
+
+
+
+ {touchedAndErrorEmail && {errors.email} }
+
+
+
+
Name
+
+
+
+ {touchedAndErrorFirstName && {errors.firstName} }
+
+
+
+
Surname
+
+
+
+ {touchedAndErrorLastName && {errors.lastName} }
+
+
+
+
Birth date
+
+
+
+ {touchedAndErrorBirthDate && {errors.dateOfBirth} }
+
+
+
+
+ Save
+
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/TabSelector.tsx b/src/pages/ProfilePage/model/TabSelector.tsx
new file mode 100644
index 00000000..e7e9e4bd
--- /dev/null
+++ b/src/pages/ProfilePage/model/TabSelector.tsx
@@ -0,0 +1,27 @@
+const greenBorder = 'border-b-2 border-accent';
+const borderGrey = 'border-b-2 border-separation-line dark:border-dark-separation-line';
+
+export default function TabSelector(props: {
+ isAccTabActive: boolean;
+ setIsAccTabActive: React.Dispatch>;
+}) {
+ const { isAccTabActive, setIsAccTabActive } = props;
+ return (
+
+ setIsAccTabActive(true)}
+ className={`${isAccTabActive ? greenBorder : borderGrey} h-9 w-1/2 transition-all`}
+ type="button"
+ >
+ Account settings
+
+ setIsAccTabActive(false)}
+ className={`${isAccTabActive ? borderGrey : greenBorder} h-9 w-1/2 transition-all`}
+ type="button"
+ >
+ Addresses
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/ThemeSelector.tsx b/src/pages/ProfilePage/model/ThemeSelector.tsx
new file mode 100644
index 00000000..30c73da9
--- /dev/null
+++ b/src/pages/ProfilePage/model/ThemeSelector.tsx
@@ -0,0 +1,130 @@
+import { useState } from 'react';
+
+import { motion } from 'framer-motion';
+
+import moon from '../../../assets/icons/moon.svg';
+import sun from '../../../assets/icons/sun.svg';
+
+const themeToggleVariants = {
+ light: { x: 0 },
+ dark: { x: '190%' },
+};
+
+export default function ThemeSelector() {
+ const [defTheme, setDefTheme] = useState(localStorage.sushiDefThemeUsage === 'true');
+ const [theme, setTheme] = useState(localStorage.sushiTheme === 'light');
+
+ function handleThemeSelect() {
+ if (theme) {
+ setTheme(false);
+ localStorage.sushiTheme = 'dark';
+ document.documentElement.classList.add('dark');
+ document.body.classList.add('dark:bg-dark-bg-primary');
+ } else {
+ setTheme(true);
+ localStorage.sushiTheme = 'light';
+ document.body.classList.remove('dark:bg-dark-bg-primary');
+ document.documentElement.classList.remove('dark');
+ }
+ }
+
+ function handleDefTheme() {
+ setDefTheme((prev: boolean) => {
+ if (!prev) {
+ localStorage.sushiDefThemeUsage = true;
+ localStorage.removeItem('sushiTheme');
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ document.documentElement.classList.add('dark');
+ setTheme(false);
+ } else {
+ document.documentElement.classList.remove('dark');
+ setTheme(true);
+ }
+ } else {
+ localStorage.sushiDefThemeUsage = false;
+ localStorage.sushiTheme = 'light';
+ document.documentElement.classList.remove('dark');
+ setTheme(true);
+ }
+ return !prev;
+ });
+ }
+
+ return (
+
+
+
+
+
+ Use default OS color theme
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/model/UserImage.tsx b/src/pages/ProfilePage/model/UserImage.tsx
new file mode 100644
index 00000000..f4b1354f
--- /dev/null
+++ b/src/pages/ProfilePage/model/UserImage.tsx
@@ -0,0 +1,29 @@
+import pencilIcon from '../../../assets/icons/pencilIcon.svg';
+import { getFullName } from '../../../entities/user';
+import { IUser } from '../../../shared/types';
+import UserIcon from '../../../shared/ui/UserIcon';
+
+export default function UserImage(props: { userData: IUser }) {
+ const { userData } = props;
+ const { firstName, lastName, email } = userData;
+
+ return (
+
+
+
+
+
+
{getFullName(firstName, lastName)}
+
{email}
+
+
+
+
+ Change image
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/types/profilePageTypes.ts b/src/pages/ProfilePage/types/profilePageTypes.ts
new file mode 100644
index 00000000..812ce7ea
--- /dev/null
+++ b/src/pages/ProfilePage/types/profilePageTypes.ts
@@ -0,0 +1,7 @@
+export type AddressObj = {
+ id?: string;
+ country: string;
+ city: string;
+ streetName: string;
+ postalCode: string;
+};
diff --git a/src/pages/ProfilePage/ui/AddressView.tsx b/src/pages/ProfilePage/ui/AddressView.tsx
new file mode 100644
index 00000000..d7599c59
--- /dev/null
+++ b/src/pages/ProfilePage/ui/AddressView.tsx
@@ -0,0 +1,72 @@
+import { motion } from 'framer-motion';
+
+import cityIcon from '../../../assets/icons/CityIcon.svg';
+import countryIcon from '../../../assets/icons/CountryIcon.svg';
+import postalCodeIcon from '../../../assets/icons/postalCodeIcon.svg';
+import streetIcon from '../../../assets/icons/StreetIcon.svg';
+import { svgAnimation } from '../../../shared/ui';
+import { AddressObj } from '../types/profilePageTypes';
+
+function getCountry(country: string) {
+ switch (country) {
+ case 'US':
+ return 'USA';
+ case 'DE':
+ return 'Germany';
+ default:
+ return 'Ukraine';
+ }
+}
+
+export default function AddressView(props: { address: AddressObj }) {
+ const { address } = props;
+ const { country, city, streetName, postalCode } = address;
+ return (
+
+
+
+ {getCountry(country)}
+
+
+
+ {city}
+
+
+
+ {streetName}
+
+
+
+ {postalCode}
+
+
+ );
+}
diff --git a/src/pages/ProfilePage/ui/InfoModal.tsx b/src/pages/ProfilePage/ui/InfoModal.tsx
new file mode 100644
index 00000000..94617bba
--- /dev/null
+++ b/src/pages/ProfilePage/ui/InfoModal.tsx
@@ -0,0 +1,45 @@
+import { AnimatePresence, motion } from 'framer-motion';
+
+export default function InfoModal(props: { msgModalShown: boolean; msgModalText: string }) {
+ const { msgModalShown, msgModalText } = props;
+ return (
+
+ {msgModalShown ? (
+
+ {msgModalText}
+
+ ) : null}
+
+ );
+}
diff --git a/src/pages/ProfilePage/ui/ProfileHeader.tsx b/src/pages/ProfilePage/ui/ProfileHeader.tsx
new file mode 100644
index 00000000..b3d4372e
--- /dev/null
+++ b/src/pages/ProfilePage/ui/ProfileHeader.tsx
@@ -0,0 +1,8 @@
+export default function ProfileHeader() {
+ return (
+
+
User information
+
Here you can edit public information about yourself.
+
+ );
+}
diff --git a/src/pages/RegPage/RegPage.tsx b/src/pages/RegPage/RegPage.tsx
new file mode 100644
index 00000000..e84d2908
--- /dev/null
+++ b/src/pages/RegPage/RegPage.tsx
@@ -0,0 +1,170 @@
+import { useCallback, useState } from 'react';
+
+import { motion } from 'framer-motion';
+
+import prepareDataForUpload from './lib/helpers';
+import useMultistepForm from './lib/hooks';
+import { RegStepFour, RegStepOne, RegStepThree, RegStepTwo } from './model';
+import { IFormData, UpdateDataParams } from './types';
+import CirclesWrapper from './ui/CirclesWrapper.tsx';
+import NavBlock from './ui/NavBlock.tsx';
+import { ErrorModal, SuccessModal } from '../../entities/form/ui';
+import { useLoginUser, useSignUpMutation } from '../../entities/user';
+import { getErrorMessage } from '../../shared/lib/helpers';
+import { ISignUpAddress } from '../../shared/types';
+import { pageVariants } from '../../shared/ui';
+
+const initVals: IFormData = {
+ email: '',
+ password: '',
+ firstName: '',
+ lastName: '',
+ dateOfBirth: '',
+ sameBillShip: true,
+ billSetDefault: true,
+ shipSetDefault: true,
+ addresses: [{} as ISignUpAddress, {} as ISignUpAddress],
+ billingAddresses: [0],
+ shippingAddresses: [0],
+};
+
+export default function RegPage() {
+ const [formData, setFormData] = useState(initVals);
+ const [isNextEnabled, setIsNextEnabled] = useState(false);
+ const [isFormSubmitted, setIsFormSubmitted] = useState(false);
+ const [loginUser] = useLoginUser();
+ const [signUpUser, { isSuccess, error, isLoading }] = useSignUpMutation();
+
+ const errorMessage = getErrorMessage(error);
+
+ const enableNext = useCallback(
+ (arg: boolean) => {
+ setIsNextEnabled(arg);
+ },
+ [setIsNextEnabled],
+ );
+
+ const updateData = useCallback(
+ (fieldsOrCallback: UpdateDataParams) => {
+ setFormData((prevState) => {
+ if (typeof fieldsOrCallback === 'function') {
+ return fieldsOrCallback(prevState);
+ }
+
+ return { ...prevState, ...fieldsOrCallback };
+ });
+ },
+ [setFormData],
+ );
+
+ const { email, password, firstName, lastName, dateOfBirth, sameBillShip, addresses, billSetDefault, shipSetDefault } =
+ formData;
+
+ const { isFirstStep, isLastStep, formLength, reStartForm, currentStepIndex, currForm, back, next } = useMultistepForm(
+ [
+ ,
+ ,
+ ,
+ ,
+ ],
+ );
+
+ function handleResetForm() {
+ reStartForm();
+ setIsFormSubmitted(false);
+ }
+
+ async function nextFunc() {
+ isNextEnabled && next();
+
+ if (!isLastStep || !isNextEnabled) return;
+
+ const signUpData = prepareDataForUpload(formData);
+
+ try {
+ const {
+ customer: { id },
+ } = await signUpUser(signUpData).unwrap();
+
+ await loginUser(email, password, id);
+ } catch (e) {
+ // console.error();
+ } finally {
+ setIsFormSubmitted(true);
+ }
+ }
+
+ return (
+
+ {/* eslint-disable-next-line no-nested-ternary */}
+ {isFormSubmitted ? (
+ isSuccess ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/pages/RegPage/lib/const/constants.ts b/src/pages/RegPage/lib/const/constants.ts
new file mode 100644
index 00000000..a93f1a08
--- /dev/null
+++ b/src/pages/RegPage/lib/const/constants.ts
@@ -0,0 +1,3 @@
+export const FIRST_ADDRESS = 0;
+export const SECOND_ADDRESS = 1;
+export const RESTRICTED_AGE = 13;
diff --git a/src/pages/RegPage/lib/const/index.ts b/src/pages/RegPage/lib/const/index.ts
new file mode 100644
index 00000000..700ef2d5
--- /dev/null
+++ b/src/pages/RegPage/lib/const/index.ts
@@ -0,0 +1 @@
+export { FIRST_ADDRESS, SECOND_ADDRESS, RESTRICTED_AGE } from './constants.ts';
diff --git a/src/pages/RegPage/lib/helpers/index.ts b/src/pages/RegPage/lib/helpers/index.ts
new file mode 100644
index 00000000..cf21b161
--- /dev/null
+++ b/src/pages/RegPage/lib/helpers/index.ts
@@ -0,0 +1,3 @@
+import prepareDataForUpload from './prepareDataForUpload.ts';
+
+export default prepareDataForUpload;
diff --git a/src/pages/RegPage/lib/helpers/prepareDataForUpload.ts b/src/pages/RegPage/lib/helpers/prepareDataForUpload.ts
new file mode 100644
index 00000000..544b3f7a
--- /dev/null
+++ b/src/pages/RegPage/lib/helpers/prepareDataForUpload.ts
@@ -0,0 +1,46 @@
+import { ISignUpParams } from '../../../../shared/types';
+import { IFormData } from '../../types';
+import { FIRST_ADDRESS, SECOND_ADDRESS } from '../const';
+
+/**
+ * Prepares the data for upload.
+ *
+ * @param {IFormData} formData - The form data to be prepared for upload.
+ * @return {ISignUpParams}
+ */
+function prepareDataForUpload({
+ email,
+ password,
+ firstName,
+ lastName,
+ dateOfBirth,
+ billSetDefault,
+ shipSetDefault,
+ addresses,
+ sameBillShip,
+}: IFormData): ISignUpParams {
+ const defaultBillingAddress = billSetDefault ? FIRST_ADDRESS : undefined;
+ let defaultShippingAddress = shipSetDefault ? FIRST_ADDRESS : undefined;
+
+ const billingAddresses = [FIRST_ADDRESS];
+ const shippingAddresses = sameBillShip ? [FIRST_ADDRESS] : [SECOND_ADDRESS];
+
+ if (!sameBillShip && shipSetDefault) {
+ defaultShippingAddress = SECOND_ADDRESS;
+ }
+
+ return {
+ email,
+ password,
+ firstName,
+ lastName,
+ dateOfBirth,
+ addresses: sameBillShip ? [addresses[FIRST_ADDRESS]] : addresses,
+ billingAddresses,
+ shippingAddresses,
+ defaultBillingAddress,
+ defaultShippingAddress,
+ };
+}
+
+export default prepareDataForUpload;
diff --git a/src/pages/RegPage/lib/hooks/index.ts b/src/pages/RegPage/lib/hooks/index.ts
new file mode 100644
index 00000000..39e45655
--- /dev/null
+++ b/src/pages/RegPage/lib/hooks/index.ts
@@ -0,0 +1,3 @@
+import useMultistepForm from './useMultistepForm.tsx';
+
+export default useMultistepForm;
diff --git a/src/pages/RegPage/lib/hooks/useMultistepForm.tsx b/src/pages/RegPage/lib/hooks/useMultistepForm.tsx
new file mode 100644
index 00000000..967ced6a
--- /dev/null
+++ b/src/pages/RegPage/lib/hooks/useMultistepForm.tsx
@@ -0,0 +1,36 @@
+import { ReactElement, useState } from 'react';
+
+export default function useMultistepForm(steps: ReactElement[]) {
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+
+ function next() {
+ setCurrentStepIndex((i) => {
+ if (i >= steps.length - 1) return i;
+ return i + 1;
+ });
+ }
+
+ function back() {
+ setCurrentStepIndex((i) => {
+ if (i <= 0) return i;
+ return i - 1;
+ });
+ }
+
+ function reStartForm() {
+ setCurrentStepIndex(0);
+ }
+
+ const formLength = steps.length;
+
+ return {
+ reStartForm,
+ isFirstStep: currentStepIndex === 0,
+ isLastStep: currentStepIndex === steps.length - 1,
+ currentStepIndex,
+ currForm: steps[currentStepIndex],
+ formLength,
+ next,
+ back,
+ };
+}
diff --git a/src/pages/RegPage/model/RegStepFour.tsx b/src/pages/RegPage/model/RegStepFour.tsx
new file mode 100644
index 00000000..e4f70dd9
--- /dev/null
+++ b/src/pages/RegPage/model/RegStepFour.tsx
@@ -0,0 +1,290 @@
+import { useEffect } from 'react';
+
+import { useFormik } from 'formik';
+import { motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import postalCodeIcon from '../../../assets/icons/postalCodeIcon.svg';
+import postalCodeIconRed from '../../../assets/icons/postalCodeIconRed.svg';
+import streetIcon from '../../../assets/icons/StreetIcon.svg';
+import streetIconRed from '../../../assets/icons/StreetIconRed.svg';
+import { CustomRegForm } from '../../../entities/form/ui';
+import { validPostalCode, validStreet } from '../../../shared/const/validationSchemas';
+import { ISignUpAddress } from '../../../shared/types';
+import { checkboxAnimation, ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import { UserFormProps } from '../types';
+
+export default function RegStepFour(props: UserFormProps) {
+ const {
+ addresses: [
+ { country: billCountry = '', postalCode: billPostalCode = '', streetName: billStreet = '' } = {},
+ { country: shipCountry = '', postalCode: shipPostalCode = '', streetName: shipStreet = '' } = {},
+ ] = [],
+ sameBillShip,
+ billSetDefault,
+ shipSetDefault,
+ updateData,
+ enableNext,
+ } = props;
+
+ const validationSchema = Yup.object({
+ billPostalCode: validPostalCode(billCountry).postalCode,
+ billStreet: validStreet().streetName,
+ shipPostalCode: validPostalCode(shipCountry).postalCode,
+ shipStreet: validStreet().streetName,
+ });
+
+ const formik = useFormik({
+ initialValues: {
+ billPostalCode,
+ billStreet,
+ shipPostalCode,
+ shipStreet,
+ billSetDefault,
+ shipSetDefault,
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+
+ const { handleChange, handleBlur, errors, touched, values, setValues, setTouched } = formik;
+ const shipBillCluesStyles = 'relative after:absolute after:-top-5 after:right-0 after:text-2xs';
+
+ useEffect(() => {
+ if (!sameBillShip) return;
+
+ setTouched({ shipPostalCode: true, shipStreet: true });
+
+ setValues({
+ ...values,
+ shipPostalCode: values.billPostalCode,
+ shipStreet: values.billStreet,
+ shipSetDefault: billSetDefault,
+ });
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [sameBillShip, values.billStreet, values.billPostalCode, billSetDefault]);
+
+ useEffect(() => {
+ updateData((prevState) => ({
+ ...prevState,
+ billSetDefault: values.billSetDefault as boolean,
+ shipSetDefault: values.shipSetDefault as boolean,
+ addresses: [
+ {
+ ...prevState.addresses[0],
+ streetName: values.billStreet as string,
+ postalCode: values.billPostalCode as string,
+ },
+ {
+ ...(prevState.addresses[1] as ISignUpAddress),
+ streetName: values.shipStreet as string,
+ postalCode: values.shipPostalCode as string,
+ },
+ ],
+ }));
+
+ if (
+ (touched.billPostalCode === undefined && values.billPostalCode === '') ||
+ (touched.billStreet === undefined && values.billStreet === '') ||
+ (touched.shipPostalCode === undefined && values.shipPostalCode === '') ||
+ (touched.shipStreet === undefined && values.shipStreet === '')
+ ) {
+ enableNext(false);
+ return;
+ }
+ if (errors.billPostalCode || errors.billStreet || errors.shipPostalCode || errors.shipStreet) {
+ enableNext(false);
+ return;
+ }
+ enableNext(true);
+ }, [values, errors, touched, updateData, enableNext, sameBillShip]);
+
+ const touchedAndErrorBillPostalCode = touched.billPostalCode && errors.billPostalCode;
+ const touchedAndErrorBillStreet = touched.billStreet && errors.billStreet;
+ const touchedAndErrorShipPostalCode = touched.shipPostalCode && errors.shipPostalCode;
+ const touchedAndErrorShipStreet = touched.shipStreet && errors.shipStreet;
+
+ return (
+
+
+
+
+ {touchedAndErrorBillPostalCode && {errors.billPostalCode} }
+
+
+
+
+ {touchedAndErrorBillStreet && {errors.billStreet} }
+
+
+
+
+ {sameBillShip ? `Set as default billing & shipping address` : 'Set as default billing address'}
+
+
+ {!sameBillShip ? (
+
+
+
+
+ {touchedAndErrorShipPostalCode && {errors.shipPostalCode} }
+
+
+
+
+ {touchedAndErrorShipStreet && {errors.shipStreet} }
+
+
+
+
+ {sameBillShip ? `Set as default billing & shipping address` : 'Set as default shipping address'}
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/pages/RegPage/model/RegStepOne.tsx b/src/pages/RegPage/model/RegStepOne.tsx
new file mode 100644
index 00000000..4f49abd8
--- /dev/null
+++ b/src/pages/RegPage/model/RegStepOne.tsx
@@ -0,0 +1,113 @@
+import { useEffect } from 'react';
+
+import { useFormik } from 'formik';
+import { motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import emailIcon from '../../../assets/icons/emailIcon.svg';
+import emailIconRed from '../../../assets/icons/emailIconRed.svg';
+import lockIcon from '../../../assets/icons/LockIcon.svg';
+import lockIconRed from '../../../assets/icons/LockIconRed.svg';
+import { CustomRegForm } from '../../../entities/form/ui';
+import { validEmail, validPassword } from '../../../shared/const/validationSchemas';
+import { ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import { UserFormProps } from '../types';
+
+const validationSchema = Yup.object({
+ ...validEmail(),
+ ...validPassword(),
+});
+
+export default function RegStepOne(props: UserFormProps) {
+ const { email, password, updateData, enableNext } = props;
+ const formik = useFormik({
+ initialValues: {
+ email,
+ password,
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+
+ const { handleChange, handleBlur, errors, touched, values, setFieldError } = formik;
+
+ useEffect(() => {
+ updateData({ email: values.email, password: values.password });
+ if (
+ (touched.email === undefined && values.email === '') ||
+ (touched.password === undefined && values.password === '')
+ ) {
+ enableNext(false);
+ return;
+ }
+ if (errors.email || errors.password) {
+ enableNext(false);
+ return;
+ }
+
+ if (/\s/.test(values.password as string)) {
+ setFieldError('password', "Password can't contain spaces");
+ enableNext(false);
+ return;
+ }
+ setFieldError('password', undefined);
+
+ enableNext(true);
+ }, [values, errors, touched, enableNext, updateData, setFieldError]);
+
+ const touchedAndErrorEmail = touched.email && errors.email;
+ const touchedAndErrorPassword = touched.password && errors.password;
+
+ return (
+
+
+
+
+ {touchedAndErrorEmail && {errors.email} }
+
+
+
+
+ {touchedAndErrorPassword && {errors.password} }
+
+
+ );
+}
diff --git a/src/pages/RegPage/model/RegStepThree.tsx b/src/pages/RegPage/model/RegStepThree.tsx
new file mode 100644
index 00000000..cf5bcfaf
--- /dev/null
+++ b/src/pages/RegPage/model/RegStepThree.tsx
@@ -0,0 +1,264 @@
+import { useEffect } from 'react';
+
+import { useFormik } from 'formik';
+import { AnimatePresence, motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import cityIcon from '../../../assets/icons/CityIcon.svg';
+import cityIconRed from '../../../assets/icons/CityIconRed.svg';
+import countryIcon from '../../../assets/icons/CountryIcon.svg';
+import { CustomRegForm } from '../../../entities/form/ui';
+import { validCity } from '../../../shared/const/validationSchemas';
+import { ISignUpAddress } from '../../../shared/types';
+import { checkboxAnimation, ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import { UserFormProps } from '../types';
+import { InputIcon } from '../ui';
+
+const validationSchema = Yup.object({
+ billCity: validCity().city,
+ shipCity: validCity().city,
+});
+
+const shipBillCluesStyles = 'relative after:absolute after:-top-5 after:right-0 after:text-2xs';
+
+export default function RegStepThree(props: UserFormProps) {
+ const {
+ addresses: [
+ { country: billCountry = 'US', city: billCity = '' } = {},
+ { country: shipCountry = 'US', city: shipCity = '' } = {},
+ ] = [],
+ sameBillShip,
+ updateData,
+ enableNext,
+ } = props;
+
+ const formik = useFormik({
+ initialValues: {
+ billCountry,
+ billCity,
+ shipCountry,
+ shipCity,
+ sameBillShip,
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+ const { handleChange, handleBlur, errors, touched, values, setValues } = formik;
+
+ useEffect(() => {
+ if (values.sameBillShip) {
+ setValues({ ...values, shipCountry: values.billCountry, shipCity: values.billCity });
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [values.sameBillShip, values.billCity, values.billCountry]);
+
+ useEffect(() => {
+ const shipAddress: ISignUpAddress = {
+ city: values.shipCity!,
+ country: values.shipCountry!,
+ streetName: '',
+ postalCode: '',
+ };
+
+ const billAddress: ISignUpAddress = {
+ city: values.billCity!,
+ country: values.billCountry!,
+ streetName: '',
+ postalCode: '',
+ };
+
+ updateData({
+ sameBillShip: values.sameBillShip,
+ addresses: [billAddress, shipAddress],
+ });
+
+ if (
+ (touched.shipCity === undefined && values.shipCity === '') ||
+ (touched.billCity === undefined && values.billCity === '')
+ ) {
+ enableNext(false);
+ return;
+ }
+ if (errors.shipCountry || errors.shipCity || errors.billCountry || errors.billCity) {
+ enableNext(false);
+ return;
+ }
+ enableNext(true);
+ }, [values, errors, touched, updateData, enableNext]);
+
+ const touchedAndErrorBillCity = touched.billCity && errors.billCity;
+ const touchedAndErrorShipCity = touched.shipCity && errors.shipCity;
+
+ return (
+
+
+
+
+ USA
+
+
+ Ukraine
+
+
+ Germany
+
+
+
+
+
+
+
+ {touchedAndErrorBillCity && {errors.billCity} }
+
+
+ {!values.sameBillShip ? (
+
+
+
+
+ USA
+
+
+ Ukraine
+
+
+ Germany
+
+
+
+
+
+
+
+ {touchedAndErrorShipCity && {errors.shipCity} }
+
+
+ ) : null}
+
+
+
+
+ Use the same address
+ as a billing and a shipping
+
+
+
+ );
+}
diff --git a/src/pages/RegPage/model/RegStepTwo.tsx b/src/pages/RegPage/model/RegStepTwo.tsx
new file mode 100644
index 00000000..b234de07
--- /dev/null
+++ b/src/pages/RegPage/model/RegStepTwo.tsx
@@ -0,0 +1,158 @@
+import React, { useEffect, useState } from 'react';
+
+import { useFormik } from 'formik';
+import { motion } from 'framer-motion';
+import * as Yup from 'yup';
+
+import calendarIcon from '../../../assets/icons/CalendarIcon.svg';
+import calendarIconRed from '../../../assets/icons/CalendarIconRed.svg';
+import userIcon from '../../../assets/icons/UserIcon.svg';
+import userIconRed from '../../../assets/icons/UserIconRed.svg';
+import { CustomRegForm } from '../../../entities/form/ui';
+import { validBirthDate, validName } from '../../../shared/const/validationSchemas';
+import { ErrorMessage, inputAnimation, svgAnimation } from '../../../shared/ui';
+import { UserFormProps } from '../types';
+
+const validationSchema = Yup.object({
+ ...validBirthDate(),
+ firstName: validName().name,
+ lastName: validName().name,
+});
+
+export default function RegStepTwo(props: UserFormProps) {
+ const { firstName, lastName, dateOfBirth, updateData, enableNext } = props;
+ const formik = useFormik({
+ initialValues: {
+ firstName,
+ lastName,
+ dateOfBirth,
+ },
+ validationSchema,
+ onSubmit: () => {},
+ });
+ const { handleChange, handleBlur, errors, touched, values } = formik;
+ const [dateInputType, setDateInputType] = useState('text');
+ const [isDateFocus, setIsDateFocus] = useState(false);
+
+ function handleTransitionEnd() {
+ setDateInputType(document.activeElement?.id === 'dateOfBirthInput' ? 'date' : 'text');
+ }
+
+ function blurHandler(e: React.FocusEvent) {
+ formik.handleBlur(e);
+ setIsDateFocus((currVal) => !currVal);
+ }
+ function focusHandler(e: React.FocusEvent) {
+ formik.handleChange(e);
+ setIsDateFocus((currVal) => !currVal);
+ }
+
+ useEffect(() => {
+ updateData({ firstName: values.firstName, lastName: values.lastName, dateOfBirth: values.dateOfBirth });
+
+ if (
+ (touched.firstName === undefined && values.firstName === '') ||
+ (touched.lastName === undefined && values.lastName === '') ||
+ (touched.dateOfBirth === undefined && values.dateOfBirth === '')
+ ) {
+ enableNext(false);
+ return;
+ }
+ if (errors.firstName || errors.lastName || errors.dateOfBirth) {
+ enableNext(false);
+ return;
+ }
+ enableNext(true);
+ }, [values, errors, touched, updateData, enableNext]);
+
+ const touchedAndErrorFirstName = touched.firstName && errors.firstName;
+ const touchedAndErrorLastName = touched.lastName && errors.lastName;
+ const touchedAndErrorBirthDate = touched.dateOfBirth && errors.dateOfBirth;
+
+ return (
+
+
+
+
+
+ {touchedAndErrorFirstName && {errors.firstName} }
+
+
+
+ ) => focusHandler(e)}
+ className={`loginRegInput placeholder:placeholder-opacity-0 ${
+ touchedAndErrorBirthDate ? 'border-shop-cart-red' : ''
+ }`}
+ onChange={handleChange}
+ onBlur={(e: React.FocusEvent) => blurHandler(e)}
+ value={values.dateOfBirth}
+ />
+
+ {touchedAndErrorBirthDate && {errors.dateOfBirth} }
+
+
+
+
+
+
+ {touchedAndErrorLastName && {errors.lastName} }
+
+
+ );
+}
diff --git a/src/pages/RegPage/model/index.ts b/src/pages/RegPage/model/index.ts
new file mode 100644
index 00000000..3ca0c921
--- /dev/null
+++ b/src/pages/RegPage/model/index.ts
@@ -0,0 +1,6 @@
+import RegStepFour from './RegStepFour.tsx';
+import RegStepOne from './RegStepOne.tsx';
+import RegStepThree from './RegStepThree.tsx';
+import RegStepTwo from './RegStepTwo.tsx';
+
+export { RegStepOne, RegStepTwo, RegStepThree, RegStepFour };
diff --git a/src/pages/RegPage/types/index.ts b/src/pages/RegPage/types/index.ts
new file mode 100644
index 00000000..961afcc8
--- /dev/null
+++ b/src/pages/RegPage/types/index.ts
@@ -0,0 +1,2 @@
+export type { IFormData } from './interfaces.ts';
+export type { UpdateDataParams, UpdateDataFields, UpdateDataCallBack, UserFormProps, PropsType } from './types.ts';
diff --git a/src/pages/RegPage/types/interfaces.ts b/src/pages/RegPage/types/interfaces.ts
new file mode 100644
index 00000000..15048d49
--- /dev/null
+++ b/src/pages/RegPage/types/interfaces.ts
@@ -0,0 +1,13 @@
+import { ISignUpParams, SignUpAddresses } from '../../../shared/types';
+
+export interface IFormData extends ISignUpParams {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ dateOfBirth: string;
+ sameBillShip: boolean;
+ billSetDefault: boolean;
+ shipSetDefault: boolean;
+ addresses: SignUpAddresses;
+}
diff --git a/src/pages/RegPage/types/types.ts b/src/pages/RegPage/types/types.ts
new file mode 100644
index 00000000..3ccfd25d
--- /dev/null
+++ b/src/pages/RegPage/types/types.ts
@@ -0,0 +1,20 @@
+import { IFormData } from './interfaces.ts';
+
+export type UpdateDataCallBack = (prevState: IFormData) => IFormData;
+
+export type UpdateDataFields = Partial;
+
+export type UpdateDataParams = UpdateDataFields | UpdateDataCallBack;
+
+export type UserFormProps = Partial & {
+ updateData: (fieldsOrCallback: UpdateDataParams) => void;
+ enableNext: (arg: boolean) => void;
+};
+
+export type PropsType = {
+ backFunc: () => void;
+ nextFunc: () => void;
+ isNextEnabled: boolean;
+ isFirstStep: boolean;
+ isLoading: boolean;
+};
diff --git a/src/pages/RegPage/ui/CheckCircle.tsx b/src/pages/RegPage/ui/CheckCircle.tsx
new file mode 100644
index 00000000..702f8c5f
--- /dev/null
+++ b/src/pages/RegPage/ui/CheckCircle.tsx
@@ -0,0 +1,54 @@
+import PrimaryGulp from '../../../assets/icons/PrimaryGulp';
+
+export default function CheckCircle(props: { numb: number; currStep: number; title: string }) {
+ const { numb, title, currStep } = props;
+
+ function getStatus() {
+ if (numb - currStep < 0) {
+ return 'done';
+ }
+ if (numb - currStep > 0) {
+ return 'pending';
+ }
+ return 'active';
+ }
+ const status = getStatus();
+ const getClasses = () => {
+ if (status === 'done') {
+ return 'bg-accent border-accent text-accent';
+ }
+ if (status === 'pending') {
+ return 'bg-primary border-inactive-icons-grey text-inactive-icons-grey';
+ }
+ return 'bg-primary border-accent text-accent';
+ };
+ const addClasses = getClasses();
+ return (
+
+
+ {status === 'done' ?
: numb + 1}
+
+
+ {title}
+
+
+ );
+}
diff --git a/src/pages/RegPage/ui/CirclesWrapper.tsx b/src/pages/RegPage/ui/CirclesWrapper.tsx
new file mode 100644
index 00000000..ae5a3652
--- /dev/null
+++ b/src/pages/RegPage/ui/CirclesWrapper.tsx
@@ -0,0 +1,31 @@
+import { motion } from 'framer-motion';
+
+import CheckCircle from './CheckCircle';
+
+const titles = ['Email & Password', 'Name & Birth', 'Country & City', 'PC & Street'];
+
+export default function CirclesWrapper(props: {
+ currStep: number;
+ quantity: number;
+ sameBillShip: boolean;
+ currentStepIndex: number;
+}) {
+ const { currStep, quantity, currentStepIndex, sameBillShip } = props;
+ const circles = Array.from({ length: quantity }, (_, i) => (
+
+ ));
+ return (
+
+ {circles}
+
+ );
+}
diff --git a/src/pages/RegPage/ui/InputIcon.tsx b/src/pages/RegPage/ui/InputIcon.tsx
new file mode 100644
index 00000000..9728bd05
--- /dev/null
+++ b/src/pages/RegPage/ui/InputIcon.tsx
@@ -0,0 +1,27 @@
+import { motion } from 'framer-motion';
+
+import { svgAnimation } from '../../../shared/ui';
+
+interface IInputIconProps {
+ icon: string;
+ delay?: number;
+}
+
+function InputIcon({ icon, delay = 0.15 }: IInputIconProps) {
+ return (
+
+ );
+}
+
+InputIcon.defaultProps = {
+ delay: 0.15,
+};
+export default InputIcon;
diff --git a/src/pages/RegPage/ui/NavBlock.tsx b/src/pages/RegPage/ui/NavBlock.tsx
new file mode 100644
index 00000000..beeef411
--- /dev/null
+++ b/src/pages/RegPage/ui/NavBlock.tsx
@@ -0,0 +1,61 @@
+import { motion } from 'framer-motion';
+import { Link } from 'react-router-dom';
+
+import { PropsType } from '../types';
+
+interface Props extends PropsType {
+ sameBillShip: boolean;
+ currentStepIndex: number;
+}
+
+export default function NavBlock(props: Props) {
+ const { backFunc, nextFunc, isNextEnabled, isFirstStep, isLoading, sameBillShip, currentStepIndex } = props;
+
+ return (
+
+
+ Back
+
+
+ Already have an account?
+
+ Log in
+
+
+
+ Continue
+
+
+ );
+}
diff --git a/src/pages/RegPage/ui/index.ts b/src/pages/RegPage/ui/index.ts
new file mode 100644
index 00000000..f8066c06
--- /dev/null
+++ b/src/pages/RegPage/ui/index.ts
@@ -0,0 +1,6 @@
+import CheckCircle from './CheckCircle.tsx';
+import CirclesWrapper from './CirclesWrapper.tsx';
+import InputIcon from './InputIcon.tsx';
+import NavBlock from './NavBlock.tsx';
+
+export { CheckCircle, CirclesWrapper, NavBlock, InputIcon };
diff --git a/src/shared/api/authQuery.ts b/src/shared/api/authQuery.ts
new file mode 100644
index 00000000..0e1fcce9
--- /dev/null
+++ b/src/shared/api/authQuery.ts
@@ -0,0 +1,13 @@
+import { fetchBaseQuery } from '@reduxjs/toolkit/dist/query/react';
+
+const authQuery = fetchBaseQuery({
+ baseUrl: import.meta.env.VITE_AUTH_SERVICE_URL,
+ prepareHeaders: (headers) => {
+ headers.set(
+ 'Authorization',
+ `Basic ${btoa(`${import.meta.env.VITE_CLIENT_ID}:${import.meta.env.VITE_CLIENT_SECRET}`)}`,
+ );
+ },
+});
+
+export default authQuery;
diff --git a/src/shared/api/baseQuery.ts b/src/shared/api/baseQuery.ts
new file mode 100644
index 00000000..46ad36e9
--- /dev/null
+++ b/src/shared/api/baseQuery.ts
@@ -0,0 +1,13 @@
+import { fetchBaseQuery } from '@reduxjs/toolkit/dist/query/react';
+
+import { RootState } from '../../app/store';
+
+const baseQuery = fetchBaseQuery({
+ baseUrl: import.meta.env.VITE_API_HOST_URL,
+ prepareHeaders: (headers, { getState }) => {
+ const { accessToken } = (getState() as RootState).userReducer;
+ headers.set('Authorization', `Bearer ${accessToken}`);
+ },
+});
+
+export default baseQuery;
diff --git a/src/shared/api/baseQueryWithReauth.ts b/src/shared/api/baseQueryWithReauth.ts
new file mode 100644
index 00000000..8bfe777b
--- /dev/null
+++ b/src/shared/api/baseQueryWithReauth.ts
@@ -0,0 +1,65 @@
+import { BaseQueryFn } from '@reduxjs/toolkit/dist/query';
+import { type FetchArgs, FetchBaseQueryError } from '@reduxjs/toolkit/query';
+import { Mutex } from 'async-mutex';
+
+import authQuery from './authQuery.ts';
+import baseQuery from './baseQuery.ts';
+import { ErrorCodeStatus } from '../types';
+
+const mutex = new Mutex();
+const baseQueryWithReauth: BaseQueryFn = async (
+ args,
+ api,
+ extraOptions,
+) => {
+ await mutex.waitForUnlock();
+
+ let result = await baseQuery(args, api, extraOptions);
+
+ if (result.error && result.error.status === ErrorCodeStatus.UNAUTHORIZED) {
+ if (!mutex.isLocked()) {
+ const release = await mutex.acquire();
+ try {
+ const refreshResultAnonToken = await authQuery(
+ {
+ url: `/oauth/${import.meta.env.VITE_PROJECT_KEY}/anonymous/token`,
+ method: 'POST',
+ params: {
+ grant_type: 'client_credentials',
+ scope: import.meta.env.VITE_DEFAULT_CUSTOMER_SCOPE,
+ },
+ },
+ api,
+ extraOptions,
+ );
+
+ if (
+ refreshResultAnonToken.data &&
+ typeof refreshResultAnonToken.data === 'object' &&
+ 'access_token' in refreshResultAnonToken.data &&
+ 'refresh_token' in refreshResultAnonToken.data
+ ) {
+ api.dispatch({
+ type: 'user/updateAccessToken',
+ payload: {
+ accessToken: refreshResultAnonToken.data.access_token as string,
+ refreshToken: refreshResultAnonToken.data.refresh_token as string,
+ },
+ });
+ result = await baseQuery(args, api, extraOptions);
+ } else {
+ api.dispatch({ type: 'user/loggedOut' });
+ }
+ } finally {
+ release();
+ }
+ } else {
+ await mutex.waitForUnlock();
+ result = await baseQuery(args, api, extraOptions);
+ }
+ }
+
+ return result;
+};
+
+export default baseQueryWithReauth;
diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts
new file mode 100644
index 00000000..46926b7a
--- /dev/null
+++ b/src/shared/api/index.ts
@@ -0,0 +1,4 @@
+import rootApi from './rootApi.ts';
+import rootAuthApi from './rootAuthApi.ts';
+
+export { rootApi, rootAuthApi };
diff --git a/src/shared/api/rootApi.ts b/src/shared/api/rootApi.ts
new file mode 100644
index 00000000..5f8a1bed
--- /dev/null
+++ b/src/shared/api/rootApi.ts
@@ -0,0 +1,12 @@
+import { createApi } from '@reduxjs/toolkit/query/react';
+
+import baseQueryWithReauth from './baseQueryWithReauth.ts';
+
+const rootApi = createApi({
+ reducerPath: 'rootApi',
+ tagTypes: ['CartItems'],
+ baseQuery: baseQueryWithReauth,
+ endpoints: () => ({}),
+});
+
+export default rootApi;
diff --git a/src/shared/api/rootAuthApi.ts b/src/shared/api/rootAuthApi.ts
new file mode 100644
index 00000000..50a70187
--- /dev/null
+++ b/src/shared/api/rootAuthApi.ts
@@ -0,0 +1,11 @@
+import { createApi } from '@reduxjs/toolkit/dist/query/react';
+
+import authQuery from './authQuery.ts';
+
+const rootAuthApi = createApi({
+ reducerPath: 'rootAuthApi',
+ baseQuery: authQuery,
+ endpoints: () => ({}),
+});
+
+export default rootAuthApi;
diff --git a/src/shared/const/constants.ts b/src/shared/const/constants.ts
new file mode 100644
index 00000000..9bed64e1
--- /dev/null
+++ b/src/shared/const/constants.ts
@@ -0,0 +1,3 @@
+export const DEFAULT_TITLE = 'SushiSushi - Royal Asia Flavour';
+export const LOGO_NAME = 'SushiSushi'.split('');
+export const PRODUCT_PATH = 'categories/all';
diff --git a/src/shared/const/index.ts b/src/shared/const/index.ts
new file mode 100644
index 00000000..1cb0d21c
--- /dev/null
+++ b/src/shared/const/index.ts
@@ -0,0 +1 @@
+export { DEFAULT_TITLE, LOGO_NAME, PRODUCT_PATH } from './constants.ts';
diff --git a/src/shared/const/validationSchemas.tsx b/src/shared/const/validationSchemas.tsx
new file mode 100644
index 00000000..66f792ae
--- /dev/null
+++ b/src/shared/const/validationSchemas.tsx
@@ -0,0 +1,98 @@
+import * as Yup from 'yup';
+
+import { padZero } from '../lib/helpers';
+
+export const RESTRICTED_AGE = 13;
+
+function correctDate(year: number, month: number, day: number) {
+ return new Date(`${year}-${padZero(month + 1)}-${day.toString().padStart(2, '0')}`);
+}
+
+export function validEmail() {
+ const emailRegEx = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
+
+ return {
+ email: Yup.string()
+ .matches(emailRegEx, { message: 'Email must be email@example.com', excludeEmptyString: true })
+ .required('Email is required'),
+ };
+}
+
+export function validPassword() {
+ const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&~()_+\-=':"}{/?<>;,.\]\\[*]).{8,}$/;
+
+ return {
+ password: Yup.string()
+ .min(8, 'Minimum 8 symbols required')
+ .matches(passwordRegex, { message: 'Password must have A, a, 1 and special symbols', excludeEmptyString: true })
+ .required('Password is required'),
+ };
+}
+
+export function validName() {
+ const nameRegEx = /^[a-zA-Z]+$/;
+
+ return {
+ name: Yup.string()
+ .matches(nameRegEx, { message: 'Name must contain A, a letters', excludeEmptyString: true })
+ .max(20, 'Too long name')
+ .required('Last name is required'),
+ };
+}
+
+export function validBirthDate() {
+ const currentDate = new Date();
+ const ageLimit = correctDate(
+ currentDate.getFullYear() - RESTRICTED_AGE,
+ currentDate.getMonth(),
+ currentDate.getDate(),
+ );
+
+ return {
+ dateOfBirth: Yup.date().max(ageLimit, 'Age restriction: 13+').required('Birth date is required'),
+ };
+}
+
+export function validCity() {
+ const cityRegEx = /^(?=.*[a-zA-Z])[a-zA-Z ]+$/;
+
+ return {
+ city: Yup.string()
+ .matches(cityRegEx, { message: 'City name must contain only A, a letters', excludeEmptyString: true })
+ .max(20, 'Too long name')
+ .required('City name is required'),
+ };
+}
+
+export function validPostalCode(country: string) {
+ const usaPostCode = /^\d{5}(-\d{4})?$/;
+ const ukrGerPostCode = /^\d{5}$/;
+
+ function getRegEx(_country: string) {
+ switch (_country) {
+ case 'UA':
+ case 'DE':
+ return ukrGerPostCode;
+ default:
+ return usaPostCode;
+ }
+ }
+
+ const billRegEx = getRegEx(country);
+
+ return {
+ postalCode: Yup.string()
+ .matches(billRegEx, { message: 'Enter valid postal code', excludeEmptyString: true })
+ .required('PostalCode is required'),
+ };
+}
+
+export function validStreet() {
+ const streetRegEx = /^(?=.*[a-zA-Z]).*$/;
+
+ return {
+ streetName: Yup.string()
+ .matches(streetRegEx, { message: 'Street name must contain at least one letter', excludeEmptyString: true })
+ .required('Street name is required'),
+ };
+}
diff --git a/src/shared/lib/helpers/capitalize.ts b/src/shared/lib/helpers/capitalize.ts
new file mode 100644
index 00000000..87019f62
--- /dev/null
+++ b/src/shared/lib/helpers/capitalize.ts
@@ -0,0 +1,10 @@
+/**
+ * Converts the first character of a string to uppercase.
+ * @param {string} str - The input string.
+ * @return {string} The input string with the first character capitalized.
+ */
+function capitalize(str: string) {
+ return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
+}
+
+export default capitalize;
diff --git a/src/shared/lib/helpers/deleteCookie.ts b/src/shared/lib/helpers/deleteCookie.ts
new file mode 100644
index 00000000..c2a1f085
--- /dev/null
+++ b/src/shared/lib/helpers/deleteCookie.ts
@@ -0,0 +1,13 @@
+/**
+ * Clears a specific cookie on the page.
+ *
+ * @param {string[]} names - The name of the cookie to be cleared. Can take multiple cookie names.
+ * @returns {void}
+ */
+function deleteCookie(...names: string[]) {
+ names.forEach((name) => {
+ document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
+ });
+}
+
+export default deleteCookie;
diff --git a/src/shared/lib/helpers/getCookie.ts b/src/shared/lib/helpers/getCookie.ts
new file mode 100644
index 00000000..90ff4703
--- /dev/null
+++ b/src/shared/lib/helpers/getCookie.ts
@@ -0,0 +1,19 @@
+/**
+ * Retrieves the cookie from the storage.
+ *
+ * @param {...string} names - The names of the cookies to retrieve.
+ * @returns {(string | undefined)[]} The array of cookie value, or undefined if not found.
+ */
+function getCookie(...names: string[]) {
+ const { cookie } = document;
+
+ return names.map((name) => {
+ return cookie
+ .split('; ')
+ .find((row) => row.startsWith(name))
+ ?.split('=')
+ .at(1);
+ });
+}
+
+export default getCookie;
diff --git a/src/shared/lib/helpers/getErrorMessage.ts b/src/shared/lib/helpers/getErrorMessage.ts
new file mode 100644
index 00000000..a0002110
--- /dev/null
+++ b/src/shared/lib/helpers/getErrorMessage.ts
@@ -0,0 +1,21 @@
+import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
+
+/**
+ * Returns the error message from the fetchBaseQueryError object, if available.
+ *
+ * @param {unknown} fetchBaseQueryError - The error object to extract the error message from.
+ * @return {string} The error message.
+ */
+function getErrorMessage(fetchBaseQueryError: unknown) {
+ const errData = (fetchBaseQueryError as FetchBaseQueryError)?.data;
+
+ let errorMessage = '';
+
+ if (errData instanceof Object && 'message' in errData) {
+ errorMessage = errData.message as string;
+ }
+
+ return errorMessage;
+}
+
+export default getErrorMessage;
diff --git a/src/shared/lib/helpers/index.ts b/src/shared/lib/helpers/index.ts
new file mode 100644
index 00000000..6ada627e
--- /dev/null
+++ b/src/shared/lib/helpers/index.ts
@@ -0,0 +1,8 @@
+import capitalize from './capitalize.ts';
+import deleteCookie from './deleteCookie.ts';
+import getCookie from './getCookie.ts';
+import getErrorMessage from './getErrorMessage.ts';
+import padZero from './padZero.ts';
+import setCookie from './setCookie.ts';
+
+export { setCookie, getCookie, deleteCookie, getErrorMessage, capitalize, padZero };
diff --git a/src/shared/lib/helpers/padZero.ts b/src/shared/lib/helpers/padZero.ts
new file mode 100644
index 00000000..99c05bca
--- /dev/null
+++ b/src/shared/lib/helpers/padZero.ts
@@ -0,0 +1,11 @@
+/**
+ * Pads a number with a leading zero if it's a single digit number.
+ *
+ * @param {number} num - The number to pad with zero.
+ * @return {string} - The padded number as a string.
+ */
+function padZero(num: number) {
+ return num.toString().padStart(2, '0');
+}
+
+export default padZero;
diff --git a/src/shared/lib/helpers/setCookie.ts b/src/shared/lib/helpers/setCookie.ts
new file mode 100644
index 00000000..f6101fc9
--- /dev/null
+++ b/src/shared/lib/helpers/setCookie.ts
@@ -0,0 +1,22 @@
+import { CookieTuple } from '../../types';
+
+/**
+ * Saving the data in the cookies
+ *
+ * @param {CookieTuple[]} cookies - array of tuples
+ * @return {void} - This method does not return a value
+ *
+ * [value, name, expires] - accepted tuple
+ */
+function setCookie(...cookies: CookieTuple[]) {
+ const expireDate = new Date();
+
+ cookies.forEach(([value, name, expires]) => {
+ const expiresMilliseconds = expires * 1000;
+ expireDate.setTime(expireDate.getTime() + expiresMilliseconds);
+
+ document.cookie = `${name}=${value}; expires=${expireDate}; path=/; secure`;
+ });
+}
+
+export default setCookie;
diff --git a/src/shared/lib/hooks/index.ts b/src/shared/lib/hooks/index.ts
new file mode 100644
index 00000000..2bc0c0ba
--- /dev/null
+++ b/src/shared/lib/hooks/index.ts
@@ -0,0 +1,6 @@
+import useRevokeAccessRefreshTokens from './revokeTokens/useRevokeAccessRefreshTokens.ts';
+import useAppDispatch from './useAppDispatch/useAppDispatch.ts';
+import useAppSelector from './useAppSelector/useAppSelector.ts';
+import useGetPath from './useGetPath/useGetPath.ts';
+
+export { useAppDispatch, useAppSelector, useRevokeAccessRefreshTokens, useGetPath };
diff --git a/src/shared/lib/hooks/revokeTokens/useRevokeAccessRefreshTokens.ts b/src/shared/lib/hooks/revokeTokens/useRevokeAccessRefreshTokens.ts
new file mode 100644
index 00000000..26e9cc8d
--- /dev/null
+++ b/src/shared/lib/hooks/revokeTokens/useRevokeAccessRefreshTokens.ts
@@ -0,0 +1,18 @@
+import { useRevokeTokenMutation } from '../../../../entities/user';
+import { TokenTypeHints } from '../../../types';
+
+/**
+ * Revokes the access and refresh tokens by calling the `revokeToken` mutation.
+ *
+ * @returns {(accessToken: string, refreshToken: string) => void} - The function that perform toke revocation.
+ */
+function useRevokeAccessRefreshTokens() {
+ const [revokeToken] = useRevokeTokenMutation();
+
+ return (accessToken: string, refreshToken: string) => {
+ revokeToken({ token: accessToken, tokenTypeHint: TokenTypeHints.ACCESS_TOKEN });
+ revokeToken({ token: refreshToken, tokenTypeHint: TokenTypeHints.REFRESH_TOKEN });
+ };
+}
+
+export default useRevokeAccessRefreshTokens;
diff --git a/src/shared/lib/hooks/useAppDispatch/useAppDispatch.ts b/src/shared/lib/hooks/useAppDispatch/useAppDispatch.ts
new file mode 100644
index 00000000..209ba572
--- /dev/null
+++ b/src/shared/lib/hooks/useAppDispatch/useAppDispatch.ts
@@ -0,0 +1,6 @@
+import { useDispatch } from 'react-redux';
+
+import { AppDispatch } from '../../../../app/store';
+
+const useAppDispatch = () => useDispatch();
+export default useAppDispatch;
diff --git a/src/shared/lib/hooks/useAppSelector/useAppSelector.ts b/src/shared/lib/hooks/useAppSelector/useAppSelector.ts
new file mode 100644
index 00000000..3a188bf9
--- /dev/null
+++ b/src/shared/lib/hooks/useAppSelector/useAppSelector.ts
@@ -0,0 +1,6 @@
+import { TypedUseSelectorHook, useSelector } from 'react-redux';
+
+import { RootState } from '../../../../app/store';
+
+const useAppSelector: TypedUseSelectorHook = useSelector;
+export default useAppSelector;
diff --git a/src/shared/lib/hooks/useGetPath/useGetPath.ts b/src/shared/lib/hooks/useGetPath/useGetPath.ts
new file mode 100644
index 00000000..a5dbbb30
--- /dev/null
+++ b/src/shared/lib/hooks/useGetPath/useGetPath.ts
@@ -0,0 +1,8 @@
+import { useLocation } from 'react-router-dom';
+
+function useGetPath() {
+ const { pathname } = useLocation();
+ return pathname.slice(pathname.lastIndexOf('/') + 1);
+}
+
+export default useGetPath;
diff --git a/src/shared/types/enums.ts b/src/shared/types/enums.ts
new file mode 100644
index 00000000..7ac09b4b
--- /dev/null
+++ b/src/shared/types/enums.ts
@@ -0,0 +1,12 @@
+enum ErrorCodeStatus {
+ BAD_REQUEST = 400,
+ NOT_FOUND = 404,
+ UNAUTHORIZED = 401,
+}
+
+enum TokenTypeHints {
+ ACCESS_TOKEN = 'access_token',
+ REFRESH_TOKEN = 'refresh_token',
+}
+
+export { ErrorCodeStatus, TokenTypeHints };
diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts
new file mode 100644
index 00000000..e2edadbc
--- /dev/null
+++ b/src/shared/types/index.ts
@@ -0,0 +1,19 @@
+export type {
+ ILoginUserParams,
+ ILoginUserDataResponse,
+ IUserAddress,
+ IUser,
+ ISignUpParams,
+ ISignUpAddress,
+ IUpdateUserParams,
+} from './interfaces.ts';
+export type {
+ CookieTuple,
+ SignUpAddresses,
+ IAuthResponse,
+ CartResponse,
+ CreateCartResponse,
+ LineItem,
+ ProductResult,
+} from './types.ts';
+export { ErrorCodeStatus, TokenTypeHints } from './enums.ts';
diff --git a/src/shared/types/interfaces.ts b/src/shared/types/interfaces.ts
new file mode 100644
index 00000000..de140d46
--- /dev/null
+++ b/src/shared/types/interfaces.ts
@@ -0,0 +1,84 @@
+import { SignUpAddresses } from './types.ts';
+
+export interface ILoginUserParams {
+ password: string;
+ email: string;
+ scope?: string;
+}
+
+export interface IUpdateUserParams {
+ id: string;
+ accessToken: string;
+ body: object;
+}
+
+export interface IUserAddress {
+ id: string;
+ streetName: string;
+ city: string;
+ country: string;
+ postalCode: string;
+}
+
+export interface IUser {
+ id: string;
+ version: number;
+ versionModifiedAt: string;
+ lastMessageSequenceNumber: number;
+ createdAt: string;
+ lastModifiedAt: string;
+ lastModifiedBy: {
+ isPlatformClient: true;
+ user: {
+ typeId: string;
+ id: string;
+ };
+ };
+ createdBy: {
+ isPlatformClient: true;
+ user: {
+ typeId: string;
+ id: string;
+ };
+ };
+ email: string;
+ firstName: string;
+ lastName: string;
+ middleName: string;
+ title: string;
+ salutation: string;
+ dateOfBirth: string;
+ password: string;
+ addresses: IUserAddress[];
+ defaultShippingAddressId: string;
+ defaultBillingAddressId: string;
+ shippingAddressIds: string;
+ billingAddressIds: string;
+ isEmailVerified: false;
+ stores: [];
+ authenticationMode: string;
+}
+
+export type ILoginUserDataResponse = Readonly<{
+ customer: IUser;
+}>;
+
+export interface ISignUpAddress {
+ streetName: string;
+ city: string;
+ postalCode: string;
+ country: string;
+}
+
+export interface ISignUpParams {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ dateOfBirth: string;
+ addresses: SignUpAddresses;
+ billingAddresses: number[];
+ shippingAddresses: number[];
+ defaultShippingAddress?: number;
+ defaultBillingAddress?: number;
+}
diff --git a/src/shared/types/types.ts b/src/shared/types/types.ts
new file mode 100644
index 00000000..c90d4ed8
--- /dev/null
+++ b/src/shared/types/types.ts
@@ -0,0 +1,71 @@
+import { ISignUpAddress } from './interfaces.ts';
+import { ProductAttribute, ProductImage, ProductPrice } from '../../entities/product/types/types.ts';
+
+export type CookieTuple = [string, string, number];
+export type SignUpAddresses = [ISignUpAddress, ISignUpAddress] | [ISignUpAddress];
+
+export type ProductResult = Readonly<{
+ id: string;
+ name: {
+ de: string;
+ en: string;
+ uk: string;
+ };
+ masterVariant: {
+ id: number;
+ attributes: ProductAttribute[];
+ images: ProductImage[];
+ prices: ProductPrice[];
+ };
+}>;
+
+export type IAuthResponse = Readonly<{
+ access_token: string;
+ expires_in: number;
+ refresh_token: string;
+ scope: string;
+ token_type: string;
+}>;
+
+type DiscountedPricePerQuantity = Readonly<{
+ quantity: number;
+ discountedPrice: {
+ includedDiscounts: {
+ discountedAmount: {
+ centAmount: number;
+ };
+ }[];
+ };
+}>;
+
+export type LineItem = Readonly<{
+ id: string;
+ productId: string;
+ discountedPricePerQuantity: DiscountedPricePerQuantity[];
+ name: {
+ en: string;
+ };
+ price: ProductPrice;
+ quantity: number;
+}>;
+
+export type CartResponse = Readonly<{
+ type: 'Cart';
+ id: string;
+ customerId: string;
+ version: number;
+ createdBy: {
+ anonymousId: string;
+ };
+ lineItems: LineItem[];
+ totalPrice: {
+ type: string;
+ currencyCode: string;
+ centAmount: number;
+ };
+ totalLineItemQuantity: number;
+}>;
+
+export type CreateCartResponse = Readonly<{
+ data: CartResponse;
+}>;
diff --git a/src/shared/ui/AnimateCounter.tsx b/src/shared/ui/AnimateCounter.tsx
new file mode 100644
index 00000000..db9e6bcc
--- /dev/null
+++ b/src/shared/ui/AnimateCounter.tsx
@@ -0,0 +1,33 @@
+import { useEffect, useRef } from 'react';
+
+import { useInView, useMotionValue, useSpring } from 'framer-motion';
+
+function AnimateCounter({ value }: { value: number }) {
+ const ref = useRef(null);
+ const motionValue = useMotionValue(0);
+ const springValue = useSpring(motionValue, {
+ damping: 100,
+ stiffness: 200,
+ });
+ const isInView = useInView(ref, { once: true, margin: '-100px' });
+
+ useEffect(() => {
+ if (isInView) {
+ motionValue.set(value);
+ }
+ }, [motionValue, isInView, value]);
+
+ useEffect(
+ () =>
+ springValue.on('change', (latest) => {
+ if (ref.current) {
+ ref.current.textContent = Intl.NumberFormat('en-US').format(latest.toFixed(0));
+ }
+ }),
+ [springValue],
+ );
+
+ return ;
+}
+
+export default AnimateCounter;
diff --git a/src/shared/ui/Blackout.tsx b/src/shared/ui/Blackout.tsx
new file mode 100644
index 00000000..b5e542af
--- /dev/null
+++ b/src/shared/ui/Blackout.tsx
@@ -0,0 +1,39 @@
+import { useEffect } from 'react';
+
+interface IBlackoutProps {
+ isBlackout: boolean;
+ unlock?: () => void;
+ isScrollable?: boolean;
+}
+
+function Blackout({ isBlackout, unlock, isScrollable }: IBlackoutProps) {
+ useEffect(() => {
+ if (isScrollable) return;
+
+ if (isBlackout) {
+ document.body.style.overflow = 'hidden';
+ } else {
+ document.body.style.overflow = 'auto';
+ }
+ }, [isBlackout, isScrollable]);
+
+ if (!isBlackout) return null;
+
+ return (
+
+ );
+}
+
+Blackout.defaultProps = {
+ unlock: null,
+ isScrollable: false,
+};
+
+export default Blackout;
diff --git a/src/shared/ui/Button.tsx b/src/shared/ui/Button.tsx
new file mode 100644
index 00000000..f5073710
--- /dev/null
+++ b/src/shared/ui/Button.tsx
@@ -0,0 +1,44 @@
+interface ButtonProps {
+ label: string;
+ onClick: (e: React.MouseEvent) => void;
+ disabled?: boolean;
+ outline?: boolean;
+ small?: boolean;
+}
+
+export default function Button({ label, onClick, disabled, outline, small }: ButtonProps) {
+ return (
+
+ {label}
+
+ );
+}
+
+Button.defaultProps = {
+ disabled: false,
+ outline: true,
+ small: false,
+};
diff --git a/src/shared/ui/ErrorMessage.tsx b/src/shared/ui/ErrorMessage.tsx
new file mode 100644
index 00000000..6dfe51ee
--- /dev/null
+++ b/src/shared/ui/ErrorMessage.tsx
@@ -0,0 +1,27 @@
+import { motion } from 'framer-motion';
+
+interface IErrorMessageProps {
+ children: string | undefined;
+}
+
+function ErrorMessage({ children }: IErrorMessageProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default ErrorMessage;
diff --git a/src/shared/ui/Input.tsx b/src/shared/ui/Input.tsx
new file mode 100644
index 00000000..13959af6
--- /dev/null
+++ b/src/shared/ui/Input.tsx
@@ -0,0 +1,55 @@
+interface InputProps {
+ id: string;
+ label: string;
+ type?: string;
+ disabled?: boolean;
+}
+
+export default function Input({ id, label, disabled, type = 'text' }: InputProps): React.ReactElement {
+ return (
+
+
+
+ {label}
+
+
+ );
+}
+
+Input.defaultProps = {
+ disabled: false,
+ type: 'text',
+};
diff --git a/src/shared/ui/LoadingAnimation.tsx b/src/shared/ui/LoadingAnimation.tsx
new file mode 100644
index 00000000..fd3a62a3
--- /dev/null
+++ b/src/shared/ui/LoadingAnimation.tsx
@@ -0,0 +1,192 @@
+import { motion } from 'framer-motion';
+
+export default function LoadingAnimation() {
+ const firstSushi = {
+ initial: { pathLength: 0 },
+ animate: { pathLength: 1 },
+ transition: { duration: 0.4 },
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/shared/ui/LoginNotRequired.tsx b/src/shared/ui/LoginNotRequired.tsx
new file mode 100644
index 00000000..bdd156c4
--- /dev/null
+++ b/src/shared/ui/LoginNotRequired.tsx
@@ -0,0 +1,21 @@
+import { ReactNode } from 'react';
+
+import { Navigate } from 'react-router-dom';
+
+import { useAppSelector } from '../lib/hooks';
+
+interface ILoginRequiredProps {
+ children: ReactNode;
+}
+
+function LoginNotRequired({ children }: ILoginRequiredProps) {
+ const { isLogged } = useAppSelector((state) => state.userReducer);
+
+ if (!isLogged) {
+ return ;
+ }
+
+ return children;
+}
+
+export default LoginNotRequired;
diff --git a/src/shared/ui/LoginRequired.tsx b/src/shared/ui/LoginRequired.tsx
new file mode 100644
index 00000000..3a7cef54
--- /dev/null
+++ b/src/shared/ui/LoginRequired.tsx
@@ -0,0 +1,21 @@
+import { ReactNode } from 'react';
+
+import { Navigate } from 'react-router-dom';
+
+import { useAppSelector } from '../lib/hooks';
+
+interface ILoginRequiredProps {
+ children: ReactNode;
+}
+
+function LoginRequired({ children }: ILoginRequiredProps) {
+ const { isLogged } = useAppSelector((state) => state.userReducer);
+
+ if (isLogged) {
+ return ;
+ }
+
+ return children;
+}
+
+export default LoginRequired;
diff --git a/src/shared/ui/MinusIcon.tsx b/src/shared/ui/MinusIcon.tsx
new file mode 100644
index 00000000..c2f3376e
--- /dev/null
+++ b/src/shared/ui/MinusIcon.tsx
@@ -0,0 +1,16 @@
+function MinusIcon() {
+ return (
+
+
+
+ );
+}
+
+export default MinusIcon;
diff --git a/src/shared/ui/NavigateToCategories.tsx b/src/shared/ui/NavigateToCategories.tsx
new file mode 100644
index 00000000..175795d2
--- /dev/null
+++ b/src/shared/ui/NavigateToCategories.tsx
@@ -0,0 +1,22 @@
+import { ReactNode } from 'react';
+
+import { Navigate } from 'react-router-dom';
+
+import { PRODUCT_PATH } from '../const';
+import { useGetPath } from '../lib/hooks';
+
+interface ILoginRequiredProps {
+ children: ReactNode;
+}
+
+function NavigateToCategories({ children }: ILoginRequiredProps) {
+ const path = useGetPath();
+
+ if (path !== PRODUCT_PATH) {
+ return ;
+ }
+
+ return children;
+}
+
+export default NavigateToCategories;
diff --git a/src/shared/ui/PlusIcon.tsx b/src/shared/ui/PlusIcon.tsx
new file mode 100644
index 00000000..6ad7447e
--- /dev/null
+++ b/src/shared/ui/PlusIcon.tsx
@@ -0,0 +1,17 @@
+function PlusIcon() {
+ return (
+
+
+
+
+ );
+}
+
+export default PlusIcon;
diff --git a/src/shared/ui/UserIcon.tsx b/src/shared/ui/UserIcon.tsx
new file mode 100644
index 00000000..d69bbcf2
--- /dev/null
+++ b/src/shared/ui/UserIcon.tsx
@@ -0,0 +1,15 @@
+export default function UserIcon(props: { isHeader: boolean }) {
+ const { isHeader } = props;
+ return (
+
+
+
+
+ );
+}
diff --git a/src/shared/ui/animations.tsx b/src/shared/ui/animations.tsx
new file mode 100644
index 00000000..36d74c3e
--- /dev/null
+++ b/src/shared/ui/animations.tsx
@@ -0,0 +1,99 @@
+export const inputAnimation = {
+ initial: { x: '-12%', opacity: 0 },
+ animate: { x: 0, opacity: 1 },
+ transition: {
+ type: 'spring',
+ stiffness: 560,
+ damping: 17,
+ },
+};
+
+export const svgAnimation = {
+ initial: { y: '110%', scale: 0, rotate: '30deg' },
+ animate: { y: '0%', scale: 1, rotate: '0' },
+ transition: {
+ type: 'spring',
+ stiffness: 520,
+ damping: 15,
+ delay: 0.1,
+ },
+};
+
+export const pageVariants = {
+ initial: {
+ opacity: 0,
+ rotateY: '-90deg',
+ transition: {
+ type: 'spring',
+ stiffness: 110,
+ },
+ },
+ in: {
+ opacity: 1,
+ rotateY: '0deg',
+ transition: {
+ type: 'spring',
+ stiffness: 110,
+ },
+ },
+ out: {
+ scale: 5,
+ opacity: 0.5,
+ },
+};
+
+export const checkboxAnimation = {
+ initial: { scale: 0 },
+ animate: { scale: 1 },
+ transitionLabel: {
+ type: 'spring',
+ stiffness: 660,
+ damping: 38,
+ delay: 0.4,
+ },
+ transitionInput: {
+ type: 'spring',
+ stiffness: 660,
+ damping: 23,
+ delay: 0.3,
+ },
+};
+
+export const buttonTapAnimation = {
+ scale: 0.9,
+};
+
+export const buttonTransition = {
+ duration: 0.02,
+};
+
+export const itemInitial = {
+ opacity: 0,
+ scale: 0.7,
+};
+
+export const itemAnimation = {
+ opacity: 1,
+ scale: 1,
+};
+
+export const itemTransition = {
+ type: 'spring',
+ stiffness: 800,
+ damping: 35,
+};
+
+export const itemExit = {
+ opacity: 0,
+ scale: 0,
+};
+
+export const emptyCartInitial = { opacity: 0, scale: 0 };
+
+export const emptyCartAnimate = { opacity: 1, scale: 1 };
+
+export const emptyCartTransition = {
+ type: 'spring',
+ stiffness: 560,
+ damping: 20,
+};
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
new file mode 100644
index 00000000..d1d64b91
--- /dev/null
+++ b/src/shared/ui/index.ts
@@ -0,0 +1,22 @@
+import AnimateCounter from './AnimateCounter.tsx';
+import Blackout from './Blackout.tsx';
+import Button from './Button.tsx';
+import ErrorMessage from './ErrorMessage.tsx';
+import Input from './Input.tsx';
+import LoginNotRequired from './LoginNotRequired.tsx';
+import LoginRequired from './LoginRequired.tsx';
+import NavigateToCategories from './NavigateToCategories.tsx';
+import PlusIcon from './PlusIcon.tsx';
+
+export {
+ Button,
+ Input,
+ ErrorMessage,
+ LoginRequired,
+ NavigateToCategories,
+ Blackout,
+ AnimateCounter,
+ LoginNotRequired,
+ PlusIcon,
+};
+export { checkboxAnimation, inputAnimation, svgAnimation, pageVariants } from './animations.tsx';
diff --git a/src/test/ApiRequests.test.tsx b/src/test/ApiRequests.test.tsx
new file mode 100644
index 00000000..7709f833
--- /dev/null
+++ b/src/test/ApiRequests.test.tsx
@@ -0,0 +1,83 @@
+import { act, renderHook } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { describe, it } from 'vitest';
+
+import { TEST_ACCOUNT_EMAIL, TEST_ACCOUNT_PASSWORD } from './constants';
+import { setupStore } from '../app/store';
+import { useAnonymousSessionMutation, useLoginTokenMutation, useRevokeTokenMutation } from '../entities/user';
+import { TokenTypeHints } from '../shared/types';
+
+interface ICleanUpTokens {
+ accessTokens: string[];
+ refreshTokens: string[];
+}
+
+const mockUserData = { email: TEST_ACCOUNT_EMAIL, password: TEST_ACCOUNT_PASSWORD };
+const tokensToCleanUp: ICleanUpTokens = {
+ accessTokens: [],
+ refreshTokens: [],
+};
+
+function wrapper({ children }: { children: React.ReactNode }) {
+ const store = setupStore();
+ return {children} ;
+}
+
+describe('Api requests', () => {
+ afterAll(async () => {
+ const { result } = renderHook(() => useRevokeTokenMutation(), { wrapper });
+
+ const [revokeToken] = result.current;
+ const { accessTokens, refreshTokens } = tokensToCleanUp;
+
+ accessTokens.map((token) => {
+ return act(() => {
+ revokeToken({ token, tokenTypeHint: TokenTypeHints.ACCESS_TOKEN });
+ });
+ });
+
+ refreshTokens.map((token) => {
+ return act(() => {
+ revokeToken({ token, tokenTypeHint: TokenTypeHints.REFRESH_TOKEN });
+ });
+ });
+ });
+
+ it('Getting the anonymous token', async () => {
+ const { result } = renderHook(() => useAnonymousSessionMutation(), { wrapper });
+
+ const [getAnonToken, initialResponse] = result.current;
+
+ expect(initialResponse.data).toBeUndefined();
+ expect(initialResponse.isLoading).toBe(false);
+
+ await act(() => getAnonToken());
+
+ const [, loadedResponse] = result.current;
+ expect(loadedResponse.data).not.toBeUndefined();
+ expect(loadedResponse.isLoading).toBe(false);
+ expect(loadedResponse.isSuccess).toBe(true);
+
+ tokensToCleanUp.accessTokens.push(loadedResponse?.data?.access_token as string);
+ tokensToCleanUp.refreshTokens.push(loadedResponse?.data?.refresh_token as string);
+ });
+
+ it('Getting the login token', async () => {
+ const { result: loginRequestResult } = renderHook(() => useLoginTokenMutation(), { wrapper });
+
+ const [loginUser, loginInitialResponse] = loginRequestResult.current;
+
+ expect(loginInitialResponse.data).toBeUndefined();
+ expect(loginInitialResponse.isLoading).toBe(false);
+
+ await act(() => loginUser(mockUserData));
+
+ const [, loginLoadedResponse] = loginRequestResult.current;
+ expect(loginLoadedResponse.data).not.toBeUndefined();
+ expect(loginLoadedResponse.isLoading).toBe(false);
+ expect(loginLoadedResponse.isSuccess).toBe(true);
+
+ tokensToCleanUp.accessTokens.push(loginLoadedResponse?.data?.access_token as string);
+ tokensToCleanUp.refreshTokens.push(loginLoadedResponse?.data?.refresh_token as string);
+ });
+});
diff --git a/src/test/App.test.tsx b/src/test/App.test.tsx
new file mode 100644
index 00000000..bc0ddac9
--- /dev/null
+++ b/src/test/App.test.tsx
@@ -0,0 +1,65 @@
+import { screen } from '@testing-library/react';
+import { describe, it } from 'vitest';
+
+import RenderTestApp from './helpers/RenderTestApp.tsx';
+import { App } from '../app/App.tsx';
+
+describe('App', () => {
+ it('Renders the main logo', () => {
+ RenderTestApp( );
+
+ expect(
+ screen.getByRole('heading', {
+ level: 1,
+ }),
+ ).toHaveTextContent('SushiSushi');
+ });
+
+ it('Renders the not found page if invalid path', () => {
+ RenderTestApp( , '/eCommerce-Application/test-for-not-found-route');
+
+ expect(
+ screen.getByRole('heading', {
+ level: 5,
+ }),
+ ).toHaveTextContent('404 error');
+ });
+
+ it('Renders the login page', () => {
+ RenderTestApp( , '/login');
+
+ expect(
+ screen.getByRole('heading', {
+ level: 5,
+ }),
+ ).toHaveTextContent('Log in');
+
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Sign up' })).toBeInTheDocument();
+ });
+
+ it('Renders the sign up page', () => {
+ RenderTestApp( , '/registration');
+
+ expect(screen.getByText('Email & Password', { exact: false })).toBeInTheDocument();
+
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Log in' })).toBeInTheDocument();
+ });
+
+ // it('Renders the menu page', () => {
+ // RenderTestApp( );
+ //
+ // expect(screen.getByText('Here will be main content', { exact: false })).toBeInTheDocument();
+ // });
+ //
+ // it('Renders the about page', () => {
+ // RenderTestApp( , '/about');
+ //
+ // expect(screen.getByText('About us', { exact: false })).toBeInTheDocument();
+ // });
+});
diff --git a/src/test/LogOut.test.tsx b/src/test/LogOut.test.tsx
new file mode 100644
index 00000000..d8ddc971
--- /dev/null
+++ b/src/test/LogOut.test.tsx
@@ -0,0 +1,37 @@
+import { screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { describe, it, vi } from 'vitest';
+
+import RenderTestApp from './helpers/RenderTestApp.tsx';
+import { App } from '../app/App.tsx';
+import { userSlice } from '../entities/user';
+import * as helpers from '../shared/lib/helpers';
+
+const deleteCookieSpy = vi.spyOn(helpers, 'deleteCookie');
+const loggedOutSpy = vi.spyOn(userSlice.actions, 'loggedOut');
+
+describe('LogOut', () => {
+ afterEach(() => {
+ vi.clearAllTimers();
+ vi.clearAllMocks();
+ });
+
+ it('Logout is working', async () => {
+ RenderTestApp( , '/login', {
+ userReducer: { isLogged: true, accessToken: '', refreshToken: '', userId: '', cartId: '' },
+ });
+
+ expect(screen.getAllByText(/log out/i)[0]).toBeInTheDocument();
+
+ await userEvent.click(screen.getAllByText(/log out/i)[0]);
+
+ await waitFor(() => {
+ expect(loggedOutSpy).toBeCalledTimes(1);
+ expect(deleteCookieSpy).toBeCalledTimes(1);
+ });
+
+ expect(screen.queryByRole('button', { name: 'Log out' })).toBeNull();
+
+ expect(window.location.pathname).toBe('/');
+ });
+});
diff --git a/src/test/LoginPage.test.tsx b/src/test/LoginPage.test.tsx
new file mode 100644
index 00000000..6990d644
--- /dev/null
+++ b/src/test/LoginPage.test.tsx
@@ -0,0 +1,145 @@
+import { fireEvent, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { describe, it, vi } from 'vitest';
+
+import RenderTestApp from './helpers/RenderTestApp.tsx';
+import { App } from '../app/App.tsx';
+import { userSlice } from '../entities/user';
+import LoginPage from '../pages/LoginPage/LoginPage.tsx';
+import * as helpers from '../shared/lib/helpers';
+
+const loggedInSpy = vi.spyOn(userSlice.actions, 'loggedIn');
+const setCookieSpy = vi.spyOn(helpers, 'setCookie');
+const timeout = 5000;
+
+describe('LoginPage', () => {
+ afterEach(() => {
+ vi.clearAllTimers();
+ vi.clearAllMocks();
+ });
+
+ it('Renders the form', () => {
+ RenderTestApp( );
+
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+ expect(screen.getByRole('checkbox')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();
+ expect(screen.getByText('Sign up')).toBeInTheDocument();
+ });
+
+ it('Hides the password', () => {
+ RenderTestApp( );
+
+ expect(screen.getByRole('checkbox')).not.toBeChecked();
+
+ fireEvent.input(screen.getByPlaceholderText('Password'), {
+ target: { value: '123' },
+ });
+
+ fireEvent.click(screen.getByRole('checkbox'));
+
+ expect(screen.getByRole('checkbox')).toBeChecked();
+ expect(screen.getByPlaceholderText('Password')).toHaveValue('123');
+ expect(screen.getByPlaceholderText('Password')).toHaveAttribute('type', 'text');
+
+ fireEvent.click(screen.getByRole('checkbox'));
+
+ expect(screen.getByRole('checkbox')).not.toBeChecked();
+ expect(screen.getByPlaceholderText('Password')).toHaveValue('123');
+ expect(screen.getByPlaceholderText('Password')).toHaveAttribute('type', 'password');
+ });
+
+ it('Show error on wrong Email input', async () => {
+ RenderTestApp( );
+
+ await userEvent.click(screen.getByPlaceholderText('Email'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Email')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Email is required')).toBeInTheDocument();
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Email')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Email must be email@example.com')).toBeInTheDocument();
+ });
+
+ it('Show error on wrong Password input', async () => {
+ RenderTestApp( );
+
+ await userEvent.click(screen.getByPlaceholderText('Password'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Password')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('password is required', { exact: false })).toBeInTheDocument();
+
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'test');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Password')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Minimum 8 symbols required')).toBeInTheDocument();
+
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testtesttest');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Password')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Password must have A, a, 1 and special symbols')).toBeInTheDocument();
+ });
+
+ it('Show errors on wrong submit', async () => {
+ RenderTestApp( , '/login');
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'testemail@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'dasdasD12#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Log in' }));
+
+ await waitFor(
+ () => {
+ expect(screen.getByText('Oh snap!', { exact: false })).toBeInTheDocument();
+ expect(screen.getByText('Continue', { exact: false })).toBeInTheDocument();
+ },
+ { timeout },
+ );
+
+ await userEvent.click(screen.getByText('Continue', { exact: false }));
+
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+ });
+
+ it('Route to the main page and not to send new request on the second login', async () => {
+ RenderTestApp( , '/login', {
+ userReducer: { isLogged: true, accessToken: '', refreshToken: '', userId: '', cartId: '' },
+ });
+
+ expect(screen.getAllByText(/log out/i)[0]).toBeInTheDocument();
+ expect(screen.queryByText('log in', { exact: false })).toBeNull();
+
+ expect(loggedInSpy).toBeCalledTimes(0);
+ expect(setCookieSpy).toBeCalledTimes(0);
+
+ expect(window.location.pathname).toBe('/');
+ });
+
+ it('Route between log in & sign up', async () => {
+ RenderTestApp( , '/login');
+
+ const signUpBtn = screen.getByRole('link', { name: 'Sign up' });
+ expect(signUpBtn).toBeInTheDocument();
+ await userEvent.click(signUpBtn);
+
+ const logIn = screen.getByRole('link', { name: 'Log in' });
+ expect(logIn).toBeInTheDocument();
+ await userEvent.click(logIn);
+
+ const signUpBtn2 = screen.getByRole('link', { name: 'Sign up' });
+ expect(signUpBtn2).toBeInTheDocument();
+ await userEvent.click(signUpBtn2);
+
+ const logIn2 = screen.getByRole('link', { name: 'Log in' });
+ expect(logIn2).toBeInTheDocument();
+ });
+});
diff --git a/src/test/RegPage.test.tsx b/src/test/RegPage.test.tsx
new file mode 100644
index 00000000..4ebe64e4
--- /dev/null
+++ b/src/test/RegPage.test.tsx
@@ -0,0 +1,703 @@
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { describe, it, vi } from 'vitest';
+
+import RenderTestApp from './helpers/RenderTestApp.tsx';
+import RegPage from '../pages/RegPage/RegPage.tsx';
+
+describe('RegPage', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('Renders the first step', () => {
+ RenderTestApp( );
+
+ const backBtn = screen.getByRole('button', { name: 'Back' });
+ const continueBtn = screen.getByRole('button', { name: 'Continue' });
+
+ expect(screen.getByText('Email & Password')).toBeInTheDocument();
+
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+
+ expect(backBtn).toBeInTheDocument();
+ expect(backBtn).toBeDisabled();
+
+ expect(continueBtn).toBeInTheDocument();
+ expect(continueBtn).toBeDisabled();
+ });
+
+ it('First step empty inputs', async () => {
+ RenderTestApp( );
+
+ await userEvent.click(screen.getByPlaceholderText('Email'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Email')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Email is required')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByPlaceholderText('Password'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Password')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Password is required')).toBeInTheDocument();
+ });
+
+ it('First step wrong inputs', async () => {
+ RenderTestApp( );
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'testtesttest');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Email')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Email must be email@example.com')).toBeInTheDocument();
+
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testtesttest');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Password')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Password must have A, a, 1 and special symbols')).toBeInTheDocument();
+ });
+
+ it('First step correct inputs', async () => {
+ RenderTestApp( );
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Email')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Email is required')).toBeNull();
+
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Password')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Password is required')).toBeNull();
+
+ const continueBtn = screen.getByRole('button', { name: 'Continue' });
+
+ expect(continueBtn).toBeEnabled();
+ await userEvent.click(continueBtn);
+
+ expect(screen.queryByPlaceholderText('Email')).toBeNull();
+ expect(screen.queryByPlaceholderText('Password')).toBeNull();
+ expect(screen.getByRole('button', { name: 'Back' })).toBeEnabled();
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled();
+ });
+
+ it('Renders the second step', async () => {
+ RenderTestApp( );
+
+ /// ////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// ////////////////////////////////////
+ // Actual test
+
+ const backBtn = screen.getByRole('button', { name: 'Back' });
+ const continueBtn = screen.getByRole('button', { name: 'Continue' });
+
+ expect(screen.getByText('Name & Birth')).toBeInTheDocument();
+
+ expect(screen.getByPlaceholderText('First name')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Last name')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Birth date')).toBeInTheDocument();
+
+ expect(backBtn).toBeInTheDocument();
+ expect(backBtn).toBeEnabled();
+
+ expect(continueBtn).toBeInTheDocument();
+ expect(continueBtn).toBeDisabled();
+ });
+
+ it('Second step empty inputs', async () => {
+ RenderTestApp( );
+
+ /// /////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////
+ // Actual test
+
+ await userEvent.click(screen.getByPlaceholderText('First name'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('First name')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Last name is required')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByPlaceholderText('Last name'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Last name')).toHaveClass('border-shop-cart-red');
+ screen.getAllByText('Last name is required').forEach((match) => {
+ expect(match).toBeInTheDocument();
+ });
+
+ await userEvent.click(screen.getByPlaceholderText('Birth date'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Birth date')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Birth date is required')).toBeInTheDocument();
+ });
+
+ it('Second step wrong inputs', async () => {
+ RenderTestApp( );
+
+ /// //////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// //////////////////////////////////////
+ // Actual test
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'dsadasdasdasdsadsaddd');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('First name')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Too long name')).toBeInTheDocument();
+
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'dsadasdasdasdsadsaddd');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Last name')).toHaveClass('border-shop-cart-red');
+ screen.getAllByText('Too long name').forEach((match) => {
+ expect(match).toBeInTheDocument();
+ });
+
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2020-02-02');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Birth date')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Age restriction: 13+')).toBeInTheDocument();
+ });
+
+ it('Second step correct inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// ///////////////////////////////////////
+ // Actual test
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('First name')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Too long name')).toBeNull();
+ expect(screen.queryByText('Last name is required')).toBeNull();
+
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Last name')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Too long name')).toBeNull();
+ expect(screen.queryByText('Last name is required')).toBeNull();
+
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Birth date')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Age restriction: 13+')).toBeNull();
+ expect(screen.queryByText('Birth date is required')).toBeNull();
+
+ expect(screen.getByRole('button', { name: 'Back' })).toBeEnabled();
+
+ const continueBtn = screen.getByRole('button', { name: 'Continue' });
+ expect(continueBtn).toBeEnabled();
+
+ await userEvent.click(continueBtn);
+
+ expect(screen.queryByPlaceholderText('First name')).toBeNull();
+ expect(screen.queryByPlaceholderText('Last name')).toBeNull();
+ expect(screen.queryByPlaceholderText('Birth date')).toBeNull();
+ });
+
+ it('Renders the third step', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ const backBtn = screen.getByRole('button', { name: 'Back' });
+ const continueBtn = screen.getByRole('button', { name: 'Continue' });
+ const useSameAddressCheckbox = screen.getByTestId('checkbox');
+
+ expect(screen.getByText('Country & City')).toBeInTheDocument();
+ expect(screen.getByTestId('select')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('City')).toBeInTheDocument();
+ expect(useSameAddressCheckbox).toBeInTheDocument();
+ expect(useSameAddressCheckbox).toBeChecked();
+
+ expect(backBtn).toBeInTheDocument();
+ expect(backBtn).toBeEnabled();
+ expect(continueBtn).toBeInTheDocument();
+ expect(continueBtn).toBeDisabled();
+
+ const options = screen.getAllByTestId('select-option');
+ expect(options[0]).toHaveValue('US');
+ expect(options[1]).toHaveValue('UA');
+ expect(options[2]).toHaveValue('DE');
+
+ expect(options[0]).toHaveTextContent('USA');
+ expect(options[1]).toHaveTextContent('Ukraine');
+ expect(options[2]).toHaveTextContent('Germany');
+
+ await userEvent.click(useSameAddressCheckbox);
+
+ expect(screen.getByTestId('checkbox')).not.toBeChecked();
+
+ screen.getAllByTestId('select').forEach((select) => {
+ expect(select).toBeInTheDocument();
+ });
+
+ screen.getAllByPlaceholderText('City').forEach((input) => {
+ expect(input).toBeInTheDocument();
+ });
+ });
+
+ it('Third step empty inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ await userEvent.click(screen.getByPlaceholderText('City'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('City')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('City name is required')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByTestId('checkbox'));
+
+ const cities = screen.getAllByPlaceholderText('City');
+
+ const waitUserEvents = cities.map(async (city) => {
+ await userEvent.click(city);
+ await userEvent.tab();
+ });
+
+ await Promise.all(waitUserEvents);
+
+ cities.forEach((city) => {
+ expect(city).toHaveClass('border-shop-cart-red');
+ });
+
+ expect(screen.getAllByText('City name is required')).toHaveLength(2);
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled();
+ });
+
+ it('Third step wrong inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'dsadasdasdasdsadsaddd');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('City')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Too long name')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByTestId('checkbox'));
+
+ const cities = screen.getAllByPlaceholderText('City');
+
+ const waitUserEvents = cities.map(async (city) => {
+ await userEvent.type(city, 'dsadasdasdasdsadsaddd');
+ await userEvent.tab();
+ });
+
+ await Promise.all(waitUserEvents);
+
+ cities.forEach((city) => {
+ expect(city).toHaveClass('border-shop-cart-red');
+ });
+
+ expect(screen.getAllByText('Too long name')).toHaveLength(2);
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled();
+ });
+
+ it('Third step correct inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'test');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('City')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Too long name')).toBeNull();
+
+ await userEvent.click(screen.getByTestId('checkbox'));
+
+ const cities = screen.getAllByPlaceholderText('City');
+
+ const waitUserEvents = cities.map(async (city) => {
+ await userEvent.type(city, 'test');
+ await userEvent.tab();
+ });
+
+ await Promise.all(waitUserEvents);
+
+ cities.forEach((city) => {
+ expect(city).not.toHaveClass('border-shop-cart-red');
+ });
+
+ expect(screen.queryAllByText('Too long name')).toHaveLength(0);
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeEnabled();
+ });
+
+ it('Third step select is working properly', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ const options = screen.getAllByTestId('select-option');
+ expect((options.at(0) as HTMLOptionElement).selected).toBeTruthy();
+ expect((options.at(1) as HTMLOptionElement).selected).toBeFalsy();
+ expect((options.at(2) as HTMLOptionElement).selected).toBeFalsy();
+
+ await userEvent.selectOptions(screen.getByTestId('select'), 'UA');
+
+ const options2 = screen.getAllByTestId('select-option');
+
+ expect((options2.at(0) as HTMLOptionElement).selected).toBeFalsy();
+ expect((options2.at(1) as HTMLOptionElement).selected).toBeTruthy();
+ expect((options2.at(2) as HTMLOptionElement).selected).toBeFalsy();
+
+ await userEvent.selectOptions(screen.getByTestId('select'), 'DE');
+
+ const options3 = screen.getAllByTestId('select-option');
+
+ expect((options3.at(0) as HTMLOptionElement).selected).toBeFalsy();
+ expect((options3.at(1) as HTMLOptionElement).selected).toBeFalsy();
+ expect((options3.at(2) as HTMLOptionElement).selected).toBeTruthy();
+ });
+
+ it('Renders the fourth step', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the third step
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'test');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ const backBtn = screen.getByRole('button', { name: 'Back' });
+ const continueBtn = screen.getByRole('button', { name: 'Continue' });
+
+ expect(screen.getByText('PC & Street')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Postal code')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Street')).toBeInTheDocument();
+
+ expect(backBtn).toBeInTheDocument();
+ expect(backBtn).toBeEnabled();
+ expect(continueBtn).toBeInTheDocument();
+ expect(continueBtn).toBeDisabled();
+ });
+
+ it('Fourth step empty inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the third step
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'test');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ await userEvent.click(screen.getByPlaceholderText('Postal code'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Postal code')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('PostalCode is required')).toBeInTheDocument();
+
+ await userEvent.click(screen.getByPlaceholderText('Street'));
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Street')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Street name is required')).toBeInTheDocument();
+
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled();
+ });
+
+ it('Fourth step wrong inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the third step
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'test');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ await userEvent.type(screen.getByPlaceholderText('Postal code'), 'dsadasdasdasdsadsaddd');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Postal code')).toHaveClass('border-shop-cart-red');
+ expect(screen.getByText('Enter valid postal code')).toBeInTheDocument();
+
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled();
+ });
+
+ it('Third step correct inputs', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'test@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the third step
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'test');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ await userEvent.type(screen.getByPlaceholderText('Postal code'), '33333-3333');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Postal code')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Enter valid postal code')).toBeNull();
+
+ await userEvent.type(screen.getByPlaceholderText('Street'), 'street');
+ await userEvent.tab();
+
+ expect(screen.getByPlaceholderText('Street')).not.toHaveClass('border-shop-cart-red');
+ expect(screen.queryByText('Street name is required')).toBeNull();
+
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeEnabled();
+ });
+
+ it('Failed submit', async () => {
+ RenderTestApp( );
+
+ /// ///////////////////////////////////////
+ // Skip the first step
+
+ await userEvent.type(screen.getByPlaceholderText('Email'), 'MyEmail@gmail.com');
+ await userEvent.type(screen.getByPlaceholderText('Password'), 'testPassword123#');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the second step
+
+ await userEvent.type(screen.getByPlaceholderText('First name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Last name'), 'test');
+ await userEvent.type(screen.getByPlaceholderText('Birth date'), '2000-02-02');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the third step
+
+ await userEvent.type(screen.getByPlaceholderText('City'), 'test');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Skip the fourth step
+
+ await userEvent.type(screen.getByPlaceholderText('Postal code'), '33333-3333');
+ await userEvent.type(screen.getByPlaceholderText('Street'), 'street');
+
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ /// /////////////////////////////////////
+ // Actual test
+
+ const errorMessage = await screen.findByText('Oh snap!');
+ const description = await screen.findByText('Change a few things up and try again');
+ const continueBtn = await screen.findByText('Continue');
+
+ expect(errorMessage).toBeInTheDocument();
+ expect(description).toBeInTheDocument();
+ expect(continueBtn).toBeInTheDocument();
+ });
+});
diff --git a/src/test/constants/constants.ts b/src/test/constants/constants.ts
new file mode 100644
index 00000000..1f5d2321
--- /dev/null
+++ b/src/test/constants/constants.ts
@@ -0,0 +1,3 @@
+export const TEST_ACCOUNT_EMAIL = 'MyEmail@gmail.com';
+export const TEST_ACCOUNT_PASSWORD = '5i3wryMh@';
+export const UPDATE_TIMEOUT = 3000;
diff --git a/src/test/constants/index.ts b/src/test/constants/index.ts
new file mode 100644
index 00000000..0db0f37d
--- /dev/null
+++ b/src/test/constants/index.ts
@@ -0,0 +1 @@
+export { UPDATE_TIMEOUT, TEST_ACCOUNT_EMAIL, TEST_ACCOUNT_PASSWORD } from './constants.ts';
diff --git a/src/test/helpers/RenderReduxStore.tsx b/src/test/helpers/RenderReduxStore.tsx
new file mode 100644
index 00000000..ee642dde
--- /dev/null
+++ b/src/test/helpers/RenderReduxStore.tsx
@@ -0,0 +1,22 @@
+import { render } from '@testing-library/react';
+import { Provider } from 'react-redux';
+
+import { RootState, setupStore } from '../../app/store';
+
+/**
+ * Renders a React component with a Redux store.
+ *
+ * @param {React.ReactElement} component - The component to render.
+ * @param {RootState} initialState - The initial state of the Redux store.
+ *
+ * @returns {RootState} - The root state of the test app.
+ */
+function renderReduxStore(component: React.ReactElement, initialState?: Partial): RootState {
+ const store = setupStore(initialState);
+
+ render({component} );
+
+ return store.getState();
+}
+
+export default renderReduxStore;
diff --git a/src/test/helpers/RenderTestApp.tsx b/src/test/helpers/RenderTestApp.tsx
new file mode 100644
index 00000000..abe858cc
--- /dev/null
+++ b/src/test/helpers/RenderTestApp.tsx
@@ -0,0 +1,34 @@
+import { ReactElement } from 'react';
+
+import { render } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+
+import { RootState, setupStore } from '../../app/store';
+
+/**
+ * Renders a test app with the provided component, initial route, and initial state.
+ *
+ * @param {ReactElement} component - The component to be rendered in the test app.
+ * @param {string} initialRoute - The initial route for the MemoryRouter.
+ * @param {RootState} initialState - The initial state for the store.
+ *
+ * @return {RootState} The root state of the test app.
+ */
+function RenderTestApp(
+ component: ReactElement,
+ initialRoute: string = '/',
+ initialState?: Partial,
+): RootState {
+ const store = setupStore(initialState);
+
+ render(
+
+ {component}
+ ,
+ );
+
+ return store.getState();
+}
+
+export default RenderTestApp;
diff --git a/src/test/helpers/RenderWithRouter.tsx b/src/test/helpers/RenderWithRouter.tsx
new file mode 100644
index 00000000..c9b5bd12
--- /dev/null
+++ b/src/test/helpers/RenderWithRouter.tsx
@@ -0,0 +1,16 @@
+import { render } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+
+/**
+ * Renders the given component with a router.
+ *
+ * @param {React.ReactElement} component - The component to be rendered.
+ * @param {string} initialRoute - The initial route of the router. Default value is '/'.
+ *
+ * @returns {void}
+ */
+function RenderWithRouter(component: React.ReactElement, initialRoute: string = '/'): void {
+ render({component} );
+}
+
+export default RenderWithRouter;
diff --git a/src/test/setupTest.ts b/src/test/setupTest.ts
new file mode 100644
index 00000000..9fc1d287
--- /dev/null
+++ b/src/test/setupTest.ts
@@ -0,0 +1,18 @@
+import matchers from '@testing-library/jest-dom/matchers';
+import { expect, vi } from 'vitest';
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(), // deprecated
+ removeListener: vi.fn(), // deprecated
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+});
+
+expect.extend(matchers);
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/widgets/CartItem/CartItem.tsx b/src/widgets/CartItem/CartItem.tsx
new file mode 100644
index 00000000..bbb1e715
--- /dev/null
+++ b/src/widgets/CartItem/CartItem.tsx
@@ -0,0 +1,233 @@
+import { motion } from 'framer-motion';
+import { useSelector } from 'react-redux';
+import { useLocation } from 'react-router-dom';
+
+import { RootState } from '../../app/store';
+import { useGetCartByIdQuery, useUpdateCartMutation } from '../../entities/cart';
+import { AddLineItemRequestBody, RemoveLineItemRequestBody } from '../../entities/cart/types/types';
+import { ProductAttributeNames, useGetProductQuery } from '../../entities/product';
+import formatPrice from '../../entities/product/lib/helpers/formatPrice.ts';
+import pennieToMoney from '../../entities/product/lib/helpers/pennieToMoney.ts';
+import { ProductPrice } from '../../entities/product/types/types.ts';
+import getAttribute from '../../pages/ProductPage/lib/helpers/getAttribute';
+import { padZero } from '../../shared/lib/helpers';
+import { PlusIcon } from '../../shared/ui';
+import {
+ buttonTapAnimation,
+ buttonTransition,
+ itemAnimation,
+ itemExit,
+ itemInitial,
+ itemTransition,
+} from '../../shared/ui/animations.tsx';
+import LoadingAnimation from '../../shared/ui/LoadingAnimation';
+import MinusIcon from '../../shared/ui/MinusIcon.tsx';
+
+interface ICartItemProps {
+ productId: string;
+ id: string;
+ quantity: number;
+ delay: number;
+}
+
+export default function CartItem(props: ICartItemProps) {
+ const { productId, id: lineItemId, quantity, delay } = props;
+ const { data } = useGetProductQuery(productId);
+ const cartId = useSelector((state: RootState) => state.userReducer.cartId);
+ const { data: cart } = useGetCartByIdQuery(cartId);
+ const [updateCart, { isLoading: updateIsLoading }] = useUpdateCartMutation();
+ const pathName = useLocation().pathname;
+
+ const isCart = pathName === '/cart';
+ const cartVersion = cart?.version || 1;
+
+ const addToCart = async () => {
+ const body: AddLineItemRequestBody = {
+ version: cartVersion,
+ actions: [
+ {
+ action: 'addLineItem',
+ productId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ };
+ try {
+ return await updateCart({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ return null;
+ };
+
+ const removeOneFromCart = async () => {
+ try {
+ const body: RemoveLineItemRequestBody = {
+ version: cartVersion,
+ actions: [
+ {
+ action: 'removeLineItem',
+ lineItemId,
+ variantId: 1,
+ quantity: 1,
+ },
+ ],
+ };
+ return await updateCart({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ return null;
+ };
+
+ const removeAllFromCart = async () => {
+ try {
+ const body: RemoveLineItemRequestBody = {
+ version: cartVersion,
+ actions: [
+ {
+ action: 'removeLineItem',
+ lineItemId,
+ variantId: 1,
+ },
+ ],
+ };
+ return await updateCart({ cartId, body }).unwrap();
+ } catch (e) {
+ // throw new Error(e);
+ }
+ return null;
+ };
+
+ if (!data)
+ return (
+
+
+
+ );
+
+ const { attributes, prices, images } = data.masterVariant;
+ const {
+ discounted: { value: { centAmount: discountPrice = undefined } = {} } = {},
+ value: { centAmount: currPrice, currencyCode },
+ } = prices.at(0) as ProductPrice;
+
+ const centPrice = discountPrice ?? currPrice;
+ const centOldPrice = discountPrice ? currPrice : null;
+
+ const corePrice = formatPrice(pennieToMoney(centPrice), currencyCode);
+ const oldPrice = centOldPrice ? formatPrice(pennieToMoney(centOldPrice), currencyCode) : null;
+
+ const name = data.name.en;
+ const imgUrl = images[0].url as string;
+ const calories = getAttribute(attributes, ProductAttributeNames.CALORIES);
+ const weight = getAttribute(attributes, ProductAttributeNames.WEIGHT);
+
+ return (
+
+
+
+
+
+
+
+ {name}
+
+
+ {calories}kcal
+ {weight} g
+
+
+
+ ร
+
+
+ {oldPrice ? (
+
+ {oldPrice}
+
+ ) : null}
+
+ {corePrice}
+
+
+
+
+
+
+
+
{padZero(quantity)}
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/Header/Header.tsx b/src/widgets/Header/Header.tsx
new file mode 100644
index 00000000..fc5480be
--- /dev/null
+++ b/src/widgets/Header/Header.tsx
@@ -0,0 +1,108 @@
+import { stagger, useAnimate, useCycle } from 'framer-motion';
+import { Link } from 'react-router-dom';
+
+import LogOutBtn from '../../features/LogOutBtn/LogOutBtn';
+import SearchInput from '../../features/SearchInput/SearchInput.tsx';
+import UserProfileLink from '../../features/UserProfileLink/UserProfileLink';
+import { LOGO_NAME } from '../../shared/const';
+import Logo from '../ui/Logo';
+
+function Header() {
+ const [scope, animate] = useAnimate();
+ const [isSearchActive, setIsSearchActive] = useCycle(false, true);
+
+ const animationEndHandler = () => {
+ if (scope.current) {
+ animate([['.letter', { y: 5, opacity: 1 }, { duration: 0.3, delay: stagger(0.1) }]]);
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default Header;
diff --git a/src/widgets/MenuItem/MenuItem.tsx b/src/widgets/MenuItem/MenuItem.tsx
new file mode 100644
index 00000000..eda1fc13
--- /dev/null
+++ b/src/widgets/MenuItem/MenuItem.tsx
@@ -0,0 +1,93 @@
+import { useState } from 'react';
+
+import { Link } from 'react-router-dom';
+import StarsRating from 'react-star-rate';
+
+import spicyIcon from '../../assets/icons/spicy.svg';
+import veganIcon from '../../assets/icons/vegan.svg';
+import { ProductAttribute, ProductAttributeNames } from '../../entities/product';
+import formatPrice from '../../entities/product/lib/helpers/formatPrice.ts';
+import pennieToMoney from '../../entities/product/lib/helpers/pennieToMoney.ts';
+import { ProductPrice } from '../../entities/product/types/types.ts';
+import AddToCartBtn from '../../features/AddToCart/AddToCartBtn';
+import getAttribute from '../../pages/ProductPage/lib/helpers/getAttribute.ts';
+
+interface IMenuItemProps {
+ name: string;
+ image: string;
+ id: string;
+ attributes: ProductAttribute[];
+ prices: ProductPrice[];
+ isSpicy: boolean;
+ isVegan: boolean;
+}
+
+export default function MenuItem({ name, image, id, attributes, prices, isSpicy, isVegan }: IMenuItemProps) {
+ const [rating, setRating] = useState(4.5);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const {
+ discounted: { value: { centAmount: discountPrice = undefined } = {} } = {},
+ value: { centAmount: currPrice, currencyCode },
+ } = prices.at(0) as ProductPrice;
+
+ const centPrice = discountPrice ?? currPrice;
+ const centOldPrice = discountPrice ? currPrice : null;
+
+ const calories = getAttribute(attributes, ProductAttributeNames.CALORIES);
+ const weight = getAttribute(attributes, ProductAttributeNames.WEIGHT);
+
+ const corePrice = formatPrice(pennieToMoney(centPrice), currencyCode);
+ const oldPrice = centOldPrice ? formatPrice(pennieToMoney(centOldPrice), currencyCode) : null;
+
+ return (
+
+
+
+
+
setIsLoading(false)}
+ loading="lazy"
+ className={`${
+ isLoading ? 'h-full w-48 opacity-0' : 'opacity-100'
+ } inline-block h-full max-w-[50%] flex-none rounded-2xl object-cover transition-all duration-300 ease-bounce md:w-[160px]`}
+ src={image}
+ alt={name}
+ />
+ {isSpicy || isVegan ? (
+
+
+
+ ) : null}
+
+
+
{name}
+
+
{calories} kcal
+ {weight} g
+
+
+
+
{
+ setRating(rating);
+ }}
+ />
+ {rating}
+
+
+
+
+ {oldPrice ? (
+
{oldPrice}
+ ) : null}
+
{corePrice}
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/NavMenu/NavMenu.tsx b/src/widgets/NavMenu/NavMenu.tsx
new file mode 100644
index 00000000..b60e6ee5
--- /dev/null
+++ b/src/widgets/NavMenu/NavMenu.tsx
@@ -0,0 +1,100 @@
+import { AnimatePresence, motion } from 'framer-motion';
+import { NavLink } from 'react-router-dom';
+
+import { useGetCartByIdQuery } from '../../entities/cart';
+import LogOutBtn from '../../features/LogOutBtn/LogOutBtn.tsx';
+import { useAppSelector } from '../../shared/lib/hooks';
+import CartIcon from '../ui/CartIcon.tsx';
+import ContactsIcon from '../ui/ContactsIcon.tsx';
+import MainIcon from '../ui/MenuIcon';
+
+const spanInitial = {
+ scale: 0,
+};
+
+const spanAnimate = {
+ scale: 1,
+};
+
+const spanTransition = {
+ type: 'spring',
+ stiffness: 900,
+ damping: 35,
+};
+
+const spanExit = {
+ scale: 0,
+};
+
+function NavMenu() {
+ const { cartId } = useAppSelector((state) => state.userReducer);
+ const { data: cart } = useGetCartByIdQuery(cartId);
+
+ const cartItemsCount = cart?.totalLineItemQuantity;
+
+ return (
+
+
+
+ {({ isActive, isPending }) => (
+
+ Our menu
+
+ )}
+
+
+
+
+ {({ isActive, isPending }) => (
+
+
+ {!!cartItemsCount && (
+
+ {cartItemsCount}
+
+ )}
+
+
Cart
+
+ )}
+
+
+
+
+ {({ isActive, isPending }) => (
+
+ Contacts
+
+ )}
+
+
+
+
+ );
+}
+
+export default NavMenu;
diff --git a/src/widgets/ui/CartIcon.tsx b/src/widgets/ui/CartIcon.tsx
new file mode 100644
index 00000000..5dde3cea
--- /dev/null
+++ b/src/widgets/ui/CartIcon.tsx
@@ -0,0 +1,14 @@
+export default function CartIcon({ isActive }: { isActive: boolean }) {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/widgets/ui/ContactsIcon.tsx b/src/widgets/ui/ContactsIcon.tsx
new file mode 100644
index 00000000..3ec9837a
--- /dev/null
+++ b/src/widgets/ui/ContactsIcon.tsx
@@ -0,0 +1,13 @@
+export default function ContactsIcon({ isActive }: { isActive: boolean }) {
+ return (
+
+
+
+ );
+}
diff --git a/src/widgets/ui/DeliveryIcon.tsx b/src/widgets/ui/DeliveryIcon.tsx
new file mode 100644
index 00000000..cac6b0c1
--- /dev/null
+++ b/src/widgets/ui/DeliveryIcon.tsx
@@ -0,0 +1,17 @@
+export default function DeliveryIcon({ isActive }: { isActive: boolean }) {
+ return (
+
+
+
+ );
+}
diff --git a/src/widgets/ui/Logo.tsx b/src/widgets/ui/Logo.tsx
new file mode 100644
index 00000000..3f84156d
--- /dev/null
+++ b/src/widgets/ui/Logo.tsx
@@ -0,0 +1,186 @@
+import { motion } from 'framer-motion';
+
+interface LogoProps {
+ onAnimationEnd: () => void;
+}
+
+export default function Logo({ onAnimationEnd }: LogoProps) {
+ const firstSushi = {
+ initial: { pathLength: 0 },
+ animate: { pathLength: 1 },
+ transition: { duration: 0.4 },
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ onAnimationEnd();
+ }}
+ />
+
+
+ );
+}
diff --git a/src/widgets/ui/MenuIcon.tsx b/src/widgets/ui/MenuIcon.tsx
new file mode 100644
index 00000000..5e7bb628
--- /dev/null
+++ b/src/widgets/ui/MenuIcon.tsx
@@ -0,0 +1,16 @@
+export default function MainIcon({ isActive }: { isActive: boolean }) {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/ui/PaymentIcon.tsx b/src/widgets/ui/PaymentIcon.tsx
new file mode 100644
index 00000000..271355f9
--- /dev/null
+++ b/src/widgets/ui/PaymentIcon.tsx
@@ -0,0 +1,17 @@
+export default function PaymentIcon({ isActive }: { isActive: boolean }) {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 00000000..3c670efc
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,95 @@
+/** @type {import('tailwindcss').Config} */
+
+export default {
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
+ darkMode: 'class',
+ theme: {
+ extend: {
+ keyframes: {
+ wiggle: {
+ "0%, 100%": { transform: "rotate(-3deg)" },
+ "50%": { transform: "rotate(3deg)" }
+ }
+ },
+ animation: {
+ wiggle: "wiggle 200ms ease-in-out"
+ },
+ colors: {
+ accent: 'hsl(126, 55%, 47%)',
+ 'accent-light': 'hsl(0, 0%, 97%)',
+ 'accent-lightest': 'hsl(134, 73%, 90%)',
+ primary: 'hsl(0, 0%, 100%)',
+ secondary: 'hsl(0, 0%, 96%)',
+ 'text-dark': 'hsl(126, 0%, 20%)',
+ 'text-grey': 'hsl(0, 0%, 57%)',
+ 'border-black': 'hsl(0, 0%, 0%)',
+ 'inactive-icons-grey': 'hsl(199, 13%, 66%)',
+ 'shop-cart-red': 'hsl(0, 88%, 65%)',
+ 'separation-line': 'hsl(0, 0%, 95%)',
+ 'rating-star': 'hsl(50, 96%, 63%)',
+ 'rating-star-empty': 'hsl(100, 100%, 100%)',
+ 'modal-overlay': 'hsl(0, 0%, 0%)',
+ 'modal-bg': 'hsl(0, 0%, 100%)',
+ 'dark-bg-primary': 'hsl(240, 2%, 24%)',
+ 'dark-separation-line': 'hsl(252, 3%, 29%)',
+ },
+ fontFamily: {
+ poppins: ['Poppins', 'sans-serif'],
+ },
+ backgroundImage: {
+ aboutMob: "url('assets/img/about_bg_mob.svg')",
+ },
+ screens: {
+ xs: '420px',
+ },
+ transitionTimingFunction: {
+ bounce: 'cubic-bezier(.25,1.55,.65,.97)',
+ },
+ content: {
+ searchIcon: "url('$icons/search.svg')",
+ lockIcon: "url('$icons/lockIcon.svg')",
+ emailIcon: "url('$icons/emailIcon.svg')",
+ emailIconRed: "url('$icons/emailIconRed.svg')",
+ },
+ gridTemplateColumns: {
+ lgGridCols: '174px 1fr 200px',
+ xlGridCols: '350px 1fr 360px',
+ prodPageDesk: '1fr 130px',
+ lgNoCartGrid: '174px 1fr',
+ xlNoCartGrid: '350px 1fr',
+ },
+ gridTemplateRows: {
+ mobGridRows: 'auto 1fr auto',
+ tabGridRows: 'auto 1fr',
+ prodPageMob: 'repeat(3, auto) 1fr',
+ prodPageDesk: 'repeat(2, auto) 1fr',
+ },
+ spacing: {
+ 30: '7.5rem',
+ 78: '20rem',
+ 89: '22rem',
+ 128: '32rem',
+ '2px': '2px',
+ },
+ borderWidth: {
+ 1: '1px',
+ 3: '3px',
+ 6: '6px',
+ 12: '12px',
+ },
+ fontSize: {
+ '2xs': '10px',
+ '3xs': '8px',
+ '5xl': '38px',
+ },
+ },
+ maxHeight: {
+ 0: '0',
+ '1/4': '25%',
+ '1/2': '50%',
+ '3/4': '75%',
+ full: '100%',
+ },
+ },
+ plugins: [],
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..130a1ddd
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src", ".eslintrc.cjs", "vite.config.ts"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 00000000..42872c59
--- /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 00000000..c3b208f4
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,24 @@
+///
+///
+
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import { resolve } from 'path';
+
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: ['./src/test/setupTest.ts'],
+ },
+ resolve: {
+ alias: {
+ $icons: resolve('src/assets/icons'),
+ },
+ },
+ base: './',
+ server: {
+ open: '/',
+ },
+});