Skip to content

Commit

Permalink
added phonebook from previous homework
Browse files Browse the repository at this point in the history
  • Loading branch information
iVladyuser committed Nov 28, 2023
1 parent e27f54e commit 6b3f2d6
Show file tree
Hide file tree
Showing 20 changed files with 1,281 additions and 77 deletions.
754 changes: 686 additions & 68 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
"private": true,
"homepage": "https://iVladyuser.github.io/goit-react-hw-08-phonebook/",
"dependencies": {
"@reduxjs/toolkit": "^1.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.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-loader-spinner": "^5.4.5",
"react-redux": "^8.1.3",
"react-scripts": "5.0.1",
"redux-persist": "^6.0.0",
"styled-components": "^6.1.1",
"web-vitals": "^2.1.3"
},
"scripts": {
Expand Down
37 changes: 30 additions & 7 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
export const App = () => {
import { ContactForm, ContactList, Filter } from 'components';
import { Loader } from 'components/Loader/Loader';
import { Error } from 'components/Error/Error';
import { useSelector } from 'react-redux';
// import {
// selectContactsError,
// selectContactsIsLoading,
// selectContacts,
// } from '../redux/contacts/contactsSelectors';
import { contactsSelectors } from 'redux/contacts';
const App = () => {
const isLoading = useSelector(contactsSelectors.selectContactsIsLoading);
const error = useSelector(contactsSelectors.selectContactsError);
const phoneBook = useSelector(contactsSelectors.selectContacts);
return (
<div
style={{
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: 40,
color: '#010101'
flexDirection: 'column',
gap: '15px',
margin: '0 auto',
padding: '30px',
}}
>
React homework template
<h1 style={{ color: '#3645ab' }}>Phonebook</h1>
<ContactForm />
<h2>Contacts</h2>
{phoneBook.length === 0 && !error && !isLoading ? (
"You don't have any contacts yet"
) : (
<Filter />
)}
{error ? <Error /> : <ContactList />}
{isLoading && <Loader />}
</div>
);
};

export default App;
80 changes: 80 additions & 0 deletions src/components/ContactForm/ContactForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useState } from 'react';
import { nanoid } from 'nanoid';
import { useDispatch, useSelector } from 'react-redux';
import { FormInput, Form, FormButton, FormLabel } from './ContactForm.styled';
// import { addContact } from 'redux/contacts/phoneBookSlice';
// import { selectContacts } from 'redux/contacts/contactsSelectors';
import { contactsSelectors, contactsSlices } from 'redux/contacts';

const ContactForm = () => {
const [name, setName] = useState('');
const [number, setNumber] = useState('');

const dispatch = useDispatch();
const contacts = useSelector(contactsSelectors.selectContacts);

const onSubmitAddContact = e => {
e.preventDefault();
const data = {
name,
number: Number.parseFloat(number) || alert(`Number is not correct`),
};
const newContact = { ...data, id: nanoid() };

const isExist = contacts.some(
({ name }) => name.toLowerCase() === newContact.name.toLowerCase()
);

if (isExist) {
alert(`Oops, contact '${newContact.name}' is already in contacts!`);
return;
}

dispatch(contactsSlices.addContact(newContact));
setName('');
setNumber('');
};

const handleInputChange = e => {
const { name, value } = e.currentTarget;
switch (name) {
case 'name':
setName(value);
break;
case 'number':
setNumber(value);
break;

default:
break;
}
};

return (
<Form onSubmit={onSubmitAddContact}>
<FormLabel htmlFor="name">Name</FormLabel>
<FormInput
type="text"
name="name"
onChange={handleInputChange}
value={name}
pattern="^[a-zA-Zа-яА-Я]+(([' \-][a-zA-Zа-яА-Я ])?[a-zA-Zа-яА-Я]*)*$"
required
/>

<FormLabel htmlFor="number">Number</FormLabel>
<FormInput
type="tel"
name="number"
value={number}
onChange={handleInputChange}
pattern="\+?\d{1,4}?[ .\-\s]?\(?\d{1,3}?\)?[ .\-\s]?\d{1,4}[ .\-\s]?\d{1,4}[ .\-\s]?\d{1,9}"
required
/>

<FormButton type="submit">Add contact</FormButton>
</Form>
);
};

export default ContactForm;
52 changes: 52 additions & 0 deletions src/components/ContactForm/ContactForm.styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import styled from "styled-components";
export const Form = styled.form`
display: flex;
flex-direction: column;
max-width: 420px;
padding: 10px;
border-radius: 2px;
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.5);
margin: 30px 20px;
`;

export const FormLabel = styled.label`
margin-top: 0;
margin-bottom: 12px;
font-weight: 500;
font-size: 18px;
`;

export const FormInput = styled.input`
height: 20px;
font-size: 16px;
width: 400px;
margin-bottom: 6px;
padding: 5px;
border: none;
border-bottom: 1px solid teal;
&:focus,
&:hover,
&:active {
outline: 0;
outline-offset: 0;
border: none;
border-bottom: 1.5px solid teal;
outline: none;
}
`;

export const FormButton = styled.button`
width: 100px;
height: 28px;
margin-left: 5px;
margin-top: 5px;
border-width: inherit;
border-radius: 5px;
outline: none;
background-color: #3645ab;
color: white;
cursor: pointer;
&:focus {
background-color: #2b3788;
}
`;
46 changes: 46 additions & 0 deletions src/components/ContactList/ContactList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect } from 'react';
import {
List,
ContactItem,
CardWrapper,
ButtonDelete,
Info,
} from './ContactList.styled';
import { useDispatch, useSelector } from 'react-redux';
// import {
// deleteContact,
// getContacts,
// } from '../../redux/contacts/phoneBookSlice';
// import { selectFilteredContacts } from 'redux/contacts/contactsSelectors';
import { contactsSelectors, contactsSlices } from 'redux/contacts';

