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}

} + + + + +
+ ); +} 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}

} + + +
; +} 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 ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); -} 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 }) => ( + + +
    +
  • Users
  • +
+ + {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"