diff --git a/package-lock.json b/package-lock.json
index e5346eb..3b37ac3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"react-dom": "^18.1.0",
"react-loader-spinner": "^5.4.5",
"react-redux": "^8.1.3",
+ "react-router-dom": "^6.17.0",
"react-scripts": "5.0.1",
"redux-persist": "^6.0.0",
"web-vitals": "^2.1.3"
@@ -2631,6 +2632,14 @@
}
}
},
+ "node_modules/@remix-run/router": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz",
+ "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -11827,6 +11836,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "6.17.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz",
+ "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==",
+ "dependencies": {
+ "@remix-run/router": "1.10.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.17.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz",
+ "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==",
+ "dependencies": {
+ "@remix-run/router": "1.10.0",
+ "react-router": "6.17.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -16438,6 +16477,11 @@
"reselect": "^4.1.8"
}
},
+ "@remix-run/router": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz",
+ "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw=="
+ },
"@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -23035,6 +23079,23 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
+ "react-router": {
+ "version": "6.17.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz",
+ "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==",
+ "requires": {
+ "@remix-run/router": "1.10.0"
+ }
+ },
+ "react-router-dom": {
+ "version": "6.17.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz",
+ "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==",
+ "requires": {
+ "@remix-run/router": "1.10.0",
+ "react-router": "6.17.0"
+ }
+ },
"react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
diff --git a/package.json b/package.json
index cc834de..63443bf 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"react-dom": "^18.1.0",
"react-loader-spinner": "^5.4.5",
"react-redux": "^8.1.3",
+ "react-router-dom": "^6.17.0",
"react-scripts": "5.0.1",
"redux-persist": "^6.0.0",
"web-vitals": "^2.1.3"
diff --git a/src/components/App.jsx b/src/components/App.jsx
index ac50725..a1c2426 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -1,31 +1,42 @@
-import React, { useEffect } from 'react';
-import { ContactForm } from './ContactForm/ContactForm';
-import { Filter } from './Filter/Filter';
-import { ContactsList } from './ContactsList/ContactList';
-import { useDispatch, useSelector } from 'react-redux';
-import { fetchContacts } from 'redux/operations';
-import { selectError, selectLoading } from 'redux/selectors';
-import { Loading } from './Loading';
-export function App() {
- const isLoading = useSelector(selectLoading);
- const error = useSelector(selectError);
- const dispatch = useDispatch();
-
- useEffect(() => {
- dispatch(fetchContacts());
- }, [dispatch]);
+import { Contacts } from 'pages/Contacts/Contacts';
+import { Layout } from 'pages/Layout/Layout';
+import { Login } from 'pages/Login/Login';
+import { Register } from 'pages/Register/Register';
+import { Suspense } from 'react';
+import { Route, Routes } from 'react-router-dom';
+import { AppBar } from './AppBar/AppBar';
+export function App() {
return (
-
- {isLoading &&
}
- {error && 'something went wrong'}
-
-
Phonebook
-
- Contacts
-
-
-
-
+ <>
+
+
+ } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ >
);
}
diff --git a/src/components/AppBar/AppBar.jsx b/src/components/AppBar/AppBar.jsx
new file mode 100644
index 0000000..79febe8
--- /dev/null
+++ b/src/components/AppBar/AppBar.jsx
@@ -0,0 +1,9 @@
+import { Navigation } from "components/Navigation/Navigation"
+
+export const AppBar = () =>{
+ return(
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Navigation/Navigation.jsx b/src/components/Navigation/Navigation.jsx
new file mode 100644
index 0000000..7ffbf16
--- /dev/null
+++ b/src/components/Navigation/Navigation.jsx
@@ -0,0 +1,26 @@
+import { Link } from 'react-router-dom';
+import css from './Navigation.module.css';
+
+export function Navigation() {
+ return (
+
+ );
+}
diff --git a/src/components/Navigation/Navigation.module.css b/src/components/Navigation/Navigation.module.css
new file mode 100644
index 0000000..75ee3cc
--- /dev/null
+++ b/src/components/Navigation/Navigation.module.css
@@ -0,0 +1,20 @@
+.list{
+ display: flex;
+ gap: 20px;
+ list-style: none;
+ padding: 0;
+}
+.link{
+ font-size: 20px;
+ text-decoration: none;
+ color: pink;
+ transition: color 200ms ease-in-out;
+}
+.link:hover,
+.link:focus{
+ color: rgb(241, 131, 149);
+}
+.nav{
+ display: flex;
+ justify-content: center;
+}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 92e1faa..4cad879 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,12 +5,15 @@ import { Provider } from 'react-redux';
import { persistor, store } from 'redux/store';
import './index.css';
import { PersistGate } from 'redux-persist/integration/react';
+import { BrowserRouter } from 'react-router-dom';
ReactDOM.createRoot(document.getElementById('root')).render(
+
+
diff --git a/src/pages/Contacts/Contacts.jsx b/src/pages/Contacts/Contacts.jsx
new file mode 100644
index 0000000..df6dd81
--- /dev/null
+++ b/src/pages/Contacts/Contacts.jsx
@@ -0,0 +1,5 @@
+export const Contacts = () =>{
+ return(
+
+ )
+}
\ No newline at end of file
diff --git a/src/pages/Contacts/Contacts.module.css b/src/pages/Contacts/Contacts.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/Layout/Layout.jsx b/src/pages/Layout/Layout.jsx
new file mode 100644
index 0000000..e6df3ba
--- /dev/null
+++ b/src/pages/Layout/Layout.jsx
@@ -0,0 +1,35 @@
+import React, { Suspense, useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { fetchContacts } from 'redux/operations';
+import { selectError, selectLoading } from 'redux/selectors';
+import { ContactForm } from 'components/ContactForm/ContactForm';
+import { Filter } from 'components/Filter/Filter';
+import { ContactsList } from 'components/ContactsList/ContactList';
+import { Loading } from 'components/Loading';
+import { Outlet } from 'react-router-dom';
+
+export const Layout = () => {
+ const isLoading = useSelector(selectLoading);
+ const error = useSelector(selectError);
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(fetchContacts());
+ }, [dispatch]);
+ return (
+
+ {isLoading &&
}
+ {error && 'something went wrong'}
+
+
Phonebook
+
+ Contacts
+
+
+
+
Loading page... }>
+
+
+
+ );
+};
diff --git a/src/pages/Layout/Layout.module.css b/src/pages/Layout/Layout.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/Login/Login.jsx b/src/pages/Login/Login.jsx
new file mode 100644
index 0000000..19ff54b
--- /dev/null
+++ b/src/pages/Login/Login.jsx
@@ -0,0 +1,32 @@
+import css from './Login.module.css';
+
+export const Login = () => {
+ return (
+
+ );
+};
diff --git a/src/pages/Login/Login.module.css b/src/pages/Login/Login.module.css
new file mode 100644
index 0000000..2987fae
--- /dev/null
+++ b/src/pages/Login/Login.module.css
@@ -0,0 +1,53 @@
+.form {
+ display: flex;
+ text-align: center;
+ flex-direction: column;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
+ width: 300px;
+ height: 320px;
+ background-color: white;
+ border-radius: 8px;
+}
+.title {
+ color: rgb(53, 189, 235);
+ margin: 44px 0;
+}
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ margin-bottom: 44px;
+}
+.input {
+ border-radius: 3px;
+ border: 1px rgb(130, 209, 235) solid;
+ width: 74%;
+ padding: 5px;
+ transition: border 200ms ease-in-out;
+}
+.input:hover,
+.input:focus {
+ border: 1px rgb(53, 189, 235) solid;
+}
+.button {
+ align-self: center;
+ width: 50%;
+ border-radius: 16px;
+ padding: 6px 2px;
+ border: 1px rgb(53, 189, 235) solid;
+ background-color: rgb(130, 209, 235);
+ color: white;
+ cursor: pointer;
+ transition: background-color 200ms ease-in-out;
+}
+.button:hover,
+.button:focus {
+ background-color: rgb(53, 189, 235);
+}
diff --git a/src/pages/Register/Register.jsx b/src/pages/Register/Register.jsx
new file mode 100644
index 0000000..1b49143
--- /dev/null
+++ b/src/pages/Register/Register.jsx
@@ -0,0 +1,48 @@
+import css from './Register.module.css';
+export const Register = () => {
+ const handleSubmit = e =>{
+ e.preventDefault()
+ const newUser = {
+ name: e.target.elements.name.value,
+ email: e.target.elements.email.value,
+ password: e.target.elements.password.value
+ }
+ console.log(newUser)
+ }
+ return (
+
+ );
+};
diff --git a/src/pages/Register/Register.module.css b/src/pages/Register/Register.module.css
new file mode 100644
index 0000000..07b054e
--- /dev/null
+++ b/src/pages/Register/Register.module.css
@@ -0,0 +1,53 @@
+.form {
+ display: flex;
+ text-align: center;
+ flex-direction: column;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
+ width: 300px;
+ height: 320px;
+ background-color: white;
+ border-radius: 8px;
+}
+.title {
+ color: green;
+ margin: 32px 0;
+}
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ margin-bottom: 32px;
+}
+.input {
+ border-radius: 3px;
+ border: 1px rgb(2, 170, 2) solid;
+ width: 74%;
+ padding: 5px;
+ transition: border 200ms ease-in-out;
+}
+.input:hover,
+.input:focus{
+ border: 1px green solid;
+}
+.button {
+ align-self: center;
+ width: 50%;
+ border-radius: 16px;
+ padding: 6px 2px;
+ border: 1px green solid;
+ background-color: rgb(2, 170, 2);
+ color: white;
+ cursor: pointer;
+ transition: background-color 200ms ease-in-out;
+}
+.button:hover,
+.button:focus {
+ background-color: green;
+}
diff --git a/src/redux/auth/operations.js b/src/redux/auth/operations.js
new file mode 100644
index 0000000..2f71e38
--- /dev/null
+++ b/src/redux/auth/operations.js
@@ -0,0 +1,66 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
+
+axios.defaults.baseURL = 'https://connections-api.herokuapp.com/';
+
+const setAuthHeader = token => {
+ axios.defaults.headers.common.Authorization = `Bearer ${token}`;
+};
+
+const clearAuthHeader = () => {
+ axios.defaults.headers.common.Authorization = '';
+};
+
+export const register = createAsyncThunk(
+ 'auth/register',
+ async (credentials, thunkAPI) => {
+ try {
+ const response = await axios.post('users/signup', credentials);
+ setAuthHeader(response.data.token);
+ return response.data;
+ } catch (e) {
+ thunkAPI.rejectWithValue(e.message);
+ }
+ }
+);
+
+export const logIn = createAsyncThunk(
+ 'auth/login',
+ async (credentials, thunkAPI) => {
+ try {
+ const response = await axios.post('users/login', credentials);
+ setAuthHeader(response.data.token);
+ return response.data;
+ } catch (e) {
+ thunkAPI.rejectWithValue(e.message);
+ }
+ }
+);
+
+export const logOut = createAsyncThunk('auth/logout', async (_, thunkAPI) => {
+ try {
+ await axios.post('users/logout');
+ clearAuthHeader();
+ } catch (e) {
+ thunkAPI.rejectWithValue(e.message);
+ }
+});
+
+export const refreshUser = createAsyncThunk(
+ 'auth/refresh',
+ async (_, thunkAPI) => {
+ const state = thunkAPI.getState();
+ const persistedToken = state.auth.token;
+
+ if (persistedToken === null) {
+ return thunkAPI.rejectWithValue('oops unable');
+ }
+ try {
+ setAuthHeader(persistedToken);
+ const response = await axios.get('users/current');
+ return response.data;
+ } catch (e) {
+ thunkAPI.rejectWithValue(e.message);
+ }
+ }
+);
diff --git a/src/redux/auth/selectors.js b/src/redux/auth/selectors.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/redux/auth/slice.js b/src/redux/auth/slice.js
new file mode 100644
index 0000000..e69de29