From 186877afd6eec49d37b4db70cf5cc0c0b4169047 Mon Sep 17 00:00:00 2001 From: Anastasia-front Date: Wed, 10 May 2023 12:55:36 +0300 Subject: [PATCH] done --- README.en.md | 105 ------ README.es.md | 105 ------ README.md | 114 +----- README.pl.md | 117 ------ package-lock.json | 337 ++++++++++++++++-- package.json | 7 +- src/components/App.jsx | 44 ++- src/components/ContactForm/ContactForm.jsx | 58 +++ .../ContactForm/ContactForm.styled.js | 34 ++ src/components/ContactForm/index.js | 1 + src/components/ContactList/ContactList.jsx | 38 ++ .../ContactList/ContactList.styled.js | 24 ++ src/components/ContactList/index.js | 1 + src/components/Filter/Filter.jsx | 21 ++ src/components/Filter/Filter.styled.js | 21 ++ src/components/Filter/index.js | 1 + src/components/Section/Section.jsx | 18 + src/components/Section/Section.styled.js | 14 + src/components/Section/index.js | 1 + src/index.js | 6 +- src/redux/contactSlice.js | 48 +++ src/redux/contactsOperations.js | 40 +++ src/redux/filterSlice.js | 16 + src/redux/selectors.js | 7 + src/redux/store.js | 10 + 25 files changed, 715 insertions(+), 473 deletions(-) delete mode 100644 README.en.md delete mode 100644 README.es.md delete mode 100644 README.pl.md create mode 100644 src/components/ContactForm/ContactForm.jsx create mode 100644 src/components/ContactForm/ContactForm.styled.js create mode 100644 src/components/ContactForm/index.js create mode 100644 src/components/ContactList/ContactList.jsx create mode 100644 src/components/ContactList/ContactList.styled.js create mode 100644 src/components/ContactList/index.js create mode 100644 src/components/Filter/Filter.jsx create mode 100644 src/components/Filter/Filter.styled.js create mode 100644 src/components/Filter/index.js create mode 100644 src/components/Section/Section.jsx create mode 100644 src/components/Section/Section.styled.js create mode 100644 src/components/Section/index.js create mode 100644 src/redux/contactSlice.js create mode 100644 src/redux/contactsOperations.js create mode 100644 src/redux/filterSlice.js create mode 100644 src/redux/selectors.js create mode 100644 src/redux/store.js diff --git a/README.en.md b/README.en.md deleted file mode 100644 index f7128cb..0000000 --- a/README.en.md +++ /dev/null @@ -1,105 +0,0 @@ -# React homework template - -This project was created with -[Create React App](https://github.com/facebook/create-react-app). To get -acquainted and configure additional features -[refer to documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -## Creating a repository by template - -Use this GoIT repository as a template for creating a repository -of your project. To use it just tap the `«Use this template»` button and choose -`«Create a new repository»` option, as you can see on the image below. - -![Creating repo from a template step 1](./assets/template-step-1.png) - -The page for creating a new repository will open on the next step. Fill out -the Name field and make sure the repository is public, then click -`«Create repository from template»` button. - -![Creating repo from a template step 2](./assets/template-step-2.png) - -You now have a personal project repository, having a repository-template file -and folder structure. After that, you can work with it as you would with any -other private repository: clone it on your computer, write code, commit, and -send it to GitHub. - -## Preparing for coding - -1. Make sure you have an LTS version of Node.js installed on your computer. - [Download and install](https://nodejs.org/en/) if needed. -2. Install the project's base dependencies with the `npm install` command. -3. Start development mode by running the `npm start` command. -4. Go to [http://localhost:3000](http://localhost:3000) in your browser. This - page will automatically reload after saving changes to the project files. - -## Deploy - -The production version of the project will automatically be linted, built, and -deployed to GitHub Pages, in the `gh-pages` branch, every time the `main` branch -is updated. For example, after a direct push or an accepted pull request. To do -this, you need to edit the `homepage` field in the `package.json` file, -replacing `your_username` and `your_repo_name` with your own, and submit the -changes to GitHub. - -```json -"homepage": "https://your_username.github.io/your_repo_name/" -``` - -Next, you need to go to the settings of the GitHub repository (`Settings` > -`Pages`) and set the distribution of the production version of files from the -`/root` folder of the `gh-pages` branch, if this was not done automatically. - -![GitHub Pages settings](./assets/repo-settings.png) - -### Deployment status - -The deployment status of the latest commit is displayed with an icon next to its -ID. - -- **Yellow color** - the project is being built and deployed. -- **Green color** - deployment completed successfully. -- **Red color** - an error occurred during linting, build or deployment. - -More detailed information about the status can be viewed by clicking on the -icon, and in the drop-down window, follow the link `Details`. - -![Deployment status](./assets/deploy-status.png) - -### Live page - -After some time, usually a couple of minutes, the live page can be viewed at the -address specified in the edited `homepage` property. For example, here is a link -to a live version for this repository -[https://goitacademy.github.io/react-homework-template](https://goitacademy.github.io/react-homework-template). - -If a blank page opens, make sure there are no errors in the `Console` tab -related to incorrect paths to the CSS and JS files of the project (**404**). You -most likely have the wrong value for the `homepage` property in the -`package.json` file. - -### Routing - -If your application uses the `react-router-dom` library for routing, you must -additionally configure the `` component by passing the exact name -of your repository in the `basename` prop. Slashes at the beginning and end of -the line are required. - -```jsx - - - -``` - -## How it works - -![How it works](./assets/how-it-works.png) - -1. After each push to the `main` branch of the GitHub repository, a special - script (GitHub Action) is launched from the `.github/workflows/deploy.yml` - file. -2. All repository files are copied to the server, where the project is - initialized and linted and built before deployment. -3. If all steps are successful, the built production version of the project - files is sent to the `gh-pages` branch. Otherwise, the script execution log - will indicate what the problem is. diff --git a/README.es.md b/README.es.md deleted file mode 100644 index d8783c1..0000000 --- a/README.es.md +++ /dev/null @@ -1,105 +0,0 @@ -# React homework template - -Este proyecto fue creado con la ayuda de -[Create React App](https://github.com/facebook/create-react-app). -[Consulte la documentación](https://facebook.github.io/create-react-app/docs/getting-started) -para familiarizarse con las funciones opcionales y configurarlas. - -## Crear un repositorio desde una plantilla - -Usa este repositorio de la organización GoIT como plantilla para crear el repositorio de tu proyecto. -Para hacer esto, haz clic en `«Use this template»` y selecciona la opción -`«Create a new repository»`, tal como se muestra en la imagen. - -![Creating repo from a template step 1](./assets/template-step-1.png) - -Para el siguiente paso deberás abrir la página para crear un nuevo repositorio. -Ponle nombre, asegúrate de que el repositorio sea público y haz clic en el botón -`«Create repository from template»`. - -![Creating repo from a template step 2](./assets/template-step-2.png) - -Ahora ya tienes un repositorio de proyecto personal, junto a una estructura de -archivos y carpetas del repositorio de plantillas. Luego trabaja con esto, así -como con cualquier otro repositorio personal, realiza una copia en tu computadora -y súbelo a GitHub. - -## Prepararse para el trabajo - -1. Asegúrate de que la versión LTS de Node.js está instalada en tu computador. - [Descárguela e instálela](https://nodejs.org/en/) de ser necesario. -2. Instala las dependencias base del proyecto con el comando `npm install`. -3. Inicia el modo de desarrollo ejecutando el comando `npm start`. -4. En tu navegador, ve a la dirección - [http://localhost:3000](http://localhost:3000). Esta página se recargará - automáticamente después de guardar los cambios en los archivos del proyecto. - -## Implementación - -La versión de producción del proyecto se verificará, compilará y desplegará -automáticamente en GitHub Pages, en la rama `gh-pages`, cada vez que se -actualice la rama `main`. Por ejemplo, después de un Push directo o de una -Pool-request aceptada. Para ello, edita el campo `homepage` del archivo -`package.json`, sustituyendo `your_username` y `your_repo_name` por los tuyos -propios, y envía los cambios a GitHub. - -```json -"homepage": "https://your_username.github.io/your_repo_name/" -``` - -A continuación, ve a la configuración del repositorio de GitHub (`Settings` > -`Pages`) y selecciona distribuir la versión de producción de los archivos desde -la carpeta `/root` de la rama `gh-pages`, si no se ha hecho automáticamente. - -![GitHub Pages settings](./assets/repo-settings.png) - -### Estado de la implantación - -El estado del último commit se indica con un icono junto al ID del commit. - -- **Color amarillo** - el proyecto está compilado e implementado. -- **Color verde** - La implementación se completó con éxito. -- **Color rojo** - Se ha producido un error durante la verificación, la - compilación o la implementación - -Se puede ver información de estado más detallada haciendo clic en el icono y en -la ventana desplegable del enlace `Detalles`. - -![Deployment status](./assets/deploy-status.png) - -### Página activa - -Después de un tiempo, normalmente un par de minutos, la página real se puede ver -en la dirección especificada en la propiedad `homepage`. Por ejemplo, aquí está -el enlace a la versión activa de este repositorio -[https://goitacademy.github.io/react-homework-template](https://goitacademy.github.io/react-homework-template). - -Si se abre una página en blanco, asegúrate de que no haya errores en la pestaña -`Console` relacionados con rutas incorrectas de archivos CSS y JS del proyecto -(**404**). Probablemente tienes un valor incorrecto para la propiedad `homepage` -en el archivo `package.json`. - -### Enrutamiento - -Si la aplicación utiliza la librería `react-router-dom` para el enrutamiento, el -componente `` debe ser configurado adicionalmente pasando en la -prop `basename`, el nombre exacto de tu repositorio. Las barras inclinadas al -principio y al final de la cadena son obligatorias. - -```jsx - - - -``` - -## ¿Cómo funciona? - -![How it works](./assets/how-it-works.png) - -1. Después de cada push a la rama `main` del repositorio GitHub, se ejecuta un - script especial (GitHub Action) del archivo `.github/workflows/deploy.yml`. -2. Todos los archivos del repositorio se copian en el servidor, donde el - proyecto se inicializa, se verifica y se compila antes de ser implementado. -3. Si todos los pasos tienen éxito, la versión de producción compilada de los - archivos del proyecto se envía a la rama `gh-pages`. De lo contrario, el - registro de ejecución del script indicará cuál es el problema. diff --git a/README.md b/README.md index af1f02c..a2d18b8 100644 --- a/README.md +++ b/README.md @@ -1,113 +1 @@ -# React homework template - -Этот проект был создан при помощи -[Create React App](https://github.com/facebook/create-react-app). Для знакомства -и настройки дополнительных возможностей -[обратись к документации](https://facebook.github.io/create-react-app/docs/getting-started). - -## Создание репозитория по шаблону - -Используй этот репозиторий организации GoIT как шаблон для создания репозитория -своего проекта. Для этого нажми на кнопку `«Use this template»` и выбери опцию -`«Create a new repository»`, как показано на изображении. - -![Creating repo from a template step 1](./assets/template-step-1.png) - -На следующем шаге откроется страница создания нового репозитория. Заполни поле -его имени, убедись что репозиторий публичный, после чего нажми кнопку -`«Create repository from template»`. - -![Creating repo from a template step 2](./assets/template-step-2.png) - -После того как репозиторий будет создан, необходимо перейти в настройки -созданного репозитория на вкладку `Settings` > `Actions` > `General` как -показано на изображении. - -![Settings GitHub Actions permissions step 1](./assets/gh-actions-perm-1.png) - -Проскролив страницу до самого конца, в секции `«Workflow permissions»` выбери -опцию `«Read and write permissions»` и поставь галочку в чекбоксе. Это -необходимо для автоматизации процесса деплоя проекта. - -![Settings GitHub Actions permissions step 2](./assets/gh-actions-perm-2.png) - -Теперь у тебя есть личный репозиторий проекта, со структурой файлов и папок -репозитория-шаблона. Далее работай с ним как с любым другим личным репозиторием, -клонируй его себе на компьютер, пиши код, делай коммиты и отправляй их на -GitHub. - -## Подготовка к работе - -1. Убедись что на компьютере установлена LTS-версия Node.js. - [Скачай и установи](https://nodejs.org/en/) её если необходимо. -2. Установи базовые зависимости проекта командой `npm install`. -3. Запусти режим разработки, выполнив команду `npm start`. -4. Перейди в браузере по адресу [http://localhost:3000](http://localhost:3000). - Эта страница будет автоматически перезагружаться после сохранения изменений в - файлах проекта. - -## Деплой - -Продакшн версия проекта будет автоматически проходить линтинг, собираться и -деплоиться на GitHub Pages, в ветку `gh-pages`, каждый раз когда обновляется -ветка `main`. Например, после прямого пуша или принятого пул-реквеста. Для этого -необходимо в файле `package.json` отредактировать поле `homepage`, заменив -`your_username` и `your_repo_name` на свои, и отправить изменения на GitHub. - -```json -"homepage": "https://your_username.github.io/your_repo_name/" -``` - -Далее необходимо зайти в настройки GitHub-репозитория (`Settings` > `Pages`) и -выставить раздачу продакшн версии файлов из папки `/root` ветки `gh-pages`, если -это небыло сделано автоматически. - -![GitHub Pages settings](./assets/repo-settings.png) - -### Статус деплоя - -Статус деплоя крайнего коммита отображается иконкой возле его идентификатора. - -- **Желтый цвет** - выполняется сборка и деплой проекта. -- **Зеленый цвет** - деплой завершился успешно. -- **Красный цвет** - во время линтинга, сборки или деплоя произошла ошибка. - -Более детальную информацию о статусе можно посмотреть кликнув по иконке, и в -выпадающем окне перейти по ссылке `Details`. - -![Deployment status](./assets/deploy-status.png) - -### Живая страница - -Через какое-то время, обычно пару минут, живую страницу можно будет посмотреть -по адресу указанному в отредактированном свойстве `homepage`. Например, вот -ссылка на живую версию для этого репозитория -[https://goitacademy.github.io/react-homework-template](https://goitacademy.github.io/react-homework-template). - -Если открывается пустая страница, убедись что во вкладке `Console` нет ошибок -связанных с неправильными путями к CSS и JS файлам проекта (**404**). Скорее -всего у тебя неправильное значение свойства `homepage` в файле `package.json`. - -### Маршрутизация - -Если приложение использует библиотеку `react-router-dom` для маршрутизации, -необходимо дополнительно настроить компонент ``, передав в пропе -`basename` точное название твоего репозитория. Слеш в начале строки обязателен. - -```jsx - - - -``` - -## Как это работает - -![How it works](./assets/how-it-works.png) - -1. После каждого пуша в ветку `main` GitHub-репозитория, запускается специальный - скрипт (GitHub Action) из файла `.github/workflows/deploy.yml`. -2. Все файлы репозитория копируются на сервер, где проект инициализируется и - проходит линтинг и сборку перед деплоем. -3. Если все шаги прошли успешно, собранная продакшн версия файлов проекта - отправляется в ветку `gh-pages`. В противном случае, в логе выполнения - скрипта будет указано в чем проблема. +# React homework "contact book application with asynchronous operations and user authorization" diff --git a/README.pl.md b/README.pl.md deleted file mode 100644 index be1f226..0000000 --- a/README.pl.md +++ /dev/null @@ -1,117 +0,0 @@ -**Read in other languages: [rosyjski](README.md), [polski](README.pl.md).** - -# React homework template - -Ten projekt został stworzony przy pomocy -[Create React App](https://github.com/facebook/create-react-app). W celu -zapoznania się z konfiguracją dodatkowych opcji -[zobacz dokumentację](https://facebook.github.io/create-react-app/docs/getting-started). - -## Utworzenie repozytorium zgodnie z szablonem - -Wykorzystaj to repozytorium organizacji GoIT jako szablon do utworzenia -repozytorium własnego projektu. W tym celu kliknij na przycisk -`"Use this template"` i wybierz opcję `"Create a new repository"`, jak pokazano -na rysunku. - -![Creating repo from a template step 1](./assets/template-step-1.png) - -W następnym kroku otworzy się strona utworzenia nowego repozytorium. Wypełnij -pole nazwy i upewnij się, że repozytorium jest publiczne, a następnie kliknij na -przycisk `"Create repository from template"`. - -![Creating repo from a template step 2](./assets/template-step-2.png) - -Po utworzeniu repozytorium, należy przejść do jego ustawień w zakładce `Settings` > `Actions` > `General`, jak pokazano na rysunku. - -![Settings GitHub Actions permissions step 1](./assets/gh-actions-perm-1.png) - -Przescrolluj stronę do samego końca, w sekcji `«Workflow permissions»` wybierz opcję `«Read and write permissions»` i zaznacz pole w checkboksie. Jest to niezbędne do automatyzacji procesu deploymentu projektu. - -![Settings GitHub Actions permissions step 2](./assets/gh-actions-perm-2.png) - -Teraz masz własne repozytorium projektu, ze strukturą folderów i plików jak w -szablonie. Pracuj z nim jak z innymi repozytoriami, klonuj je na swój komputer, -pisz kod, rób commity i wysyłaj na GitHub. - -## Przygotowanie do pracy - -1. Upewnij się, że na komputerze zainstalowana jest wersja LTS Node.js. - [Ściągnij i zainstaluj](https://nodejs.org/en/), jeżeli trzeba. -2. Utwórz bazowe zależności projektu przy pomocy polecenia `npm install`. -3. Włącz tryb pracy, wykonując polecenie `npm start`. -4. Przejdź w przeglądarce pod adres - [http://localhost:3000](http://localhost:3000). Ta strona będzie - automatycznie przeładowywać się po zapisaniu zmian w plikach projektu. - -## Deployment - -Produkcyjna wersja projektu będzie automatycznie poddana pracy lintera, budowana -i deployowana na GitHub Pages, w gałęzi `gh-pages` za każdym razem, gdy -aktualizuje się gałąź `main`, na przykład po bezpośrednim pushu lub przyjętym -pull requeście. W tym celu należy w pliku `package.json` zredagować pole -`homepage`, zamieniając `your_username` i `your_repo_name` na swoje nazwy i -wysłać zmiany do GitHub. - -```json -"homepage": "https://your_username.github.io/your_repo_name/" -``` - -Następnie należy przejść do ustawień repozytorium GitHub (`Settings` > `Pages`) -i wydystrybuować wersję produkcyjną plików z folderu `/root` gałęzi `gh-pages`, -jeśli nie zostało to wykonane automatycznie. - -![GitHub Pages settings](./assets/repo-settings.png) - -### Status deploymentu - -Status deploymentu ostatniego commitu wyświetla się jako ikona obok jego -identyfikatora. - -- **Żółty kolor** - wykonuje się zbudowanie i deployment projektu. -- **Zielony kolor** - deploymnt zakończył się sukcesem. -- **Czerwony kolor** - podczas pracy lintera, budowania lub deploymentu wystąpił - błąd. - -Bardziej szczegółowe informacje o statusie można zobaczyć po kliknięciu na -ikonkę i przejściu w wyskakującym oknie do odnośnika `Details`. - -![Deployment status](./assets/deploy-status.png) - -### Deployowana strona - -Po jakimś czasie, zazwyczaj kilku minut, zdeployowaną stronę będzie można -zobaczyć pod adresem wskazanym w zredagowanej właściwości `homepage`. Tutaj na -przykład znajduje się odnośnik do zdeployowanej strony w wersji dla tego -repozytorium -[https://goitacademy.github.io/react-homework-template](https://goitacademy.github.io/react-homework-template). - -Jeżeli otwiera się pusta strona, upewnij się, że w zakładce `Console` nie ma -błędów związanych z nieprawidłowymi ścieżkami do plików CSS i JS projektu -(**404**). Najprawdopodobniej wprowadzona została niewłaściwa wartość -właściwości `homepage` w pliku `package.json`. - -### Trasowanie - -Jeżeli aplikacja wykorzystuje bibliotekę `react-router-dom` dla trasowania, -należy uzupełniająco skonfigurować komponent ``, przekazując w -propsie `basename` dokładną nazwę twojego repozytorium. Slash na początku i na -końcu łańcucha jest obowiązkowy. - -```jsx - - - -``` - -## Jak to działa - -![How it works](./assets/how-it-works.png) - -1. Po każdym pushu do gałęzi `main` repozytorium GitHub, uruchamia się specjalny - skrypt (GitHub Action) z pliku `.github/workflows/deploy.yml`. -2. Wszystkie pliki repozytorium kopiują się na serwer, gdzie projekt zostaje - zainicjowany i przechodzi pracę lintera oraz zbudowanie przed deploymentem. -3. Jeżeli wszystkie kroki zakończyły się sukcesem, zbudowana wersja produkcyjna - plików projektu wysyłana jest do gałęzi `gh-pages`. W przeciwnym razie, w - logu wykonania skryptu zostanie wskazane z czym jest problem. diff --git a/package-lock.json b/package-lock.json index edebee4..991f079 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,16 @@ "name": "react-homework-template", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "axios": "^1.4.0", + "nanoid": "^4.0.2", + "prop-types": "^15.8.1", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-redux": "^8.0.5", "react-scripts": "5.0.1", "web-vitals": "^2.1.3" } @@ -2570,6 +2575,29 @@ "node": ">= 8" } }, + "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/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3110,6 +3138,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3282,6 +3319,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "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/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -4108,6 +4150,29 @@ "node": ">=12" } }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -7039,9 +7104,9 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" }, "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "funding": [ { "type": "individual", @@ -7526,6 +7591,19 @@ "he": "bin/he" } }, + "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/hoist-non-react-statics/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/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -7789,9 +7867,9 @@ } }, "node_modules/immer": { - "version": "9.0.14", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.14.tgz", - "integrity": "sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw==", + "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" @@ -9554,14 +9632,20 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "node_modules/nanoid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", - "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^14 || ^16 || >=18" } }, "node_modules/natural-compare": { @@ -11277,6 +11361,23 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/postcss/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==", + "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/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11389,6 +11490,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -11600,6 +11706,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "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" + }, + "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.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -11727,6 +11876,22 @@ "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/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -11869,6 +12034,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "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.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -13367,6 +13537,14 @@ "punycode": "^2.1.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", @@ -16074,6 +16252,17 @@ } } }, + "@reduxjs/toolkit": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz", + "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==", + "requires": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + } + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -16461,6 +16650,15 @@ "@types/node": "*" } }, + "@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==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -16633,6 +16831,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@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==" + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -17217,6 +17420,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz", "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==" }, + "axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "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==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -19401,9 +19626,9 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" }, "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, "fork-ts-checker-webpack-plugin": { "version": "6.5.2", @@ -19725,6 +19950,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "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==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "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==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -19921,9 +20161,9 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" }, "immer": { - "version": "9.0.14", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.14.tgz", - "integrity": "sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw==" + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" }, "import-fresh": { "version": "3.3.0", @@ -21206,9 +21446,9 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "nanoid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", - "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==" }, "natural-compare": { "version": "1.4.0", @@ -21685,6 +21925,13 @@ "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + } } }, "postcss-attribute-case-insensitive": { @@ -22394,6 +22641,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -22549,6 +22801,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "requires": { + "@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" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -22644,6 +22916,20 @@ "strip-indent": "^3.0.0" } }, + "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==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "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==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -22755,6 +23041,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -23854,6 +24145,12 @@ "punycode": "^2.1.0" } }, + "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==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 18768d5..8ee181a 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,18 @@ "name": "react-homework-template", "version": "0.1.0", "private": true, - "homepage": "https://goitacademy.github.io/react-homework-template/", + "homepage": "https://Anastasia-front.github.io/goit-react-hw-08-phonebook/", "dependencies": { + "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "axios": "^1.4.0", + "nanoid": "^4.0.2", + "prop-types": "^15.8.1", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-redux": "^8.0.5", "react-scripts": "5.0.1", "web-vitals": "^2.1.3" }, diff --git a/src/components/App.jsx b/src/components/App.jsx index ce3f3bf..bcacb09 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -1,16 +1,38 @@ +import Section from './Section'; +import ContactForm from './ContactForm'; +import ContactList from './ContactList'; +import Filter from './Filter'; +import { useDispatch, useSelector } from 'react-redux'; +import { useEffect } from 'react'; +import { fetchContacts } from 'redux/contactsOperations'; +import { selectError, selectIsLoading } from 'redux/selectors'; + export const App = () => { + const dispatch = useDispatch(); + const isLoading = useSelector(selectIsLoading); + const error = useSelector(selectError); + + useEffect(() => { + dispatch(fetchContacts()); + }, [dispatch]); return ( -
- React homework template +
+
+ +
+ +
+ + {isLoading && !error && ( + Request in progress... + )} + {error && ( + + The operation failed with error: ${error} + + )} + {!isLoading && !error && } +
); }; diff --git a/src/components/ContactForm/ContactForm.jsx b/src/components/ContactForm/ContactForm.jsx new file mode 100644 index 0000000..03e4e5e --- /dev/null +++ b/src/components/ContactForm/ContactForm.jsx @@ -0,0 +1,58 @@ +import { Form, Label, Input, Submit } from './ContactForm.styled'; +import { nanoid } from 'nanoid'; +import { useDispatch, useSelector } from 'react-redux'; +import { addContact } from 'redux/contactsOperations'; +import { selectContacts } from 'redux/selectors'; + +export default function ContactForm() { + const dispatch = useDispatch(); + const contacts = useSelector(selectContacts); + + const handleSubmit = event => { + event.preventDefault(); + const form = event.target; + const name = form.elements.name.value; + const number = form.elements.number.value; + + const isDuplicate = contacts.some( + contact => contact.name.toLowerCase() === name.toLowerCase() + ); + if (isDuplicate) { + alert(`${name} is already in contacts`); + } else { + dispatch(addContact({ name, number })); + form.reset(); + } + }; + + const nameInputId = nanoid(); + const numberInputId = nanoid(); + + return ( +
+ + + Add to contact +
+ ); +} diff --git a/src/components/ContactForm/ContactForm.styled.js b/src/components/ContactForm/ContactForm.styled.js new file mode 100644 index 0000000..e07c2bd --- /dev/null +++ b/src/components/ContactForm/ContactForm.styled.js @@ -0,0 +1,34 @@ +import styled from 'styled-components'; + +export const Form = styled.form` + border: 1px solid black; + margin-left: 40px; + padding: 15px; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 15px; + width: 200px; +`; + +export const Label = styled.label` + display: flex; + flex-direction: column; + gap: 10px; + font-size: 16px; +`; +export const Input = styled.input` + background-color: #f5fffb; + border: 1px solid #ababab; + border-radius: 3px; +`; +export const Submit = styled.button` + background-color: #ffecc8; + border: 1px solid grey; + border-radius: 3px; + padding: 3px; + &:hover, + &:active { + background-color: teal; + } +`; diff --git a/src/components/ContactForm/index.js b/src/components/ContactForm/index.js new file mode 100644 index 0000000..4744fea --- /dev/null +++ b/src/components/ContactForm/index.js @@ -0,0 +1 @@ +export { default } from './ContactForm'; diff --git a/src/components/ContactList/ContactList.jsx b/src/components/ContactList/ContactList.jsx new file mode 100644 index 0000000..7cdb7d4 --- /dev/null +++ b/src/components/ContactList/ContactList.jsx @@ -0,0 +1,38 @@ +import { Li, List, Delete } from './ContactList.styled'; +import { useSelector, useDispatch } from 'react-redux'; +import { selectContacts, selectFilter } from 'redux/selectors'; +import { deleteContact } from 'redux/contactsOperations'; + +const getVisibleTasks = (contacts, filter) => { + const normilizedFilter = filter.toLowerCase(); + return contacts.filter(contact => + contact['name'].toLowerCase().includes(normilizedFilter) + ); +}; + +const Items = () => { + const contacts = useSelector(selectContacts); + const filter = useSelector(selectFilter); + const visibleTasks = getVisibleTasks(contacts, filter); + const dispatch = useDispatch(); + return ( + + {visibleTasks.length ? ( + visibleTasks.map(({ id, name, number }) => ( +
  • +

    + {name}: {number} + dispatch(deleteContact(id))}> + Delete + +

    +
  • + )) + ) : ( +

    There are no contacts in your phonebook

    + )} +
    + ); +}; + +export default Items; diff --git a/src/components/ContactList/ContactList.styled.js b/src/components/ContactList/ContactList.styled.js new file mode 100644 index 0000000..b68ba09 --- /dev/null +++ b/src/components/ContactList/ContactList.styled.js @@ -0,0 +1,24 @@ +import styled from 'styled-components'; + +export const List = styled.ul` + margin: 0 0 0 30px; + list-style-type: circle; +`; +export const Li = styled.li` + display: list-item; + font-size: 12px; +`; +export const Delete = styled.button` + background-color: #ffcec8; + border: 1px solid grey; + border-radius: 3px; + margin-left: 7px; + padding: 3px; + font-size: 12px; + height: min-content; + &:hover, + &:active, + &:focus { + background-color: teal; + } +`; diff --git a/src/components/ContactList/index.js b/src/components/ContactList/index.js new file mode 100644 index 0000000..f25e568 --- /dev/null +++ b/src/components/ContactList/index.js @@ -0,0 +1 @@ +export { default } from './ContactList'; diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx new file mode 100644 index 0000000..7fbe60c --- /dev/null +++ b/src/components/Filter/Filter.jsx @@ -0,0 +1,21 @@ +import { FilterTitle, FilterInput, FilterDiv } from './Filter.styled'; +import { useDispatch } from 'react-redux'; +import { setFilter } from 'redux/filterSlice'; + +const Filter = () => { + const dispatch = useDispatch(); + + const handleFilterChange = filter => dispatch(setFilter(filter)); + const changeFilter = e => { + handleFilterChange(e.currentTarget.value); + }; + + return ( + + Find contacts by name + + + ); +}; + +export default Filter; diff --git a/src/components/Filter/Filter.styled.js b/src/components/Filter/Filter.styled.js new file mode 100644 index 0000000..d774089 --- /dev/null +++ b/src/components/Filter/Filter.styled.js @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +export const FilterDiv = styled.div` + display: flex; + justify-content: flex-start; + flex-direction: column; + margin: 0 0 10px 10px; + gap: 10px; +`; + +export const FilterTitle = styled.h3` + font-size: 20px; + margin: 10px 0 0 40px; +`; +export const FilterInput = styled.input` + background-color: #f5fffb; + border: 1px solid #ababab; + border-radius: 3px; + width: 150px; + margin-left: 40px; +`; diff --git a/src/components/Filter/index.js b/src/components/Filter/index.js new file mode 100644 index 0000000..7494969 --- /dev/null +++ b/src/components/Filter/index.js @@ -0,0 +1 @@ +export { default } from './Filter'; diff --git a/src/components/Section/Section.jsx b/src/components/Section/Section.jsx new file mode 100644 index 0000000..2aa2f25 --- /dev/null +++ b/src/components/Section/Section.jsx @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types'; +import { StyleSection, Title } from './Section.styled'; + +const Section = ({ title, children }) => { + return ( + + {title} + {children} + + ); +}; + +export default Section; + +Section.propTypes = { + title: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, +}; diff --git a/src/components/Section/Section.styled.js b/src/components/Section/Section.styled.js new file mode 100644 index 0000000..6a11edf --- /dev/null +++ b/src/components/Section/Section.styled.js @@ -0,0 +1,14 @@ +import styled from 'styled-components'; + +export const StyleSection = styled.section` + display: flex; + justify-content: flex-start; + flex-direction: column; + gap: 10px; + margin-bottom: 20px; +`; +export const Title = styled.h1` + font-size: 25px; + margin-left: 40px; + margin-top: 20px; +`; diff --git a/src/components/Section/index.js b/src/components/Section/index.js new file mode 100644 index 0000000..9bd61c3 --- /dev/null +++ b/src/components/Section/index.js @@ -0,0 +1 @@ +export { default } from './Section'; diff --git a/src/index.js b/src/index.js index 2bde91e..6252c48 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,13 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { App } from 'components/App'; import './index.css'; +import { Provider } from 'react-redux'; +import { store } from './redux/store'; ReactDOM.createRoot(document.getElementById('root')).render( - + + + ); diff --git a/src/redux/contactSlice.js b/src/redux/contactSlice.js new file mode 100644 index 0000000..0115212 --- /dev/null +++ b/src/redux/contactSlice.js @@ -0,0 +1,48 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { fetchContacts, addContact, deleteContact } from './contactsOperations'; + +const handlePending = state => { + state.isLoading = true; +}; + +const handleRejected = (state, action) => { + state.isLoading = false; + state.error = action.payload; +}; + +const contactSlice = createSlice({ + name: 'contacts', + initialState: { + items: [], + isLoading: false, + error: null, + }, + extraReducers: { + [fetchContacts.pending]: handlePending, + [fetchContacts.fulfilled](state, action) { + state.isLoading = false; + state.error = null; + state.items = action.payload; + }, + [fetchContacts.rejected]: handleRejected, + [addContact.pending]: handlePending, + [addContact.fulfilled](state, action) { + state.isLoading = false; + state.error = null; + state.items.push(action.payload); + }, + [addContact.rejected]: handleRejected, + [deleteContact.pending]: handlePending, + [deleteContact.fulfilled](state, action) { + state.isLoading = false; + state.error = null; + const index = state.items.findIndex( + contact => contact.id === action.payload + ); + state.items.splice(index, 1); + }, + [deleteContact.rejected]: handleRejected, + }, +}); + +export const contacts = contactSlice.reducer; diff --git a/src/redux/contactsOperations.js b/src/redux/contactsOperations.js new file mode 100644 index 0000000..34255e7 --- /dev/null +++ b/src/redux/contactsOperations.js @@ -0,0 +1,40 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; + +axios.defaults.baseURL = 'https://645a06ba95624ceb21f550be.mockapi.io/api/v1/'; + +export const fetchContacts = createAsyncThunk( + 'contacts/fetchAll', + async (_, thunkAPI) => { + try { + const response = await axios.get('/contacts'); + return response.data; + } catch (e) { + return thunkAPI.rejectWithValue(e.message); + } + } +); + +export const addContact = createAsyncThunk( + 'contacts/addContact', + async ({ name, number }, thunkAPI) => { + try { + const response = await axios.post('/contacts', { name, number }); + return response.data; + } catch (e) { + return thunkAPI.rejectWithValue(e.message); + } + } +); + +export const deleteContact = createAsyncThunk( + 'contacts/deleteContact', + async (contactId, thunkAPI) => { + try { + const response = await axios.delete(`/contacts/${contactId}`); + return response.data; + } catch (e) { + return thunkAPI.rejectWithValue(e.message); + } + } +); diff --git a/src/redux/filterSlice.js b/src/redux/filterSlice.js new file mode 100644 index 0000000..8a361b0 --- /dev/null +++ b/src/redux/filterSlice.js @@ -0,0 +1,16 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const filtersInitialState = { value: '' }; + +const filterSlice = createSlice({ + name: 'filter', + initialState: filtersInitialState, + reducers: { + setFilter(state, action) { + state.value = action.payload; + }, + }, +}); + +export const { setFilter } = filterSlice.actions; +export const filter = filterSlice.reducer; diff --git a/src/redux/selectors.js b/src/redux/selectors.js new file mode 100644 index 0000000..d4a3c04 --- /dev/null +++ b/src/redux/selectors.js @@ -0,0 +1,7 @@ +export const selectContacts = state => state.contacts.items; + +export const selectFilter = state => state.filter.value; + +export const selectIsLoading = state => state.contacts.isLoading; + +export const selectError = state => state.contacts.error; diff --git a/src/redux/store.js b/src/redux/store.js new file mode 100644 index 0000000..b07d168 --- /dev/null +++ b/src/redux/store.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { contacts } from './contactSlice'; +import { filter } from './filterSlice'; + +export const store = configureStore({ + reducer: { + contacts, + filter, + }, +});