Skip to content

Commit

Permalink
Proyecto 8 finalizado
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorgemomo committed Jul 8, 2023
1 parent 670a598 commit 65f11ef
Show file tree
Hide file tree
Showing 49 changed files with 4,579 additions and 1,437 deletions.
4,518 changes: 3,104 additions & 1,414 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,28 @@
"name": "react-homework-template",
"version": "0.1.0",
"private": true,
"homepage": "https://goitacademy.github.io/react-homework-template/",
"homepage": "https://jorgemomo.github.io/goit-react-hw-08-phonebook/",
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@redux-devtools/extension": "^3.2.5",
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.4.0",
"nanoid": "^4.0.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-epic-spinners": "^0.5.0",
"react-hot-toast": "^2.4.1",
"react-loader-spinner": "^5.3.4",
"react-redux": "^8.1.1",
"react-router-dom": "^6.14.1",
"react-scripts": "5.0.1",
"react-toastify": "^9.1.3",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"web-vitals": "^2.1.3"
},
"scripts": {
Expand Down
16 changes: 0 additions & 16 deletions src/components/App.jsx

This file was deleted.

74 changes: 74 additions & 0 deletions src/components/App/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useDispatch, useSelector } from 'react-redux';
import { useEffect, lazy, Suspense} from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import {PrivateRoute} from 'routes/PrivatRoutes';
import {PublicRoute} from 'routes/PublicRoutes';
import { authOperations, authSelectors } from 'redux/auth';
import {Loader} from 'components/Loader/Loader';
import {AppBar} from 'components/AppBar/AppBar';
import { ToastContainer } from 'react-toastify';

const PageHome = lazy(() => import('pages/PageHome'));
const PageRegistration = lazy(() => import('pages/PageRegistration'));
const PageLogin = lazy(() => import('pages/PageLogin'));
const PageContacts = lazy(() => import('pages/PageContacts'));

export const App = () => {
const dispatch = useDispatch();
const isFetchingCurrentUser = useSelector(authSelectors.getIsFetchingCurrent);

useEffect(() => {
dispatch(authOperations.fetchCurrentUser());
}, [dispatch]);

return (
<>
{!isFetchingCurrentUser && (
<>
<AppBar />
<Suspense fallback={<Loader />}>
<Routes>
<Route
path="/"
exact
element={
<PublicRoute>
<PageHome />
</PublicRoute>
}
/>
<Route
path="register"
element={
<PublicRoute redirectTo="/contacts" restricted>
<PageRegistration />
</PublicRoute>
}
/>
<Route
path="login"
element={
<PublicRoute redirectTo="/contacts" restricted>
< PageLogin />
</PublicRoute>
}
/>
<Route
path="contacts"
element={
<PrivateRoute>
<PageContacts />
</PrivateRoute>
}
/>
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</Suspense>
<ToastContainer autoClose={3700} position="top-center" />
</>
)}
</>
);
};


20 changes: 20 additions & 0 deletions src/components/AppBar/AppBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useSelector } from 'react-redux';
import { authSelectors } from 'redux/auth/';
import { AuthForm } from 'components/AuthForm/AuthForm';
import { Navigation } from 'components/Navigation/Navigation';
import { UserMenu } from 'components/UserMenu/UserMenu';

import { Header, Box } from './AppBar.styled';

export function AppBar() {
const isLoggedIn = useSelector(authSelectors.getIsLoggedIn);

return (
<Header>
<Box>
<Navigation />
{isLoggedIn ? <UserMenu /> : <AuthForm />}
</Box>
</Header>
);
}
20 changes: 20 additions & 0 deletions src/components/AppBar/AppBar.styled.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from '@emotion/styled';

export const Header = styled.section`
min-width: 440px;
background-color: lightblue;
width: 80%;
margin: 12px auto;
padding: 2em;
border-radius: 10px;
box-shadow: 0 0 0 1px rgb(194, 192, 184) inset,
0 5px 0 -3px rgb(194, 192, 184);
`;

export const Box = styled.div`
display: flex;
justify-content: space-between;
max-width: auto
margin: 0 auto;
border-bottom: 3px solid;
`;
15 changes: 15 additions & 0 deletions src/components/AuthForm/AuthForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Link } from './AuthForm.styled';

export function AuthForm() {
return (
<nav>
<Link to="/register" exact>
Sign up
</Link>
<Link to="/login" exact>
Log in
</Link>
</nav>
);
}

19 changes: 19 additions & 0 deletions src/components/AuthForm/AuthForm.styled.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import styled from '@emotion/styled'
import { NavLink } from "react-router-dom";

