From 0b621c9ff3d27591a8d442c9e0261b7ee99aa80f Mon Sep 17 00:00:00 2001
From: sofiialives <127687828+sofiialives@users.noreply.github.com>
Date: Wed, 1 Nov 2023 18:13:25 +0200
Subject: [PATCH] edited routes and loadings
---
package-lock.json | 11 +++
package.json | 1 +
src/components/App.jsx | 35 +++++++--
src/components/ContactForm/ContactForm.jsx | 2 -
src/components/ContactItem/ContactItem.jsx | 31 ++++++++
.../ContactItem/ContactItem.module.css | 20 +++++
src/components/ContactsList/ContactList.jsx | 23 ++----
.../ContactsList/ContactList.module.css | 20 -----
src/components/ContactsList/LoadingDelete.jsx | 13 ++++
src/components/Loading.jsx | 20 ++---
src/components/PrivateRoute.js | 11 +++
src/components/RestrictedRoute .js | 9 +++
src/pages/Contacts/Contacts.jsx | 22 +++---
src/pages/Error404/Error.jsx | 18 +++++
src/pages/Error404/Error.module.css | 10 +++
src/pages/Login/Login.jsx | 61 +++++++--------
src/pages/Register/Register.jsx | 77 +++++++++----------
src/pages/Welcome/Welcome.jsx | 6 ++
src/redux/contacts/contactsSlice.js | 25 +++++-
src/redux/contacts/operations.js | 4 +-
20 files changed, 279 insertions(+), 140 deletions(-)
create mode 100644 src/components/ContactItem/ContactItem.jsx
create mode 100644 src/components/ContactItem/ContactItem.module.css
create mode 100644 src/components/ContactsList/LoadingDelete.jsx
create mode 100644 src/components/PrivateRoute.js
create mode 100644 src/components/RestrictedRoute .js
create mode 100644 src/pages/Error404/Error.jsx
create mode 100644 src/pages/Error404/Error.module.css
diff --git a/package-lock.json b/package-lock.json
index c858ac7..b0caa80 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.0",
"nanoid": "^5.0.1",
+ "notiflix": "^3.2.6",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-loader-spinner": "^5.4.5",
@@ -9784,6 +9785,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/notiflix": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/notiflix/-/notiflix-3.2.6.tgz",
+ "integrity": "sha512-mUoaJ/s9E4r8o1O8XRqcuXEylpgOkfXtI5SEkBBlBK+LK7nq1c6qQaQcF7QzS/S018Ww7uUqSZotOs5cHENvVw=="
+ },
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -21761,6 +21767,11 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
},
+ "notiflix": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/notiflix/-/notiflix-3.2.6.tgz",
+ "integrity": "sha512-mUoaJ/s9E4r8o1O8XRqcuXEylpgOkfXtI5SEkBBlBK+LK7nq1c6qQaQcF7QzS/S018Ww7uUqSZotOs5cHENvVw=="
+ },
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
diff --git a/package.json b/package.json
index 854593a..d73f404 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.0",
"nanoid": "^5.0.1",
+ "notiflix": "^3.2.6",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-loader-spinner": "^5.4.5",
diff --git a/src/components/App.jsx b/src/components/App.jsx
index 04c5be5..2b8fd0f 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -5,13 +5,15 @@ import { AppBar } from './AppBar/AppBar';
import { useDispatch, useSelector } from 'react-redux';
import { refreshUser } from 'redux/auth/operations';
import { selectIsRefreshing } from 'redux/auth/selectors';
-
-
+import { PrivateRoute } from './PrivateRoute';
+import { RestrictedRoute } from './RestrictedRoute ';
import { Loading } from './Loading';
+
const Welcome = lazy(() => import('pages/Welcome/Welcome'));
const Register = lazy(() => import('pages/Register/Register'));
-const Login= lazy(() => import('pages/Login/Login'));
+const Login = lazy(() => import('pages/Login/Login'));
const Contacts = lazy(() => import('pages/Contacts/Contacts'));
+const Error = lazy(() => import('pages/Error404/Error'))
export function App() {
const isRefreshing = useSelector(selectIsRefreshing);
@@ -25,13 +27,32 @@ export function App() {
!isRefreshing && (
<>
- }>
+ }>
} />
} />
- } />
- } />
- } />
+ }
+ />
+ }
+ />
+ } />
+ }
+ />
+ } />
+ }
+ />
+ } />
>
diff --git a/src/components/ContactForm/ContactForm.jsx b/src/components/ContactForm/ContactForm.jsx
index a052e83..0864293 100644
--- a/src/components/ContactForm/ContactForm.jsx
+++ b/src/components/ContactForm/ContactForm.jsx
@@ -78,8 +78,6 @@ export function ContactForm() {
{
+ const [isDelete, setIsDelete] = useState(false);
+ const dispatch = useDispatch();
+
+ const handleDelete = id => {
+ setIsDelete(true);
+ dispatch(deleteContact(id))
+ .unwrap()
+ .then(() => setIsDelete(false));
+ };
+
+ return (
+
+
+ {contact.name}: {contact.number || contact.phone}
+
+
+
+ );
+};
diff --git a/src/components/ContactItem/ContactItem.module.css b/src/components/ContactItem/ContactItem.module.css
new file mode 100644
index 0000000..cd6ae81
--- /dev/null
+++ b/src/components/ContactItem/ContactItem.module.css
@@ -0,0 +1,20 @@
+.buttonFilter {
+ border-radius: 50%;
+ font-size: 18px;
+ color: rgb(240, 27, 27);
+ background-color: rgb(250, 181, 181);
+ border: 1px red solid;
+ width: 40px;
+ height: 40px;
+ box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2);
+ cursor: pointer;
+ transition: background-color 200ms ease-in-out, transform 200ms,
+ box-shadow 200ms;
+ margin-left: 10px;
+ }
+ .buttonFilter:hover,
+ .buttonFilter:focus {
+ transform: scale(1.1);
+ background-color: rgb(246, 150, 150);
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
+ }
\ No newline at end of file
diff --git a/src/components/ContactsList/ContactList.jsx b/src/components/ContactsList/ContactList.jsx
index 40921b9..3be3079 100644
--- a/src/components/ContactsList/ContactList.jsx
+++ b/src/components/ContactsList/ContactList.jsx
@@ -1,14 +1,17 @@
import css from './ContactList.module.css';
-import { deleteContact } from 'redux/contacts/operations';
-import { useDispatch, useSelector } from 'react-redux';
+import { useSelector } from 'react-redux';
import { useMemo } from 'react';
-import { selectItems, selectFilter, selectError } from 'redux/contacts/selectors';
+import {
+ selectItems,
+ selectFilter,
+ selectError,
+} from 'redux/contacts/selectors';
+import { ContactItem } from 'components/ContactItem/ContactItem';
export const ContactsList = () => {
const error = useSelector(selectError);
const contacts = useSelector(selectItems);
const filter = useSelector(selectFilter);
- const dispatch = useDispatch();
const filteredContacts = useMemo(() => {
if (filter === '') return contacts;
@@ -23,17 +26,7 @@ export const ContactsList = () => {
{error && 'something went wrong'}
{filteredContacts.map(contact => (
- -
-
- {contact.name}: {contact.number || contact.phone}
-
-
-
+
))}
diff --git a/src/components/ContactsList/ContactList.module.css b/src/components/ContactsList/ContactList.module.css
index b317b18..f89923d 100644
--- a/src/components/ContactsList/ContactList.module.css
+++ b/src/components/ContactsList/ContactList.module.css
@@ -1,23 +1,3 @@
-.buttonFilter {
- border-radius: 50%;
- font-size: 18px;
- color: rgb(240, 27, 27);
- background-color: rgb(250, 181, 181);
- border: 1px red solid;
- width: 40px;
- height: 40px;
- box-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2);
- cursor: pointer;
- transition: background-color 200ms ease-in-out, transform 200ms,
- box-shadow 200ms;
- margin-left: 10px;
-}
-.buttonFilter:hover,
-.buttonFilter:focus {
- transform: scale(1.1);
- background-color: rgb(246, 150, 150);
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
-}
.list{
display: flex;
flex-direction: column;
diff --git a/src/components/ContactsList/LoadingDelete.jsx b/src/components/ContactsList/LoadingDelete.jsx
new file mode 100644
index 0000000..b64f735
--- /dev/null
+++ b/src/components/ContactsList/LoadingDelete.jsx
@@ -0,0 +1,13 @@
+import { RotatingLines } from "react-loader-spinner";
+
+export const LoadingDelete = () => {
+ return (
+
+ );
+};
diff --git a/src/components/Loading.jsx b/src/components/Loading.jsx
index e86d778..7b5b41f 100644
--- a/src/components/Loading.jsx
+++ b/src/components/Loading.jsx
@@ -1,15 +1,15 @@
-import { Audio } from 'react-loader-spinner';
+import { ColorRing } from 'react-loader-spinner';
export const Loading = () => {
return (
-
+
);
};
diff --git a/src/components/PrivateRoute.js b/src/components/PrivateRoute.js
new file mode 100644
index 0000000..4c72ef2
--- /dev/null
+++ b/src/components/PrivateRoute.js
@@ -0,0 +1,11 @@
+import { Navigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { selectIsLoggedIn, selectIsRefreshing } from 'redux/auth/selectors';
+
+export const PrivateRoute = ({ component: Component, redirectTo = '/' }) => {
+ const isLoggedIn = useSelector(selectIsLoggedIn)
+ const isRefreshing = useSelector(selectIsRefreshing)
+ const shouldRedirect = !isLoggedIn && !isRefreshing;
+
+ return shouldRedirect ? : Component;
+};
\ No newline at end of file
diff --git a/src/components/RestrictedRoute .js b/src/components/RestrictedRoute .js
new file mode 100644
index 0000000..597efad
--- /dev/null
+++ b/src/components/RestrictedRoute .js
@@ -0,0 +1,9 @@
+import { Navigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { selectIsLoggedIn } from 'redux/auth/selectors';
+
+export const RestrictedRoute = ({ component: Component, redirectTo = '/' }) => {
+ const isLoggedIn = useSelector(selectIsLoggedIn);
+
+ return isLoggedIn ? : Component;
+};
diff --git a/src/pages/Contacts/Contacts.jsx b/src/pages/Contacts/Contacts.jsx
index 376be77..78c505d 100644
--- a/src/pages/Contacts/Contacts.jsx
+++ b/src/pages/Contacts/Contacts.jsx
@@ -3,12 +3,13 @@ import { ContactsList } from 'components/ContactsList/ContactList';
import { Filter } from 'components/Filter/Filter';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { Navigate } from 'react-router-dom';
import { selectIsLoggedIn } from 'redux/auth/selectors';
import { fetchContacts } from 'redux/contacts/operations';
import css from './Contacts.module.css';
+import { selectError } from 'redux/contacts/selectors';
const Contacts = () => {
+ const error = useSelector(selectError);
const isLoggedIn = useSelector(selectIsLoggedIn);
const dispatch = useDispatch();
@@ -20,17 +21,14 @@ const Contacts = () => {
return (
<>
- {isLoggedIn ? (
-
-
Phonebook
-
- Contacts
-
-
-
- ) : (
-
- )}
+ {error && 'something went wrong'}
+
+
Phonebook
+
+ Contacts
+
+
+
>
);
};
diff --git a/src/pages/Error404/Error.jsx b/src/pages/Error404/Error.jsx
new file mode 100644
index 0000000..13ea0cb
--- /dev/null
+++ b/src/pages/Error404/Error.jsx
@@ -0,0 +1,18 @@
+import { Loading } from 'components/Loading';
+import css from './Error.module.css';
+import { useSelector } from 'react-redux';
+import { selectError, selectLoading } from 'redux/contacts/selectors';
+
+const Error = () => {
+ const isLoading = useSelector(selectLoading);
+ const error = useSelector(selectError);
+ return (
+
+ {isLoading && }
+ {error && 'something went wrong'}
+
THE PAGE NOT FOUND 404
+
+ );
+};
+
+export default Error;
diff --git a/src/pages/Error404/Error.module.css b/src/pages/Error404/Error.module.css
new file mode 100644
index 0000000..1059bfc
--- /dev/null
+++ b/src/pages/Error404/Error.module.css
@@ -0,0 +1,10 @@
+.div {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ .title {
+ font-size: 40px;
+ color: red;
+ }
\ No newline at end of file
diff --git a/src/pages/Login/Login.jsx b/src/pages/Login/Login.jsx
index 0a66270..9ebc806 100644
--- a/src/pages/Login/Login.jsx
+++ b/src/pages/Login/Login.jsx
@@ -1,12 +1,13 @@
import { useDispatch, useSelector } from 'react-redux';
import css from './Login.module.css';
import { logIn } from 'redux/auth/operations';
-import { selectIsLoggedIn } from 'redux/auth/selectors';
-import { Navigate } from 'react-router-dom';
+import { selectError, selectLoading } from 'redux/contacts/selectors';
+import { Loading } from 'components/Loading';
const Login = () => {
+ const isLoading = useSelector(selectLoading);
+ const error = useSelector(selectError);
const dispatch = useDispatch();
- const isLoggedIn = useSelector(selectIsLoggedIn);
const handleSubmit = e => {
e.preventDefault();
@@ -22,34 +23,34 @@ const Login = () => {
return (
<>
- {!isLoggedIn ? (
-
- ) : ()}
+ {isLoading && }
+ {error && 'something went wrong'}
+
>
);
};
-export default Login;
\ No newline at end of file
+export default Login;
diff --git a/src/pages/Register/Register.jsx b/src/pages/Register/Register.jsx
index 3e953bd..57cf7c3 100644
--- a/src/pages/Register/Register.jsx
+++ b/src/pages/Register/Register.jsx
@@ -1,12 +1,13 @@
import { useDispatch, useSelector } from 'react-redux';
import css from './Register.module.css';
import { register } from 'redux/auth/operations';
-import { selectIsLoggedIn } from 'redux/auth/selectors';
-import { Navigate } from 'react-router-dom';
+import { selectError, selectLoading } from 'redux/contacts/selectors';
+import { Loading } from 'components/Loading';
const Register = () => {
+ const isLoading = useSelector(selectLoading);
+ const error = useSelector(selectError);
const dispatch = useDispatch();
- const isLoggedIn = useSelector(selectIsLoggedIn);
const handleSubmit = e => {
e.preventDefault();
dispatch(
@@ -23,42 +24,40 @@ const Register = () => {
return (
<>
- {!isLoggedIn ? (
-
- ) : (
-
- )}
+ {isLoading && }
+ {error && 'something went wrong'}
+
>
);
};
diff --git a/src/pages/Welcome/Welcome.jsx b/src/pages/Welcome/Welcome.jsx
index ed1d336..87d9035 100644
--- a/src/pages/Welcome/Welcome.jsx
+++ b/src/pages/Welcome/Welcome.jsx
@@ -1,11 +1,17 @@
import { useSelector } from 'react-redux';
import css from './Welcome.module.css';
import { selectIsLoggedIn } from 'redux/auth/selectors';
+import { Loading } from 'components/Loading';
+import { selectError, selectLoading } from 'redux/contacts/selectors';
const Welcome = () => {
+ const isLoading = useSelector(selectLoading);
+ const error = useSelector(selectError);
const isLoggedIn = useSelector(selectIsLoggedIn);
return (
+ {isLoading && }
+ {error && 'something went wrong'}
{isLoggedIn
? `Enjoy using your own phonebook \u2191`
diff --git a/src/redux/contacts/contactsSlice.js b/src/redux/contacts/contactsSlice.js
index ff0ff50..0a352bb 100644
--- a/src/redux/contacts/contactsSlice.js
+++ b/src/redux/contacts/contactsSlice.js
@@ -1,5 +1,10 @@
import { createSlice } from '@reduxjs/toolkit';
-import { addContact, deleteContact, fetchContacts } from './operations';
+import {
+ addContact,
+ deleteContact,
+ fetchContacts,
+ updateContact,
+} from './operations';
const handlePending = state => {
state.isLoading = true;
@@ -50,10 +55,24 @@ const contactsSlice = createSlice({
.addCase(deleteContact.fulfilled, (state, action) => {
state.isLoading = false;
state.error = null;
- const index = state.items.findIndex(item => item.id === action.payload.id);
+ const index = state.items.findIndex(
+ item => item.id === action.payload.id
+ );
state.items.splice(index, 1);
})
- .addCase(deleteContact.rejected, handleRejected),
+ .addCase(deleteContact.rejected, handleRejected)
+ .addCase(updateContact.fulfilled, (state, action) => {
+ const updatedContacts = action.payload;
+ const indexContact = state.items.findIndex(
+ item => item.id === updatedContacts.id
+ );
+
+ if (indexContact !== -1) {
+ state.items[indexContact] = updatedContacts;
+ }
+ state.isLoading = false;
+ state.error = null;
+ }),
});
export const { setFilter, setContacts } = contactsSlice.actions;
diff --git a/src/redux/contacts/operations.js b/src/redux/contacts/operations.js
index 297efde..f52545d 100644
--- a/src/redux/contacts/operations.js
+++ b/src/redux/contacts/operations.js
@@ -41,9 +41,9 @@ export const deleteContact = createAsyncThunk(
export const updateContact = createAsyncThunk(
'contacts/updateContacts',
- async (contactId, thunkAPI) => {
+ async (contactId, thunkAPI, ...data) => {
try {
- const response = await axios.patch(`/contacts/${contactId}`);
+ const response = await axios.patch(`/contacts/${contactId}, ${data}`);
return response.data;
} catch (e) {
thunkAPI.rejectWithValue(e.message);