diff --git a/package-lock.json b/package-lock.json index 6810ed6..496cdc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { - "name": "goit-react-hw-06-phonebook", - "version": "0.1.0", + "name": "goit-react-hw-07-phonebook", + "version": "0.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "goit-react-hw-06-phonebook", - "version": "0.1.0", + "name": "goit-react-hw-07-phonebook", + "version": "0.2.0", "dependencies": { "@nextui-org/react": "^2.2.9", "@reduxjs/toolkit": "^2.2.0", "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.7", "framer-motion": "^11.0.5", "lucide-react": "^0.331.0", "nanoid": "^5.0.4", @@ -20,7 +21,6 @@ "react-dom": "^18.1.0", "react-redux": "^9.1.0", "react-scripts": "5.0.1", - "redux-persist": "^6.0.0", "web-vitals": "^2.1.3" }, "devDependencies": { @@ -6671,6 +6671,29 @@ "node": ">=12" } }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "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", @@ -9638,9 +9661,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.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -14262,6 +14285,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", @@ -14716,14 +14744,6 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, - "node_modules/redux-persist": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", - "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", - "peerDependencies": { - "redux": ">4.0.0" - } - }, "node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", @@ -22468,6 +22488,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz", "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==" }, + "axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "requires": { + "follow-redirects": "^1.15.4", + "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", @@ -24685,9 +24727,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.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, "foreground-child": { "version": "3.1.1", @@ -27853,6 +27895,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", @@ -28166,12 +28213,6 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, - "redux-persist": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", - "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", - "requires": {} - }, "redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", diff --git a/package.json b/package.json index c9aea08..69ed462 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.7", "framer-motion": "^11.0.5", "lucide-react": "^0.331.0", "nanoid": "^5.0.4", @@ -16,7 +17,6 @@ "react-dom": "^18.1.0", "react-redux": "^9.1.0", "react-scripts": "5.0.1", - "redux-persist": "^6.0.0", "web-vitals": "^2.1.3" }, "scripts": { diff --git a/src/components/ContactForm.jsx b/src/components/ContactForm.jsx index f99c8d8..6298abb 100644 --- a/src/components/ContactForm.jsx +++ b/src/components/ContactForm.jsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { addContact } from '../redux/reducers/contactsSlice'; -import { nanoid } from 'nanoid'; +import { addContact } from '../redux/operations/operations'; import { selectContacts } from '../redux/selectors/contactsSelectors'; import ModalAlert from './ModalAlert'; @@ -16,6 +15,7 @@ import { Plus } from 'lucide-react'; */ const ContactForm = () => { const dispatch = useDispatch(); + const contacts = useSelector(selectContacts); const [name, setName] = useState(''); const [phone, setPhone] = useState(''); @@ -31,12 +31,11 @@ const ContactForm = () => { const handleSubmit = event => { event.preventDefault(); - const existingContact = contacts.find( + const existingContact = contacts.items.find( contact => contact.name === name || contact.phone === phone ); if (!existingContact) { - dispatch(addContact({ id: nanoid(), name, phone })); setName(''); setPhone(''); } else { @@ -52,6 +51,8 @@ const ContactForm = () => { setIsModalOpen(true); } } + + dispatch(addContact({ name, phone })); }; return ( diff --git a/src/components/ContactList.jsx b/src/components/ContactList.jsx index 4632e6e..dddd93d 100644 --- a/src/components/ContactList.jsx +++ b/src/components/ContactList.jsx @@ -1,5 +1,6 @@ +import { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { deleteContact } from '../redux/reducers/contactsSlice'; +import { deleteContact, fetchContacts } from '../redux/operations/operations'; import { selectContacts, selectFilter, @@ -12,6 +13,7 @@ import { TableBody, TableRow, TableCell, + Spinner, } from '@nextui-org/react'; import { Button } from '@nextui-org/react'; import { Trash2 } from 'lucide-react'; @@ -21,16 +23,17 @@ import { Trash2 } from 'lucide-react'; * @returns {JSX.Element} The JSX element representing the contact list. */ const ContactList = () => { - const contacts = useSelector(selectContacts); const filter = useSelector(selectFilter); const dispatch = useDispatch(); - /** - * Filters contacts based on the filter input. - * @type {Array} - */ - const filteredContacts = contacts.filter(contact => - contact.name.toLowerCase().includes(filter.toLowerCase()) + const { items, isLoading, error } = useSelector(selectContacts); + + useEffect(() => { + dispatch(fetchContacts()); + }, [dispatch]); + + const filteredContacts = items.filter(i => + i.name.toLowerCase().includes(filter?.toLowerCase()) ); /** @@ -38,7 +41,7 @@ const ContactList = () => { * @type {Array} */ const sortedContacts = filteredContacts - .slice() + ?.slice() .sort((a, b) => a.name.localeCompare(b.name)); /** @@ -46,9 +49,7 @@ const ContactList = () => { * @param {string} id - The id of the contact to be deleted. * @returns {void} */ - const handleDelete = id => { - dispatch(deleteContact(id)); - }; + const handleDelete = id => dispatch(deleteContact(id)); return (