const ContactList = () => {
const visibleContacts = useSelector(contactsSelectors.selectFilteredContacts);
const dispatch = useDispatch();

useEffect(() => {
dispatch(contactsSlices.getContacts());
}, [dispatch]);

const handleDeleteContact = contactId => {
dispatch(contactsSlices.deleteContact(contactId));
};

return (
<List>
{visibleContacts.map(({ name, phone, id }) => (
<ContactItem key={id}>
<CardWrapper>
<Info>{name}</Info>
<Info>{phone}</Info>
<ButtonDelete onClick={() => handleDeleteContact(id)}>
Delete
</ButtonDelete>
</CardWrapper>
</ContactItem>
))}
</List>
);
};

export default ContactList;
51 changes: 51 additions & 0 deletions src/components/ContactList/ContactList.styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styled from 'styled-components';

export const List = styled.ul`
display: flex;
flex-wrap: wrap;
margin: 20px;
flex-direction: column;
gap: 10px;
`;

export const ContactItem = styled.li`
max-width: 400px;
padding: 20px;
background: white;
border-radius: 2px;
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.5);
transition: all 0.3s ease-out;
&:hover {
transform: scale(1.05);
}`;

export const CardWrapper = styled.div`
display: flex;
height: 100px;
flex-direction: row;
align-items: center;
gap: 8px;
justify-content: space-between;
`;

export const Info = styled.p`
font-size: 18px;
font-weight: 500;
letter-spacing: -0.3px;
`;

export const ButtonDelete = styled.button`
width: 70px;
height: 40px;
margin-left: 5px;
border-width: inherit;
border-radius: 5px;
outline: none;
background-color: #e22626;
color: white;
cursor: pointer;
&:focus {
background-color: #c72323;
}
`;
14 changes: 14 additions & 0 deletions src/components/Error/Error.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useSelector } from 'react-redux';
import { WrapperError } from './Error.styled';
// import { selectContactsError } from 'redux/contacts/contactsSelectors';
import { contactsSelectors } from 'redux/contacts';

export const Error = () => {
const error = useSelector(contactsSelectors.selectContactsError);

return (
<WrapperError>
<p>We're sorry, {error}</p>
</WrapperError>
);
};
11 changes: 11 additions & 0 deletions src/components/Error/Error.styled.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components';

export const WrapperError = styled.div`
text-align: center;
margin-top: 30px;
padding: 10px;
width: 400px;
font-weight: 500;
font-size: 20px;
background-color: #fa0e0ef6;
`;
30 changes: 30 additions & 0 deletions src/components/Filter/Filter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { FilterContainer, FilterLabel, FilterInput } from './Filter.styled';
import { useDispatch, useSelector } from 'react-redux';
// import { setFilterTerm } from '../../redux/contacts/filterSlice';
// import { selectContactsFilterTerm } from 'redux/contacts/contactsSelectors';
import { contactsSelectors, filterSlice } from 'redux/contacts';
const Filter = () => {
const dispatch = useDispatch();
const filterTerm = useSelector(contactsSelectors.selectContactsFilterTerm);

const changeFilter = event => {
const { value } = event.currentTarget;
dispatch(filterSlice.setFilterTerm(value));
};
return (
<FilterContainer>
<FilterLabel>
Find contact...
<FilterInput
type="text"
value={filterTerm}
placeholder="Enter name..."
onChange={changeFilter}
/>
</FilterLabel>
</FilterContainer>
);
};

export default Filter;
34 changes: 34 additions & 0 deletions src/components/Filter/Filter.styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from 'styled-components';

export const FilterContainer = styled.div`
display: flex;
flex-direction: column;
max-width: 420px;
margin: 20px;
padding: 10px;
border-radius: 2px;
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.5);
`;

export const FilterLabel = styled.label`
margin: 0px;
font-size: 18px;
`;

export const FilterInput = styled.input`
max-width: 240px;
font-size: 14px;
padding: 5px;
display: block;
border: none;
border-bottom: 1px solid teal;
&:focus,
&:hover,
&:active {
outline: 0;
outline-offset: 0;
border: none;
border-bottom: 1.5px solid teal;
}
`;
Loading

0 comments on commit 6b3f2d6

Please sign in to comment.