export const Link = styled(NavLink)`
text-decoration: none;
padding: 12px;
font-weight: 800;
color: var(--primaryTextColor);
&:not(:last-child){
margin-right: 12px;
}
transition: all 0.5s ease;
text-shadow: 2px 2px 4px blue;
&.active {
color: var(--secondaryTextColor);
}
`;
99 changes: 99 additions & 0 deletions src/components/ContactForm/ContactForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState } from 'react';
import { Form, Label, Input, Button } from './ContactForm.styled';
import toast, { Toaster } from 'react-hot-toast';
import { nanoid } from 'nanoid';
import {
useFetchContactsQuery,
useCreateContactMutation,
} from 'redux/contacts/contactsApi';
import { Loader } from 'components/Loader/Loader';

export function ContactForm() {
const [name, setName] = useState('');
const [number, setNumber] = useState('');
const { data: contacts, isLoading } = useFetchContactsQuery();
const [createContact] = useCreateContactMutation();

const handleChange = e => {
const { name, value } = e.currentTarget;

switch (name) {
case 'name':
setName(value);
break;
case 'number':
setNumber(value);
break;
default:
return;
}
};

const addContact = data => {
const contactName = contacts.map(contact => contact.name.toLowerCase());
const isAdding = contactName.includes(data.name.toLowerCase());

if (!isAdding) {
createContact(data);
reset();
toast.success(`😃 Contact, ${name} successfully added`);
} else {
toast.error(`😏${data.name} is already in contacts.`);
}
};

const handleSubmit = e => {
e.preventDefault();

const newContact = {
id: nanoid(),
name,
number,
};

addContact(newContact);
};

const reset = () => {
setName('');
setNumber('');
};

return (
<Form onSubmit={handleSubmit} autoComplete="off">
<Label>
Name
<Input
type="text"
id="name_input"
name="name"
value={name}
onChange={handleChange}
placeholder="Enter your name..."
pattern="^[a-zA-Zа-яА-Я]+(([' -][a-zA-Zа-яА-Я ])?[a-zA-Zа-яА-Я]*)*$"
title="Name may contain only letters, apostrophe, dash and spaces. For example Adrian, Jacob Mercer, Charles de Batz de Castelmore d'Artagnan"
required
/>
</Label>

<Label>
Number
<Input
type="tel"
id="name_input"
name="number"
value={number}
onChange={handleChange}
placeholder="Enter your number..."
pattern="\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}"
title="Phone number must be digits and can contain spaces, dashes, parentheses and can start with +"
required
/>
<Button type="submit">Add contact</Button>
</Label>

<Toaster />
{isLoading && <Loader />}
</Form>
);
}
65 changes: 65 additions & 0 deletions src/components/ContactForm/ContactForm.styled.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import styled from '@emotion/styled';

export const Form = styled.form`
margin-top: 10px;
margin-bottom: 20px;
border-radius: 10px;
padding: 10px;
box-shadow: var(--boxShadow);
`;

export const Label = styled.label`
display: flex;
flex-direction: column;
margin-bottom: 20px;
font-weight: 500;
font-size: 20px;
align-items: center;
`;

export const Input = styled.input`
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 2px;
padding: 10px;
width: 60%;
border: 2px solid blue;
border-radius: 20px;
cursor: pointer;
font-size: 15px;
box-shadow: 17px 17px 84px 18px rgba(21, 15, 15, 0.2) inset;
&::placeholder {
color: #ffffff;
font-style: italic;
}
&:focus {
background-color: #f9f1f1;
/* outline-color: var(--accentColor); */
box-shadow: rgba(50, 50, 93, 0.25) 0px 30px 60px -12px inset,
rgba(0, 0, 0, 0.3) 0px 18px 36px -18px inset;
border: 2px solid rgb(22, 2, 2);
}
&: nth-last-child(2) {
margin-bottom: 35px;
}
`;

export const Button = styled.button`
width: 100px;
height: 100%;
padding: 5px;
border-radius: 20px;
color: #ffffff;
cursor: pointer;
font-size: 15px;
box-shadow: 17px 17px 84px 18px rgba(21, 15, 15, 0.2) inset;
border: 2px solid blue;
background-color: lightblue;
&:hover,
&:focus {
background: rgba(73, 155, 234, 1);
}
`;
20 changes: 20 additions & 0 deletions src/components/ContactItem/ContactItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Item, Button } from './ContactItem.styled'
import { RotatingLines } from 'react-loader-spinner';
import { useDeleteContactMutation } from 'redux/contacts/contactsApi';

function ContactItem({ id, name, number}) {const [deleteContact, { isLoading: isDeleting }] = useDeleteContactMutation();

return (
<Item key={id}>
<p>
{name}: {number} {' '}
</p>
<Button type="button" onClick={() => deleteContact(id)}
disabled={isDeleting}>
{isDeleting && <RotatingLines width="10" />} Delete
</Button>
</Item>
);
};

export default ContactItem;
Loading

0 comments on commit 65f11ef

Please sign in to comment.