diff --git a/package.json b/package.json
index 5bdac3e7..a1e69871 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,11 @@
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
+ "bootstrap": "^4.6.0",
+ "prop-types": "^15.8.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
+ "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.1"
},
@@ -19,8 +22,7 @@
},
"eslintConfig": {
"extends": [
- "react-app",
- "react-app/jest"
+ "wesbos"
]
},
"browserslist": {
@@ -34,5 +36,19 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "babel-eslint": "^10.1.0",
+ "eslint": "^7.8.1",
+ "eslint-config-airbnb": "^18.2.0",
+ "eslint-config-prettier": "^6.11.0",
+ "eslint-config-wesbos": "^1.0.1",
+ "eslint-plugin-html": "^6.1.0",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-jsx-a11y": "^6.3.1",
+ "eslint-plugin-prettier": "^3.1.4",
+ "eslint-plugin-react": "^7.20.6",
+ "eslint-plugin-react-hooks": "^4.1.2",
+ "prettier": "^2.1.1"
}
}
diff --git a/src/App.css b/src/App.css
index 74b5e053..c061a9f4 100644
--- a/src/App.css
+++ b/src/App.css
@@ -2,37 +2,7 @@
text-align: center;
}
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+img {
+ width: 100%;
+ max-width: 200px;
}
diff --git a/src/App.js b/src/App.js
index 37845757..b052feeb 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,25 +1,79 @@
-import logo from './logo.svg';
import './App.css';
+import { Switch, Route, Redirect } from 'react-router-dom';
+import { useState } from 'react';
+import Home from './components/Home';
+import IndividualContact from './components/IndividualContact';
+import NewContact from './components/NewContact';
+import EditContact from './components/EditContact';
+import Header from './components/Header';
-function App() {
- return (
-
-
+const Header = () => (
+
+
Your Contact List
+
+
Your Contact List
+
+);
+
+const App = () => {
+ const [contacts, setContacts] = useState([]);
+
+ const addContact = (newContact) => {
+ setContacts(contacts.concat([newContact]));
+ };
+
+ const editContact = (newContactInfo) => {
+ setContacts((prevState) =>
+ [...prevState].map((c) =>
+ c.id === newContactInfo.id ? newContactInfo : c
+ )
+ );
+ };
+
+ const deleteContact = (id) => {
+ setContacts((prevState) =>
+ [...prevState].filter((c) => c.id !== parseInt(id))
+ );
+ };
+
+
+ return (
+
+
+
+ (
+
+ )}
+ />
+ }
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+
+
);
-}
+};
-export default App;
+export default App;
\ No newline at end of file
diff --git a/src/components/Contact.js b/src/components/Contact.js
new file mode 100644
index 00000000..654eb903
--- /dev/null
+++ b/src/components/Contact.js
@@ -0,0 +1,53 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Link, Redirect } from 'react-router-dom';
+
+const Contact = ({ contact, deleteContact }) => {
+ const [redirect, setRedirect] = useState(false);
+
+ const handleContactClick = () => {
+ setRedirect(true);
+ };
+
+ const handleDeleteButtonClick = (e) => {
+ e.stopPropagation();
+ deleteContact(contact.id);
+ };
+
+ if (redirect) {
+ return
;
+ }
+
+ return (
+
+
+
+ |
+ {contact.name} |
+ {contact.email} |
+ {contact.phone_number} |
+
+ Edit
+
+
+ |
+
+ );
+};
+
+Contact.propTypes = {
+ contact: PropTypes.object,
+ deleteContact: PropTypes.func,
+};
+
+export default Contact;
\ No newline at end of file
diff --git a/src/components/EditContact.js b/src/components/EditContact.js
new file mode 100644
index 00000000..49c53a47
--- /dev/null
+++ b/src/components/EditContact.js
@@ -0,0 +1,114 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Link, Redirect } from 'react-router-dom';
+
+const EditContact = ({ match, contacts, editContact }) => {
+ const { id } = match.params;
+ const [contact] = contacts.filter((c) => c.id === parseInt(id));
+
+ const [nameInput, setNameInput] = useState(contact.name);
+ const [emailInput, setEmailInput] = useState(contact.email);
+ const [phoneNumberInput, setPhoneNumberInput] = useState(
+ contact.phone_number
+ );
+ const [imageURLInput, setImageURLInput] = useState(contact.image_url);
+ const [redirect, setRedirect] = useState(false);
+
+ const handleEditContactClick = () => {
+ if (!nameInput || !imageURLInput || !emailInput || !phoneNumberInput) {
+ alert('No field can be left blank');
+ return;
+ }
+ const updatedContactInfo = {
+ name: nameInput,
+ image_url: imageURLInput,
+ email: emailInput,
+ phone_number: phoneNumberInput,
+ id: parseInt(id),
+ };
+ editContact(updatedContactInfo);
+ setRedirect(true);
+ };
+
+ if (redirect) {
+ return
;
+ }
+
+ return (
+
+
+
Edit Contact
+
+ Go Back
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+EditContact.propTypes = {
+ match: PropTypes.object,
+ contacts: PropTypes.array,
+ editContact: PropTypes.func,
+};
+
+export default EditContact;
\ No newline at end of file
diff --git a/src/components/Header.js b/src/components/Header.js
new file mode 100644
index 00000000..f391e173
--- /dev/null
+++ b/src/components/Header.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const Header = () => (
+
+
+
Your Contact List
+
+
+);
+
+export default Header;
\ No newline at end of file
diff --git a/src/components/Home.js b/src/components/Home.js
new file mode 100644
index 00000000..8667b586
--- /dev/null
+++ b/src/components/Home.js
@@ -0,0 +1,53 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Redirect } from 'react-router-dom';
+import Contact from './Contact';
+
+const Home = ({ contacts, deleteContact }) => {
+ const [redirect, setRedirect] = useState(false);
+
+ const contactList = contacts.map((c) => (
+
+ ));
+
+ const handleAddContactClick = () => {
+ setRedirect(true);
+ };
+
+ if (redirect) {
+ return
;
+ }
+
+ return (
+
+
+
+
+
+
+ | Profile Pic |
+ Name |
+ Email |
+ Phone Number |
+ Options: |
+
+
+ {contactList}
+
+
+
+ );
+};
+
+Home.propTypes = {
+ contacts: PropTypes.array.isRequired,
+ deleteContact: PropTypes.func,
+};
+
+export default Home;
\ No newline at end of file
diff --git a/src/components/IndividualContact.js b/src/components/IndividualContact.js
new file mode 100644
index 00000000..f387a155
--- /dev/null
+++ b/src/components/IndividualContact.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+
+const IndividualContact = ({ match, contacts }) => {
+ const { id } = match.params;
+ const [contact] = contacts.filter((c) => c.id === parseInt(id));
+ return (
+
+
+
+ Go Back
+
+

+
{contact.name}
+
{contact.email}
+
{contact.phone_number}
+
+
+ );
+ };
+
+ IndividualContact.propTypes = {
+ match: PropTypes.object,
+ contacts: PropTypes.array,
+ };
+
+export default IndividualContact;
\ No newline at end of file
diff --git a/src/components/NewContact.js b/src/components/NewContact.js
new file mode 100644
index 00000000..d0b34288
--- /dev/null
+++ b/src/components/NewContact.js
@@ -0,0 +1,109 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Link, Redirect } from 'react-router-dom';
+
+const NewContact = ({ addContact }) => {
+ const [nameInput, setNameInput] = useState('');
+ const [emailInput, setEmailInput] = useState('');
+ const [phoneNumberInput, setPhoneNumberInput] = useState('');
+ const [imageURLInput, setImageURLInput] = useState('');
+ const [redirect, setRedirect] = useState(false);
+
+ const generateId = () => Math.round(Math.random() * 100000000);
+
+ const handleAddContactClick = () => {
+ if (!nameInput || !imageURLInput || !emailInput || !phoneNumberInput) {
+ alert('All Fields are Required');
+ return;
+ }
+ const newContact = {
+ name: nameInput,
+ image_url: imageURLInput,
+ email: emailInput,
+ phone_number: phoneNumberInput,
+ id: generateId(),
+ };
+ addContact(newContact);
+ setRedirect(true);
+ };
+
+ if (redirect) {
+ return
;
+ }
+
+ return (
+
+
+
Add New Contact
+
+ Go Back
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+NewContact.propTypes = {
+ addContact: PropTypes.func.isRequired,
+};
+
+export default NewContact;
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index ef2edf8e..4c4ce5cb 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,17 +1,16 @@
import React from 'react';
import ReactDOM from 'react-dom';
+import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
+import { BrowserRouter } from 'react-router-dom';
import App from './App';
-import reportWebVitals from './reportWebVitals';
ReactDOM.render(
+
+
+
,
document.getElementById('root')
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
+);
\ No newline at end of file
diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js
deleted file mode 100644
index 5253d3ad..00000000
--- a/src/reportWebVitals.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const reportWebVitals = onPerfEntry => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;