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 (
+
+ {filteredContacts.map(contact => {
+ 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(