From d442e8c7f070e520f91fdc8cf6c61ea06c8e6ed2 Mon Sep 17 00:00:00 2001 From: Kateryna Tkachenko Date: Mon, 26 Feb 2024 22:27:02 +0200 Subject: [PATCH] ready for check --- package-lock.json | 48 ++++++++++-- package.json | 2 + src/components/App.jsx | 16 ---- src/components/App/App.jsx | 64 ++++++++++++++++ src/components/App/App.module.css | 15 ++++ src/components/ContactForm/ContactForm.jsx | 76 +++++++++++++++++++ .../ContactForm/ContactForm.module.css | 29 +++++++ src/components/ContactItem/ContactItem.jsx | 28 +++++++ .../ContactItem/ContactItem.module.css | 22 ++++++ src/components/ContactList/ContactList.jsx | 32 ++++++++ .../ContactList/ContactList.module.css | 4 + src/components/Filter/Filter.jsx | 25 ++++++ src/components/Filter/Filter.module.css | 6 ++ src/index.js | 2 +- 14 files changed, 344 insertions(+), 25 deletions(-) delete mode 100644 src/components/App.jsx create mode 100644 src/components/App/App.jsx create mode 100644 src/components/App/App.module.css create mode 100644 src/components/ContactForm/ContactForm.jsx create mode 100644 src/components/ContactForm/ContactForm.module.css create mode 100644 src/components/ContactItem/ContactItem.jsx create mode 100644 src/components/ContactItem/ContactItem.module.css create mode 100644 src/components/ContactList/ContactList.jsx create mode 100644 src/components/ContactList/ContactList.module.css create mode 100644 src/components/Filter/Filter.jsx create mode 100644 src/components/Filter/Filter.module.css diff --git a/package-lock.json b/package-lock.json index a1d4f24..76c3ed2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "nanoid": "^5.0.6", + "prop-types": "^15.8.1", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1", @@ -9590,14 +9592,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": "5.0.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz", + "integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==", + "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": "^18 || >=20" } }, "node_modules/natural-compare": { @@ -11313,6 +11321,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.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "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", @@ -21286,9 +21311,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": "5.0.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz", + "integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==" }, "natural-compare": { "version": "1.4.0", @@ -21765,6 +21790,13 @@ "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + } } }, "postcss-attribute-case-insensitive": { diff --git a/package.json b/package.json index 3675417..77e2d5e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "nanoid": "^5.0.6", + "prop-types": "^15.8.1", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1", diff --git a/src/components/App.jsx b/src/components/App.jsx deleted file mode 100644 index ce3f3bf..0000000 --- a/src/components/App.jsx +++ /dev/null @@ -1,16 +0,0 @@ -export const App = () => { - return ( -
- React homework template -
- ); -}; diff --git a/src/components/App/App.jsx b/src/components/App/App.jsx new file mode 100644 index 0000000..a9a1a01 --- /dev/null +++ b/src/components/App/App.jsx @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { nanoid } from 'nanoid'; +import ContactForm from '../ContactForm/ContactForm'; +import ContactList from '../ContactList/ContactList'; +import Filter from '../Filter/Filter'; +import css from './App.module.css'; + +export class App extends Component { + state = { + contacts: [ + { id: 'id-1', name: 'Rosie Simpson', number: '459-12-56' }, + { id: 'id-2', name: 'Hermione Kline', number: '443-89-12' }, + { id: 'id-3', name: 'Eden Clements', number: '645-17-79' }, + { id: 'id-4', name: 'Annie Copeland', number: '227-91-26' }, + ], + filter: '', + }; + + handleFilter = e => { + this.setState({ filter: e.target.value }); + }; + + handleSubmit = contact => { + this.setState(prevState => ({ + contacts: [{ id: nanoid(), ...contact }, ...prevState.contacts], + })); + }; + + handleDeleteContact = id => { + this.setState(prevState => ({ + contacts: prevState.contacts.filter(contact => contact.id !== id), + })); + }; + + render() { + const { filter, contacts } = this.state; + + return ( +
+

Phonebook

+ +

Contacts

+ + +
+ ); + } +} + +App.propTypes = { + contacts: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + number: PropTypes.string.isRequired, + }) + ), + filter: PropTypes.string, +}; diff --git a/src/components/App/App.module.css b/src/components/App/App.module.css new file mode 100644 index 0000000..57d47bb --- /dev/null +++ b/src/components/App/App.module.css @@ -0,0 +1,15 @@ +.container { + max-width: 600px; + margin: 0 auto; + padding: 20px; + background-color: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.titleFirst { + color: #333; +} + +.titleFSecond { + color: #555; +} diff --git a/src/components/ContactForm/ContactForm.jsx b/src/components/ContactForm/ContactForm.jsx new file mode 100644 index 0000000..60e12db --- /dev/null +++ b/src/components/ContactForm/ContactForm.jsx @@ -0,0 +1,76 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import css from './ContactForm.module.css'; + +class ContactForm extends Component { + state = { + name: '', + number: '', + }; + + handleChange = e => { + this.setState({ [e.target.name]: e.target.value }); + }; + + handleSubmit = e => { + e.preventDefault(); + const { name, number } = this.state; + const { contacts } = this.props; + + const isDuplicate = contacts.some( + contact => contact.name.toLowerCase() === name.toLowerCase() + ); + + if (isDuplicate) { + alert(`${name} is already in contacts!`); + } else { + this.props.onSubmit({ name, number }); + this.setState({ name: '', number: '' }); + } + }; + + render() { + const { name, number } = this.state; + + return ( +
+ + + + + + + +
+ ); + } +} + +ContactForm.propTypes = { + onSubmit: PropTypes.func.isRequired, + contacts: PropTypes.array.isRequired, +}; + +export default ContactForm; diff --git a/src/components/ContactForm/ContactForm.module.css b/src/components/ContactForm/ContactForm.module.css new file mode 100644 index 0000000..8613385 --- /dev/null +++ b/src/components/ContactForm/ContactForm.module.css @@ -0,0 +1,29 @@ +form { + display: flex; + flex-direction: column; +} + +label { + margin-bottom: 5px; + font-weight: bold; +} + +input { + padding: 8px; + margin-bottom: 15px; + border: 1px solid #ccc; + border-radius: 4px; +} + +button { + padding: 10px; + background-color: #4caf50; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: #45a049; +} diff --git a/src/components/ContactItem/ContactItem.jsx b/src/components/ContactItem/ContactItem.jsx new file mode 100644 index 0000000..0893d44 --- /dev/null +++ b/src/components/ContactItem/ContactItem.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import css from './ContactItem.module.css'; + +const ContactItem = ({ contact, onDeleteContact }) => { + return ( +
  • + {contact.name}: {contact.number} + +
  • + ); +}; + +ContactItem.propTypes = { + contact: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + number: PropTypes.string.isRequired, + }).isRequired, + onDeleteContact: PropTypes.func.isRequired, +}; + +export default ContactItem; diff --git a/src/components/ContactItem/ContactItem.module.css b/src/components/ContactItem/ContactItem.module.css new file mode 100644 index 0000000..a7f3d17 --- /dev/null +++ b/src/components/ContactItem/ContactItem.module.css @@ -0,0 +1,22 @@ +li { + padding: 10px; + border: 1px solid #ccc; + margin-bottom: 10px; + border-radius: 4px; + display: flex; + justify-content: space-between; +} + +button { + padding: 8px; + background-color: #3587e5; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + color: black; + background-color: #ffee05; +} diff --git a/src/components/ContactList/ContactList.jsx b/src/components/ContactList/ContactList.jsx new file mode 100644 index 0000000..607ff7b --- /dev/null +++ b/src/components/ContactList/ContactList.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import ContactItem from 'components/ContactItem/ContactItem'; +import PropTypes from 'prop-types'; +import css from './ContactList.module.css'; + +const ContactList = ({ contacts, filter, onDeleteContact }) => { + const filteredContacts = contacts.filter(contact => + contact.name.toLowerCase().includes(filter.toLowerCase()) + ); + + return ( + + ); +}; + +ContactList.propTypes = { + contacts: PropTypes.array.isRequired, + filter: PropTypes.string.isRequired, + onDeleteContact: PropTypes.func.isRequired, +}; + +export default ContactList; diff --git a/src/components/ContactList/ContactList.module.css b/src/components/ContactList/ContactList.module.css new file mode 100644 index 0000000..96b1c02 --- /dev/null +++ b/src/components/ContactList/ContactList.module.css @@ -0,0 +1,4 @@ +ul { + list-style: none; + padding: 0; +} diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx new file mode 100644 index 0000000..155b17e --- /dev/null +++ b/src/components/Filter/Filter.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import css from './Filter.module.css'; + +const Filter = ({ value, onChange }) => { + return ( +
    + FindContact + +
    + ); +}; + +Filter.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, +}; + +export default Filter; diff --git a/src/components/Filter/Filter.module.css b/src/components/Filter/Filter.module.css new file mode 100644 index 0000000..1faa337 --- /dev/null +++ b/src/components/Filter/Filter.module.css @@ -0,0 +1,6 @@ +input { + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + margin-left: 15px; +} diff --git a/src/index.js b/src/index.js index 2bde91e..079c450 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import { App } from 'components/App'; +import { App } from 'components/App/App'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')).render(