diff --git a/package.json b/package.json
index dc1a3ab..62b9228 100644
--- a/package.json
+++ b/package.json
@@ -3,8 +3,10 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "axios": "^0.21.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
+ "react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"web-vitals": "^2.1.0"
},
@@ -16,6 +18,7 @@
"@types/node": "^16.9.1",
"@types/react": "^17.0.20",
"@types/react-dom": "^17.0.9",
+ "@types/react-router-dom": "^5.1.8",
"typescript": "^4.4.3"
},
"scripts": {
diff --git a/src/components/Users/User.tsx b/src/components/Users/User.tsx
new file mode 100644
index 0000000..eaf15e2
--- /dev/null
+++ b/src/components/Users/User.tsx
@@ -0,0 +1,39 @@
+import { useCallback, useContext, useMemo } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+
+import { UsersContext } from 'context/Users';
+
+import type { VFC } from 'react';
+
+export const User: VFC<{}> = () => {
+ const history = useHistory();
+ const { users, error, loading, dispatch } = useContext(UsersContext);
+ const { userId } = useParams<{ userId?: string }>();
+
+ /** Finds the user for the current page; undefined if a user enters an invalid user id. */
+ const user = useMemo(() => users.find(u => u.id === Number(userId)), [users, userId]);
+
+ const deleteUser = useCallback(() => {
+ if (!user) return;
+
+ dispatch({ type: 'DELETE', payload: user });
+ history.push('/', { deletedUser: user }); // Back to the users list with the user we just deleted.
+ }, [history, dispatch, user]);
+
+ if (loading) return
Loading…
;
+ if (!user) return User {userId} not found.
+
+ return (
+
+ {error &&
Error: {error}
}
+
+
+ - id: {user.id}
+ - name: {user.name}
+ - email: {user.email}
+
+
+
+
+ );
+}
diff --git a/src/components/Users/Users.tsx b/src/components/Users/Users.tsx
new file mode 100644
index 0000000..6150fcc
--- /dev/null
+++ b/src/components/Users/Users.tsx
@@ -0,0 +1,51 @@
+import { useCallback, useContext, useMemo } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+
+import { UsersContext } from 'context/Users';
+import type { User } from 'context/Users';
+
+import type { VFC } from 'react';
+
+export const Users: VFC<{}> = () => {
+ const location = useLocation<{ deletedUser: User }>();
+ const { users, loading, error, dispatch } = useContext(UsersContext);
+
+ const showRestore = useMemo(() => {
+ if (!location.state?.deletedUser) return false;
+ // If the user still exists in the array (eg. we just restored it, don't show it)
+ // There's a far more performant way of doing this (during the restore action), but easy workaround with a very small array of users.
+ if (users.some(user => user.id === location.state.deletedUser.id)) return false;
+
+ return true;
+ }, [users, location.state?.deletedUser]);
+
+ const restoreUser = useCallback(() => {
+ if (!showRestore) return;
+
+ dispatch({
+ type: 'ADD',
+ payload: location.state.deletedUser,
+ });
+ }, [showRestore, dispatch, location.state?.deletedUser])
+
+ if (loading) return Loading…
;
+
+ return
+ {showRestore && (
+ <>
+
Deleted user {location.state.deletedUser.name} ({location.state.deletedUser.id})!
+
+ >
+ )}
+
+ {error &&
Error: {error}
}
+
+
+ {users.map(user => (
+ -
+ {user.id}: {user.name}
+
+ ))}
+
+
;
+}
diff --git a/src/components/Users/index.ts b/src/components/Users/index.ts
new file mode 100644
index 0000000..70dedbb
--- /dev/null
+++ b/src/components/Users/index.ts
@@ -0,0 +1,2 @@
+export * from './User';
+export * from './Users';
diff --git a/src/containers/App/App.css b/src/containers/App/App.css
deleted file mode 100644
index 74b5e05..0000000
--- a/src/containers/App/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/src/containers/App/App.test.tsx b/src/containers/App/App.test.tsx
deleted file mode 100644
index 6418624..0000000
--- a/src/containers/App/App.test.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import { App } from './App';
-
-test('renders learn react link', () => {
- render();
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/src/containers/App/App.tsx b/src/containers/App/App.tsx
deleted file mode 100644
index 55d22e5..0000000
--- a/src/containers/App/App.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import logo from './logo.svg';
-import './App.css';
-
-import type { VFC } from 'react';
-
-export const App: VFC<{}> = () => {
- return (
-
- );
-}
diff --git a/src/containers/App/index.ts b/src/containers/App/index.ts
deleted file mode 100644
index ac7ba3b..0000000
--- a/src/containers/App/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './App';
diff --git a/src/containers/App/logo.svg b/src/containers/App/logo.svg
deleted file mode 100644
index 9dfc1c0..0000000
--- a/src/containers/App/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/containers/Users/Router.tsx b/src/containers/Users/Router.tsx
new file mode 100644
index 0000000..cf63c70
--- /dev/null
+++ b/src/containers/Users/Router.tsx
@@ -0,0 +1,42 @@
+import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
+
+import { Users, User } from 'components/Users';
+
+import { UsersProvider, UsersContext } from 'context/Users';
+
+import type { ReactNode, VFC } from 'react';
+import type { Context } from 'context/Users';
+
+export const UsersRouter: VFC<{ children?: ReactNode }> = ({ children }) => (
+
+
+
+
+ {children}
+
+
+ {({ users, loading, error, refresh }: Context) => (
+ <>
+ Users: {users.length}
+
+
+
+ {loading && Loading…
}
+ {error && error: {error}
}
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+)
diff --git a/src/containers/Users/index.ts b/src/containers/Users/index.ts
new file mode 100644
index 0000000..44f7dc8
--- /dev/null
+++ b/src/containers/Users/index.ts
@@ -0,0 +1 @@
+export * from './Router';
diff --git a/src/context/Users/Provider.tsx b/src/context/Users/Provider.tsx
new file mode 100644
index 0000000..cda9aaf
--- /dev/null
+++ b/src/context/Users/Provider.tsx
@@ -0,0 +1,45 @@
+import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
+import axios from 'axios';
+
+import { UsersContext, defaultUsersContext } from './context';
+import { usersReducer } from './reducer';
+
+import type { ReactNode, VFC } from 'react';
+
+export const UsersProvider: VFC<{ children: ReactNode }> = ({ children }) => {
+ const [error, setError] = useState(defaultUsersContext.error);
+ const [loading, setLoading] = useState(defaultUsersContext.loading);
+ const [users, dispatch] = useReducer(usersReducer, defaultUsersContext.users);
+
+ const initializeUsers = useCallback(async () => {
+ try {
+ const { data } = await axios('https://jsonplaceholder.typicode.com/users')
+
+ dispatch({ type: 'INIT', payload: data });
+ setError(undefined);
+ } catch (e: any) {
+ setError(e.message);
+ } finally {
+ setLoading(false);
+ }
+ }, [dispatch, setLoading, setError]);
+
+ /** Load all users initially. */
+ useEffect(() => {
+ initializeUsers();
+ }, [initializeUsers]);
+
+ const contextValue = useMemo(() => ({
+ users,
+ error,
+ dispatch,
+ loading,
+ refresh: initializeUsers,
+ }), [users, error, dispatch, loading, initializeUsers])
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/context/Users/context.ts b/src/context/Users/context.ts
new file mode 100644
index 0000000..5e46df7
--- /dev/null
+++ b/src/context/Users/context.ts
@@ -0,0 +1,16 @@
+import { createContext } from 'react';
+import { Context, Action } from './types'
+
+export const defaultUsersContext = {
+ loading: true,
+ error: undefined,
+ users: [],
+ dispatch: (action: Action) => {
+ throw new Error(`WARNING: UsersContext.dispatch attempted to fire with type=${action.type} before initialized by a provider.`)
+ },
+ refresh: () => {
+ throw new Error(`WARNING: UsersContext.refresh was called before initialized by a provider.`);
+ },
+};
+
+export const UsersContext = createContext(defaultUsersContext);
diff --git a/src/context/Users/index.ts b/src/context/Users/index.ts
new file mode 100644
index 0000000..7c0dc91
--- /dev/null
+++ b/src/context/Users/index.ts
@@ -0,0 +1,3 @@
+export * from './context';
+export * from './Provider';
+export * from './types';
diff --git a/src/context/Users/reducer.ts b/src/context/Users/reducer.ts
new file mode 100644
index 0000000..f6c9ef3
--- /dev/null
+++ b/src/context/Users/reducer.ts
@@ -0,0 +1,44 @@
+import axios from 'axios';
+
+import { AddAction, DeleteAction, UpdateAction, InitAction, User, Action } from './types'
+
+export const usersReducer = (users: User[] | undefined = [], { type, payload } : Action): User[] => {
+ switch (type) {
+ // Append a User.
+ case 'ADD':
+ const createUser = (payload as AddAction['payload']);
+
+ // NOTE: You'd probably await this or have multiple dispatches before/after async to temporarily and finally update.
+ axios.post(`https://jsonplaceholder.typicode.com/users`, createUser);
+
+ return [...users, createUser];
+
+ // Delete the User from the state.
+ case 'DELETE':
+ const deleteUser = (payload as DeleteAction['payload']);
+
+ // NOTE: You'd probably await this or have multiple dispatches before/after async to temporarily and finally update.
+ axios.delete(`https://jsonplaceholder.typicode.com/users/${deleteUser.id}`);
+
+ return users.filter(user => user.id !== deleteUser.id);
+
+ // Update/Replace a User; this returns the payload in its place, does not merge.
+ case 'UPDATE':
+ const updateUser = (payload as UpdateAction['payload']);
+
+ // NOTE: You'd probably await this or have multiple dispatches before/after async to temporarily and finally update.
+ axios.put(`https://jsonplaceholder.typicode.com/users/${updateUser.id}`, updateUser);
+
+ return users.map(user => {
+ if (user.id === updateUser.id) return updateUser;
+ return user;
+ });
+
+ case 'INIT':
+ // Override the entire Users state, eg. on initialization
+ return (payload as InitAction['payload']);
+
+ default:
+ throw new Error(`Unknown action passed to UsersReducer: ${type}.`)
+ }
+}
diff --git a/src/context/Users/types.ts b/src/context/Users/types.ts
new file mode 100644
index 0000000..6843ac0
--- /dev/null
+++ b/src/context/Users/types.ts
@@ -0,0 +1,33 @@
+import type { Dispatch } from 'react';
+
+export interface User {
+ id: number;
+ name: string;
+ email: string;
+}
+
+export type AddAction = { type: 'ADD'; payload: User };
+export type InitAction = { type: 'INIT'; payload: User[] };
+export type DeleteAction = { type: 'DELETE'; payload: User };
+export type UpdateAction = { type: 'UPDATE'; payload: User };
+
+export type Action = AddAction | InitAction | DeleteAction | UpdateAction;
+
+export interface Context {
+ /** NOTE: Error is not actually hooked up… */
+ error?: string;
+
+ /** Users will be an empty array when `loading=true`. */
+ loading: boolean;
+
+ /** The users state. */
+ users: User[];
+
+ /** Dispatch an action to the users */
+ dispatch: Dispatch;
+
+ /** Refresh the users state from API in the background.
+ * NOTE: Does not set `loading`.
+ */
+ refresh: () => void;
+}
diff --git a/src/index.tsx b/src/index.tsx
index 916d939..bca1271 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -2,11 +2,11 @@ import { StrictMode } from 'react';
import { render } from 'react-dom';
import './global.css';
-import { App } from 'containers/App';
+import { UsersRouter } from 'containers/Users';
render(
-
+
,
document.getElementById('root')
);
diff --git a/yarn.lock b/yarn.lock
index ccb092a..1258688 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1091,6 +1091,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2":
+ version "7.15.4"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
+ integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
version "7.12.18"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.18.tgz#af137bd7e7d9705a412b3caaf991fe6aaa97831b"
@@ -1098,13 +1105,6 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2":
- version "7.15.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
- integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
- dependencies:
- regenerator-runtime "^0.13.4"
-
"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
@@ -1721,6 +1721,11 @@
dependencies:
"@types/node" "*"
+"@types/history@*":
+ version "4.7.9"
+ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
+ integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
+
"@types/html-minifier-terser@^5.0.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
@@ -1810,6 +1815,23 @@
dependencies:
"@types/react" "*"
+"@types/react-router-dom@^5.1.8":
+ version "5.1.8"
+ resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.8.tgz#bf3e1c8149b3d62eaa206d58599de82df0241192"
+ integrity sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==
+ dependencies:
+ "@types/history" "*"
+ "@types/react" "*"
+ "@types/react-router" "*"
+
+"@types/react-router@*":
+ version "5.1.16"
+ resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.16.tgz#f3ba045fb96634e38b21531c482f9aeb37608a99"
+ integrity sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==
+ dependencies:
+ "@types/history" "*"
+ "@types/react" "*"
+
"@types/react@*", "@types/react@^17.0.20":
version "17.0.20"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.20.tgz#a4284b184d47975c71658cd69e759b6bd37c3b8c"
@@ -2548,6 +2570,13 @@ axe-core@^4.0.2:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224"
integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg==
+axios@^0.21.4:
+ version "0.21.4"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
+ integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
+ dependencies:
+ follow-redirects "^1.14.0"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -5063,6 +5092,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147"
integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==
+follow-redirects@^1.14.0:
+ version "1.14.3"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
+ integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
+
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -5459,6 +5493,18 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
+history@^4.9.0:
+ version "4.10.1"
+ resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
+ integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ loose-envify "^1.2.0"
+ resolve-pathname "^3.0.0"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+ value-equal "^1.0.1"
+
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -5468,6 +5514,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
+hoist-non-react-statics@^3.1.0:
+ 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==
+ dependencies:
+ react-is "^16.7.0"
+
hoopy@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -6138,6 +6191,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+ integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -6999,7 +7057,7 @@ loglevel@^1.6.8:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+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"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -7205,6 +7263,14 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
+mini-create-react-context@^0.4.0:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e"
+ integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ tiny-warning "^1.0.3"
+
mini-css-extract-plugin@0.11.3:
version "0.11.3"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz#15b0910a7f32e62ffde4a7430cfefbd700724ea6"
@@ -7964,6 +8030,13 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+path-to-regexp@^1.7.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
+ integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
+ dependencies:
+ isarray "0.0.1"
+
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@@ -8829,7 +8902,7 @@ prompts@2.4.0, prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.7.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==
@@ -9049,7 +9122,7 @@ react-error-overlay@^6.0.9:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
-react-is@^16.8.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==
@@ -9064,6 +9137,35 @@ react-refresh@^0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
+react-router-dom@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
+ integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==
+ dependencies:
+ "@babel/runtime" "^7.12.13"
+ history "^4.9.0"
+ loose-envify "^1.3.1"
+ prop-types "^15.6.2"
+ react-router "5.2.1"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+
+react-router@5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d"
+ integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==
+ dependencies:
+ "@babel/runtime" "^7.12.13"
+ history "^4.9.0"
+ hoist-non-react-statics "^3.1.0"
+ loose-envify "^1.3.1"
+ mini-create-react-context "^0.4.0"
+ path-to-regexp "^1.7.0"
+ prop-types "^15.6.2"
+ react-is "^16.6.0"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+
react-scripts@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.3.tgz#b1cafed7c3fa603e7628ba0f187787964cb5d345"
@@ -9428,6 +9530,11 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+resolve-pathname@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
+ integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
+
resolve-url-loader@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz#235e2c28e22e3e432ba7a5d4e305c59a58edfc08"
@@ -10523,6 +10630,16 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
+tiny-invariant@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
+ integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+
+tiny-warning@^1.0.0, tiny-warning@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
+ integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
@@ -10945,6 +11062,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
+value-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
+ integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
+
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"