Pulled master

This commit is contained in:
Lou 2021-07-20 17:54:19 +02:00
commit 905e52ad3e
22 changed files with 610 additions and 62 deletions

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<title>Registra</title>
<link rel="icon" href="./src/assets/img/star.ico">
</head>
<body>

View file

@ -12,9 +12,15 @@
"axios": "^0.21.1",
"babel-jest": "^27.0.6",
"babel-preset-es2015": "^6.24.1",
"js-cookie": "^2.2.1",
"jwt-decode": "^3.1.2",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-router-dom": "^5.2.0"
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"redux": "^4.1.0",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@babel/core": "^7.1.6",

View file

@ -1,12 +1,47 @@
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from "react-router-dom";
import { useSelector } from "react-redux";
import Navbar from "./components/Navbar/";
import Home from "./pages/Home/";
import SignupPage from "./pages/Signup";
import LoginPage from "./pages/Login/";
import ProfilePage from "./pages/Profile";
const App = () => {
const currentUser = useSelector((state) => state.currentUser);
const UnAuthRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
currentUser ? (
<Redirect to={{ pathname: "/" }} />
) : (
<Component {...props} />
)
}
/>
);
const AuthRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
currentUser ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/signin" }} />
)
}
/>
);
return (
<div className="App">
<Router>
@ -16,12 +51,9 @@ const App = () => {
<Route exact path="/">
<Home />
</Route>
<Route exact path="/login">
<LoginPage />
</Route>
<Route exact path="/Signup">
<SignupPage />
</Route>
<UnAuthRoute path="/login" component={LoginPage} />
<UnAuthRoute path="/signup" component={SignupPage} />
<AuthRoute path="/me" component={ProfilePage} />
</Switch>
</section>
</Router>

View file

@ -1,32 +1,47 @@
import React from "react";
import { Link } from "react-router-dom";
import { useSelector } from "react-redux";
import "./index.scss";
// import Star from "../../assets/img/star-svgrepo-com.svg";
import Star from "../../assets/img/star.ico";
import LogoutButton from "../buttons/LogoutButton";
const Navbar = () => {
const currentUser = useSelector((state) => state.currentUser);
return (
<div className="navbar">
<div className="navbar__LeftSide">
<Link to="/">
{/* <img
<img
src={Star}
className="navbar__Icon"
alt="Temporary icon"
width="25"
height="25"
/> */}
/>
<p className="navbar__BrandName">Registra</p>
</Link>
</div>
<div className="navbar__RightSide">
<Link to="/signup">
<button>S'inscrire</button>
</Link>
<Link to="/login">
<button>Se connecter</button>
</Link>
<button>Profil</button>
<button>Déconnexion</button>
{!currentUser && (
<>
<Link to="/signup">
<button>S'inscrire</button>
</Link>
<Link to="/login">
<button>Se connecter</button>
</Link>
</>
)}
{currentUser && (
<>
<Link to="/me">
<button>Profile</button>
</Link>
<LogoutButton />
</>
)}
</div>
</div>
);

View file

@ -36,8 +36,10 @@
.navbar__RightSide{
display: flex;
justify-content: space-between;
width: 350px;
justify-content: end;
margin-right: 15px;
button {
margin: 0px 10px;
}
}
}

View file

@ -0,0 +1,22 @@
import React from "react";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import Cookies from "js-cookie";
import { removeCurrentUser } from "../../../redux/action/index";
const LogoutButton = () => {
const dispatch = useDispatch();
const history = useHistory();
const handleLogout = (event) => {
event.preventDefault();
Cookies.remove("token");
dispatch(removeCurrentUser());
localStorage.removeItem("persist:root");
history.push("/");
};
return <button onClick={handleLogout}>Déconnexion</button>;
};
export default LogoutButton;

View file

@ -1,14 +1,69 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import jwt_decode from "jwt-decode";
import Cookies from "js-cookie";
import post from "../../../services/request/Post";
import { setCurrentUser } from "../../../redux/action";
import "./index.scss";
const LoginForm = () => {
const [userData, setUserData] = useState(null);
const dispatch = useDispatch();
const history = useHistory();
const handleSubmit = (e) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
setUserData({
...userData,
email: formdata.get("email"),
password: formdata.get("password")
});
};
const handleLogin = async (data) => {
// Écrire une fonction pour vérifier plus précisément que data
// contient bien les données nécessaires
if (data) {
let body = {
user: { email: data.email, password: data.password }
};
const response = await post("/tokens", body);
// Pareil ici, utiliser une fonction qui vérifie la validité des données
if (response != null) {
Cookies.set("token", response.data.token, {
sameSite: "strict",
Secure: true
});
const payload = {
type: "user",
id: jwt_decode(response.data.token).user_id,
email: userData.email,
username: response.data.username
};
dispatch(setCurrentUser(payload));
history.push("/");
}
}
};
useEffect(() => {
handleLogin(userData);
}, [userData]);
return (
<div className="LoginForm">
<form>
<form action="" onSubmit={handleSubmit}>
<label htmlFor="email">Adresse email</label>
<input type="email" id="email" name="email" />
<label htmlFor="password">Mot de passe</label>
<input type="password" id="password" name="password" />
<button type="submit">Valider</button>
</form>
</div>
);

View file

@ -1,10 +1,63 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import post from "../../../services/request/Post";
import "./index.scss";
const SignupForm = () => {
const [userData, setUserData] = useState(null);
const [passwordCheck, setPasswordCheck] = useState(null);
const history = useHistory();
const handleSubmit = (event) => {
event.preventDefault();
const formdata = new FormData(event.currentTarget);
let pwd = formdata.get("password");
let c_pwd = formdata.get("confirm_password");
let username = formdata.get("username");
let email = formdata.get("email");
if (pwd !== c_pwd) {
setPasswordCheck(false);
} else if (pwd === c_pwd) {
setPasswordCheck(true);
}
setUserData({
...userData,
username,
email,
pwd
});
};
const handleSignup = async ({ email, username, pwd }) => {
let body = {
user: {
email: email,
username: username,
password: pwd
}
};
return await post("/users", body);
};
useEffect(async () => {
if (passwordCheck === false) {
console.log("Passwords do not match"); // À afficher autrement
} else if (passwordCheck === true && userData) {
const response = await handleSignup(userData);
if (response.status && response.status === 201) {
history.push({
pathname: "/",
state: { status: "201 : user created" }
});
}
}
}, [passwordCheck, userData]);
return (
<div className="signupForm">
<form>
<form action="" onSubmit={handleSubmit}>
<label htmlFor="username">Nom d'utilisateur</label>
<input type="text" id="username" name="username" />
<label htmlFor="email">Adresse email</label>
@ -13,6 +66,7 @@ const SignupForm = () => {
<input type="password" id="password" name="password" />
<label htmlFor="confirm_password">Confirmation du mot de passe</label>
<input type="password" id="confirm_password" name="confirm_password" />
<button type="submit">Valider</button>
</form>
</div>
);

View file

@ -0,0 +1,91 @@
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import Cookies from "js-cookie";
import update from "../../../services/request/Update";
import { setCurrentUser } from "../../../redux/action";
const UserUpdateForm = () => {
const [userData, setUserData] = useState(null);
const currentUser = useSelector((state) => state.currentUser);
const dispatch = useDispatch();
const history = useHistory();
const handleSubmit = (e) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
if (formdata.get("email") !== "" && formdata.get("username") !== "") {
const email = formdata.get("email");
const username = formdata.get("username");
setUserData({
...userData,
username: username,
email: email
});
} else if (
formdata.get("email") !== "" &&
formdata.get("username") === ""
) {
const email = formdata.get("email");
const username = currentUser.username;
setUserData({
...userData,
username: username,
email: email
});
} else if (
formdata.get("email") === "" &&
formdata.get("username") !== ""
) {
const email = currentUser.email;
const username = formdata.get("username");
setUserData({
...userData,
username: username,
email: email
});
}
};
const handleUpdate = async ({ username, email }) => {
const token = Cookies.get("token");
const body = { user: { username: username, email: email } };
const response = await update(`/users/${currentUser.id}`, body, token);
console.log(response);
if (response.status === 200) {
handleChange(response);
}
};
const handleChange = (res) => {
console.log("handle change : " + res.data.attributes);
const payload = {
type: "user",
id: res.data.data.id,
email: res.data.data.attributes.email,
username: res.data.data.attributes.username
};
console.log("Payload : " + payload);
dispatch(setCurrentUser(payload));
history.push("/me");
};
useEffect(() => {
if (userData !== null) {
handleUpdate(userData);
}
}, [userData]);
return (
<form action="" onSubmit={handleSubmit}>
<label htmlFor="username">Nom d'utilisateur</label>
<input type="text" id="username" name="username" />
<label htmlFor="email">Adresse email</label>
<input type="email" id="email" name="email" />
<button type="submit">Valider</button>
</form>
);
};
export default UserUpdateForm;

View file

@ -1,11 +1,19 @@
import React from "react";
import ReactDOM from "react-dom";
import "./main.scss";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "./redux/store/index";
import App from "./App.jsx";
import "./main.scss";
ReactDOM.render(
<React.StrictMode>
<App />
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</React.StrictMode>,
document.getElementById("root"),
document.getElementById("root")
);

View file

@ -1,9 +1,29 @@
import React from "react";
import React, { useEffect } from "react";
import { useLocation } from "react-router";
import { useSelector } from "react-redux";
import Cookies from "js-cookie";
const Home = () => {
const currentUser = useSelector((state) => state.currentUser);
const location = useLocation();
return (
<div className="test">
<h2>Home</h2>
{currentUser && (
<div>
<p>Utilisateurice connecté·e, bravo !</p>
</div>
)}
{!currentUser && (
<div>
<p>Utilisateurice non connecté·e.</p>
</div>
)}
<p>{location.state && location.state.status}</p>
</div>
);
};

View file

@ -1,19 +1,7 @@
import React, { useEffect } from "react";
import React from "react";
import LoginForm from "../../components/forms/LoginForm/";
import get from "../../services/request/Get";
const LoginPage = () => {
let jwt_token =
"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMSwiZXhwIjoxNjI0NjM2NTg3fQ.G2UH5uH2xsei6Msp_za50pSVeOKaes1peE0ntslnI1o";
useEffect(() => {
console.log("HELLO 2");
let response = get("/users", jwt_token);
console.log("HELLO 3");
console.log(response);
}, []);
return (
<div className="LoginPage">
<h2>Page de connexion</h2>

View file

@ -0,0 +1,66 @@
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import Cookies from "js-cookie";
import del from "../../services/request/Delete";
import { removeCurrentUser } from "../../redux/action/index";
import UserUpdateForm from "../../components/forms/UserUpdateForm";
const ProfilePage = () => {
const [deleteSuccess, setDeleteSuccess] = useState(null);
const [settings, setSettings] = useState(false);
const currentUser = useSelector((state) => state.currentUser);
const dispatch = useDispatch();
const history = useHistory();
const handleDelete = async () => {
let token = Cookies.get("token");
const response = await del(`/users/${currentUser.id}`, token);
if (response.status === 204) {
setDeleteSuccess(true);
} else if (response.status === 403) {
setDeleteSuccess(false);
}
};
const toogleSettings = () => {
if (settings === false) {
setSettings(true);
} else {
setSettings(false);
}
};
useEffect(() => {
console.log(currentUser);
if (deleteSuccess && deleteSuccess === true) {
Cookies.remove("token");
dispatch(removeCurrentUser());
localStorage.removeItem("persist:root");
history.push({
pathname: "/",
state: { status: "204 : user deleted" }
});
}
}, [deleteSuccess]);
return (
<div>
<h1>Hello {currentUser.username}</h1>
<button onClick={handleDelete}>Supprimer le compte</button>
<button onClick={toogleSettings}>Mettre à jour les informations</button>
{settings === true ? (
<div className="truc">
<h2>Modifiez vos informations :</h2>
<UserUpdateForm />
</div>
) : (
<> </>
)}
</div>
);
};
export default ProfilePage;

14
src/redux/action/index.js Normal file
View file

@ -0,0 +1,14 @@
const setCurrentUser = (currentUser) => {
return {
type: "SET_CURRENT_USER",
payload: currentUser
}
}
const removeCurrentUser = () => {
return {
type: "REMOVE_CURRENT_USER"
}
}
export { setCurrentUser, removeCurrentUser }

View file

@ -0,0 +1,14 @@
const initialState = { currentUser: null }
const reducer = ( state = initialState, action ) => {
switch (action.type){
case "SET_CURRENT_USER":
return { currentUser: action.payload };
case "REMOVE_CURRENT_USER":
return initialState;
default:
return { ...state }
}
}
export default reducer;

23
src/redux/store/index.js Normal file
View file

@ -0,0 +1,23 @@
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import reducer from "../reducer/index"
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(
persistedReducer,
composeEnhancer(applyMiddleware(thunk))
);
const persistor = persistStore(store);
export { store, persistor };

View file

@ -0,0 +1,17 @@
import axios from 'axios';
import { BASE_URL } from "./config.js";
const del = async (
endpoint,
jwt_token = null,
header = { "Authorization": jwt_token }) => {
try {
const response = await axios.delete(BASE_URL + endpoint, { headers: header })
return response
} catch (err) {
console.error(`An error occurred while trying to fetch ${endpoint}. ${err}`);
}
}
export default del;

View file

@ -1,7 +1,6 @@
import axios from 'axios';
import { BASE_URL } from "./config.js";
const get = async (
endpoint,
jwt_token = null,
@ -12,11 +11,14 @@ const get = async (
opt["Authorization"] = jwt_token
}
console.log("HELLO 1");
const response = await axios.get(BASE_URL + endpoint, { headers: opt })
console.log(response.data);
return response.data
try {
console.log("HELLO 1");
const response = await axios.get(BASE_URL + endpoint, { headers: opt })
console.log(response.data);
return response.data
} catch (err) {
console.error(`An error occurred while trying to fetch` + endpoint + `:` + err);
}
}
export default get;

View file

@ -0,0 +1,22 @@
import axios from 'axios';
import { BASE_URL } from "./config.js";
const post = async (
endpoint,
body = null,
jwt_token = null,
header = { "Content-Type": "application/json" }) => {
let opt = header;
if (jwt_token){
opt["Authorization"] = jwt_token
}
try {
return await axios.post(BASE_URL + endpoint, body, { headers: opt })
} catch (err) {
console.error(`An error occurred while trying to fetch ${endpoint}. ${err}`);
}
}
export default post;

View file

@ -0,0 +1,22 @@
import axios from 'axios';
import { BASE_URL } from "./config.js";
const update = async (
endpoint,
body = null,
jwt_token = null,
header = { "Content-Type": "application/json" }) => {
let opt = header;
if (jwt_token){
opt["Authorization"] = jwt_token
}
try {
return await axios.patch(BASE_URL + endpoint, body, { headers: opt })
} catch (err) {
console.error(`An error occurred while trying to fetch ${endpoint}. ${err}`);
}
}
export default update;

View file

@ -1,10 +1,4 @@
const BASE_URL = "https://registra-api.herokuapp.com/api/v1"
const BASE_URL = "https://registra.netlib.re/api/v1"
// const BASE_URL = "http://127.0.0.1:4000/api/v1"
const API_ENDPOINTS = {
"users": "/users",
"token": "/tokens",
"activities": "/activities",
"tasks": "/tasks"
}
export { BASE_URL, API_ENDPOINTS }
export { BASE_URL }

View file

@ -188,7 +188,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
@ -227,6 +227,43 @@
"@babel/helper-validator-identifier" "^7.14.5"
to-fast-properties "^2.0.0"
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react-redux@^7.1.16":
version "7.1.16"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react@*":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.1"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
"@vitejs/plugin-react-refresh@^1.3.1":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-refresh/-/plugin-react-refresh-1.3.3.tgz#d5afb3e0463f368a8afadfdd7305fe5c5fe78a6a"
@ -335,6 +372,11 @@ convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
csstype@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
debug@^4.1.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
@ -425,7 +467,7 @@ history@^4.9.0:
tiny-warning "^1.0.0"
value-equal "^1.0.1"
hoist-non-react-statics@^3.1.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -468,6 +510,11 @@ isarray@0.0.1:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -485,6 +532,11 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
jwt-decode@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -556,7 +608,7 @@ postcss@^8.3.4:
nanoid "^3.1.23"
source-map-js "^0.6.2"
prop-types@^15.6.2:
prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -574,11 +626,23 @@ react-dom@^17.0.0:
object-assign "^4.1.1"
scheduler "^0.20.2"
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-redux@^7.2.4:
version "7.2.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
dependencies:
"@babel/runtime" "^7.12.1"
"@types/react-redux" "^7.1.16"
hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.13.1"
react-refresh@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
@ -628,6 +692,23 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
redux-persist@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.0, redux@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
dependencies:
"@babel/runtime" "^7.9.2"
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"