0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(ui): update react rout version (#492)

update react route version
This commit is contained in:
simeng-li 2022-04-06 15:01:12 +08:00 committed by GitHub
parent 8b2f873041
commit 6f85098853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 163 additions and 184 deletions

View file

@ -18,6 +18,7 @@
"dependencies": {
"@logto/phrases": "^0.1.0",
"@logto/schemas": "^0.1.0",
"@silverhand/essentials": "^1.1.7",
"classnames": "^2.3.1",
"i18next": "^21.6.11",
"i18next-browser-languagedetector": "^6.1.3",
@ -28,7 +29,7 @@
"react-i18next": "^11.15.4",
"react-modal": "^3.14.4",
"react-phone-number-input": "^3.1.46",
"react-router-dom": "^5.2.0",
"react-router-dom": "^6.2.2",
"react-timer-hook": "^3.0.5"
},
"devDependencies": {

View file

@ -1,5 +1,5 @@
import React from 'react';
import { Route, Switch, BrowserRouter } from 'react-router-dom';
import { Route, Routes, BrowserRouter } from 'react-router-dom';
import AppContent from './components/AppContent';
import useTheme from './hooks/use-theme';
@ -19,14 +19,15 @@ const App = () => {
return (
<AppContent theme={theme}>
<BrowserRouter>
<Switch>
<Route exact path="/sign-in" component={SignIn} />
<Route exact path="/sign-in/consent" component={Consent} />
<Route exact path="/sign-in/:channel" component={SecondarySignIn} />
<Route exact path="/register" component={Register} />
<Route exact path="/register/:channel" component={Register} />
<Route exact path="/:type/:channel/passcode-validation" component={Passcode} />
</Switch>
<Routes>
{/* always keep route path with param as the last one */}
<Route path="/sign-in" element={<SignIn />} />
<Route path="/sign-in/consent" element={<Consent />} />
<Route path="/sign-in/:channel" element={<SecondarySignIn />} />
<Route path="/register" element={<Register />} />
<Route path="/register/:channel" element={<Register />} />
<Route path="/:type/:channel/passcode-validation" element={<Passcode />} />
</Routes>
</BrowserRouter>
</AppContent>
);

View file

@ -1,3 +1,5 @@
import { UserFlow } from '@/types';
import {
verifyEmailPasscode as verifyRegisterEmailPasscode,
verifyPhonePasscode as verifyRegisterPhonePasscode,
@ -11,10 +13,9 @@ import {
sendPhonePasscode as sendSignInPhonePasscode,
} from './sign-in';
export type PasscodeType = 'sign-in' | 'register';
export type PasscodeChannel = 'phone' | 'email';
export const getSendPasscodeApi = (type: PasscodeType, channel: PasscodeChannel) => {
export const getSendPasscodeApi = (type: UserFlow, channel: PasscodeChannel) => {
if (type === 'sign-in' && channel === 'email') {
return sendSignInEmailPasscode;
}
@ -30,7 +31,7 @@ export const getSendPasscodeApi = (type: PasscodeType, channel: PasscodeChannel)
return sendRegisterPhonePasscode;
};
export const getVerifyPasscodeApi = (type: PasscodeType, channel: PasscodeChannel) => {
export const getVerifyPasscodeApi = (type: UserFlow, channel: PasscodeChannel) => {
if (type === 'sign-in' && channel === 'email') {
return verifySignInEmailPasscode;
}

View file

@ -9,11 +9,12 @@ import Passcode, { defaultLength } from '@/components/Passcode';
import TextLink from '@/components/TextLink';
import PageContext from '@/hooks/page-context';
import useApi from '@/hooks/use-api';
import { UserFlow } from '@/types';
import * as styles from './index.module.scss';
type Props = {
type: 'sign-in' | 'register';
type: UserFlow;
channel: 'email' | 'phone';
target: string;
className?: string;

View file

@ -1,5 +1,6 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { sendEmailPasscode as sendRegisterEmailPasscode } from '@/apis/register';
import { sendEmailPasscode as sendSignInEmailPasscode } from '@/apis/sign-in';
@ -11,14 +12,22 @@ jest.mock('@/apis/register', () => ({ sendEmailPasscode: jest.fn(async () => Pro
describe('<EmailPasswordless/>', () => {
test('render', () => {
const { queryByText, container } = render(<EmailPasswordless type="sign-in" />);
const { queryByText, container } = render(
<MemoryRouter>
<EmailPasswordless type="sign-in" />
</MemoryRouter>
);
expect(container.querySelector('input[name="email"]')).not.toBeNull();
expect(queryByText('general.continue')).not.toBeNull();
expect(queryByText('sign_in.terms_of_use')).not.toBeNull();
});
test('required email with error message', () => {
const { queryByText, container, getByText } = render(<EmailPasswordless type="sign-in" />);
const { queryByText, container, getByText } = render(
<MemoryRouter>
<EmailPasswordless type="sign-in" />
</MemoryRouter>
);
const submitButton = getByText('general.continue');
fireEvent.click(submitButton);
@ -37,7 +46,11 @@ describe('<EmailPasswordless/>', () => {
});
test('required terms of agreement with error message', () => {
const { queryByText, container, getByText } = render(<EmailPasswordless type="sign-in" />);
const { queryByText, container, getByText } = render(
<MemoryRouter>
<EmailPasswordless type="sign-in" />
</MemoryRouter>
);
const submitButton = getByText('general.continue');
const emailInput = container.querySelector('input[name="email"]');
@ -50,7 +63,11 @@ describe('<EmailPasswordless/>', () => {
});
test('signin method properly', async () => {
const { container, getByText } = render(<EmailPasswordless type="sign-in" />);
const { container, getByText } = render(
<MemoryRouter>
<EmailPasswordless type="sign-in" />
</MemoryRouter>
);
const emailInput = container.querySelector('input[name="email"]');
if (emailInput) {
@ -69,7 +86,11 @@ describe('<EmailPasswordless/>', () => {
});
test('register method properly', async () => {
const { container, getByText } = render(<EmailPasswordless type="register" />);
const { container, getByText } = render(
<MemoryRouter>
<EmailPasswordless type="register" />
</MemoryRouter>
);
const emailInput = container.querySelector('input[name="email"]');
if (emailInput) {

View file

@ -8,7 +8,7 @@ import { LogtoErrorI18nKey } from '@logto/phrases';
import classNames from 'classnames';
import React, { useState, useCallback, useMemo, useEffect, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { getSendPasscodeApi } from '@/apis/utils';
import Button from '@/components/Button';
@ -17,11 +17,12 @@ import Input from '@/components/Input';
import TermsOfUse from '@/components/TermsOfUse';
import PageContext from '@/hooks/page-context';
import useApi from '@/hooks/use-api';
import { UserFlow } from '@/types';
import * as styles from './index.module.scss';
type Props = {
type: 'sign-in' | 'register';
type: UserFlow;
};
type FieldState = {
@ -46,7 +47,7 @@ const EmailPasswordless = ({ type }: Props) => {
const [fieldState, setFieldState] = useState<FieldState>(defaultState);
const [fieldErrors, setFieldErrors] = useState<ErrorState>({});
const { setToast } = useContext(PageContext);
const history = useHistory();
const navigate = useNavigate();
const sendPasscode = getSendPasscodeApi(type, 'email');
@ -97,10 +98,9 @@ const EmailPasswordless = ({ type }: Props) => {
console.log(result);
if (result) {
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
history.push(`/${type}/email/passcode-validation`, { email: fieldState.email });
navigate(`/${type}/email/passcode-validation`, { state: { email: fieldState.email } });
}
}, [fieldState.email, history, result, type]);
}, [fieldState.email, navigate, result, type]);
useEffect(() => {
// Clear errors

View file

@ -1,5 +1,6 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { sendPhonePasscode as sendRegisterPhonePasscode } from '@/apis/register';
import { sendPhonePasscode as sendSignInPhonePasscode } from '@/apis/sign-in';
@ -14,14 +15,22 @@ describe('<PhonePasswordless/>', () => {
const phoneNumber = '18888888888';
test('render', () => {
const { queryByText, container } = render(<PhonePasswordless type="sign-in" />);
const { queryByText, container } = render(
<MemoryRouter>
<PhonePasswordless type="sign-in" />
</MemoryRouter>
);
expect(container.querySelector('input[name="phone"]')).not.toBeNull();
expect(queryByText('general.continue')).not.toBeNull();
expect(queryByText('sign_in.terms_of_use')).not.toBeNull();
});
test('required phone with error message', () => {
const { queryByText, container, getByText } = render(<PhonePasswordless type="sign-in" />);
const { queryByText, container, getByText } = render(
<MemoryRouter>
<PhonePasswordless type="sign-in" />
</MemoryRouter>
);
const submitButton = getByText('general.continue');
fireEvent.click(submitButton);
@ -40,7 +49,11 @@ describe('<PhonePasswordless/>', () => {
});
test('required terms of agreement with error message', () => {
const { queryByText, container, getByText } = render(<PhonePasswordless type="sign-in" />);
const { queryByText, container, getByText } = render(
<MemoryRouter>
<PhonePasswordless type="sign-in" />
</MemoryRouter>
);
const submitButton = getByText('general.continue');
const phoneInput = container.querySelector('input[name="phone"]');
@ -53,7 +66,11 @@ describe('<PhonePasswordless/>', () => {
});
test('signin method properly', async () => {
const { container, getByText } = render(<PhonePasswordless type="sign-in" />);
const { container, getByText } = render(
<MemoryRouter>
<PhonePasswordless type="sign-in" />
</MemoryRouter>
);
const phoneInput = container.querySelector('input[name="phone"]');
if (phoneInput) {
@ -72,7 +89,11 @@ describe('<PhonePasswordless/>', () => {
});
test('register method properly', async () => {
const { container, getByText } = render(<PhonePasswordless type="register" />);
const { container, getByText } = render(
<MemoryRouter>
<PhonePasswordless type="register" />
</MemoryRouter>
);
const phoneInput = container.querySelector('input[name="phone"]');
if (phoneInput) {

View file

@ -8,7 +8,7 @@ import { LogtoErrorI18nKey } from '@logto/phrases';
import classNames from 'classnames';
import React, { useState, useCallback, useMemo, useEffect, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { getSendPasscodeApi } from '@/apis/utils';
import Button from '@/components/Button';
@ -18,11 +18,12 @@ import TermsOfUse from '@/components/TermsOfUse';
import PageContext from '@/hooks/page-context';
import useApi from '@/hooks/use-api';
import usePhoneNumber, { countryList } from '@/hooks/use-phone-number';
import { UserFlow } from '@/types';
import * as styles from './index.module.scss';
type Props = {
type: 'sign-in' | 'register';
type: UserFlow;
};
type FieldState = {
@ -45,7 +46,7 @@ const PhonePasswordless = ({ type }: Props) => {
const [fieldState, setFieldState] = useState<FieldState>(defaultState);
const [fieldErrors, setFieldErrors] = useState<ErrorState>({});
const { setToast } = useContext(PageContext);
const history = useHistory();
const navigate = useNavigate();
const { phoneNumber, setPhoneNumber, isValidPhoneNumber } = usePhoneNumber();
@ -104,10 +105,9 @@ const PhonePasswordless = ({ type }: Props) => {
console.log(result);
if (result) {
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
history.push(`/${type}/phone/passcode-validation`, { phone: fieldState.phone });
navigate(`/${type}/phone/passcode-validation`, { state: { phone: fieldState.phone } });
}
}, [fieldState.phone, history, result, type]);
}, [fieldState.phone, navigate, result, type]);
useEffect(() => {
// Clear errors

View file

@ -1,6 +1,6 @@
import { render } from '@testing-library/react';
import React from 'react';
import { Route, MemoryRouter } from 'react-router-dom';
import { Routes, Route, MemoryRouter } from 'react-router-dom';
import Passcode from '.';
@ -12,24 +12,12 @@ jest.mock('react-router-dom', () => ({
}));
describe('Passcode Page', () => {
it('render with invalid type should lead to 404 page', () => {
const { queryByText } = render(
<MemoryRouter initialEntries={['/foo/phone/passcode-validation']}>
<Route path="/:type/:channel/passcode-validation">
<Passcode />
</Route>
</MemoryRouter>
);
expect(queryByText('sign_in.enter_passcode')).toBeNull();
});
it('render with invalid channel should lead to 404 page', () => {
const { queryByText } = render(
<MemoryRouter initialEntries={['/sign-in/username/passcode-validation']}>
<Route path="/:type/:channel/passcode-validation">
<Passcode />
</Route>
<Routes>
<Route path="/:type/:channel/passcode-validation" element={<Passcode />} />
</Routes>
</MemoryRouter>
);
@ -39,9 +27,9 @@ describe('Passcode Page', () => {
it('render properly', () => {
const { queryByText } = render(
<MemoryRouter initialEntries={['/sign-in/email/passcode-validation']}>
<Route path="/:type/:channel/passcode-validation">
<Passcode />
</Route>
<Routes>
<Route path="/:type/:channel/passcode-validation" element={<Passcode />} />
</Routes>
</MemoryRouter>
);

View file

@ -1,44 +1,43 @@
import React from 'react';
import { Nullable } from '@silverhand/essentials';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams, useLocation } from 'react-router-dom';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import NavArrowIcon from '@/components/Icons/NavArrowIcon';
import PasscodeValidation from '@/containers/PasscodeValidation';
import { UserFlow } from '@/types';
import * as styles from './index.module.scss';
type Props = {
type: string;
type Parameters = {
type: UserFlow;
channel: string;
};
type StateType = {
type StateType = Nullable<{
email?: string;
phone?: string;
};
}>;
const Passcode = () => {
const { t } = useTranslation();
const history = useHistory();
const { type, channel } = useParams<Props>();
const location = useLocation<StateType>();
const navigate = useNavigate();
const { channel, type } = useParams<Parameters>();
const state = useLocation().state as StateType;
const invalidSignInMethod = type !== 'sign-in' && type !== 'register';
const invalidChannel = channel !== 'email' && channel !== 'phone';
// TODO: 404 page
if (type !== 'sign-in' && type !== 'register') {
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
history.push('/404');
useEffect(() => {
if (invalidSignInMethod || invalidChannel) {
navigate('/404', { replace: true });
}
}, [invalidChannel, invalidSignInMethod, navigate]);
if (invalidSignInMethod || invalidChannel) {
return null;
}
if (channel !== 'email' && channel !== 'phone') {
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
history.push('/404');
return null;
}
const target = location.state[channel];
const target = state ? state[channel] : undefined;
if (!target) {
// TODO: no email or phone found
@ -50,7 +49,7 @@ const Passcode = () => {
<div className={styles.navBar}>
<NavArrowIcon
onClick={() => {
history.goBack();
navigate(-1);
}}
/>
</div>

View file

@ -1,6 +1,6 @@
import { render } from '@testing-library/react';
import React from 'react';
import { Route, MemoryRouter } from 'react-router-dom';
import { Routes, Route, MemoryRouter } from 'react-router-dom';
import Register from '@/pages/Register';
@ -20,9 +20,9 @@ describe('<Register />', () => {
test('renders phone', async () => {
const { queryByText, container } = render(
<MemoryRouter initialEntries={['/register/phone']}>
<Route path="/register/:channel">
<Register />
</Route>
<Routes>
<Route path="/register/:channel" element={<Register />} />
</Routes>
</MemoryRouter>
);
expect(queryByText('register.create_account')).not.toBeNull();
@ -32,9 +32,9 @@ describe('<Register />', () => {
test('renders email', async () => {
const { queryByText, container } = render(
<MemoryRouter initialEntries={['/register/email']}>
<Route path="/register/:channel">
<Register />
</Route>
<Routes>
<Route path="/register/:channel" element={<Register />} />
</Routes>
</MemoryRouter>
);
expect(queryByText('register.create_account')).not.toBeNull();

View file

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import React, { useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import NavArrowIcon from '@/components/Icons/NavArrowIcon';
import CreateAccount from '@/containers/CreateAccount';
@ -8,14 +8,20 @@ import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless'
import * as styles from './index.module.scss';
type Props = {
channel?: 'phone' | 'email' | 'username';
type Parameters = {
channel?: string;
};
const Register = () => {
const { t } = useTranslation();
const history = useHistory();
const { channel } = useParams<Props>();
const navigate = useNavigate();
const { channel = 'username' } = useParams<Parameters>();
useEffect(() => {
if (channel !== 'email' && channel !== 'phone' && channel !== 'username') {
navigate('/404', { replace: true });
}
}, [channel, navigate]);
const registerForm = useMemo(() => {
if (channel === 'phone') {
@ -34,7 +40,7 @@ const Register = () => {
<div className={styles.navBar}>
<NavArrowIcon
onClick={() => {
history.goBack();
navigate(-1);
}}
/>
</div>

View file

@ -1,6 +1,6 @@
import { render } from '@testing-library/react';
import React from 'react';
import { Route, MemoryRouter } from 'react-router-dom';
import { Routes, Route, MemoryRouter } from 'react-router-dom';
import SecondarySignIn from '@/pages/SecondarySignIn';
@ -20,9 +20,9 @@ describe('<SecondarySignIn />', () => {
test('renders phone', async () => {
const { queryByText, container } = render(
<MemoryRouter initialEntries={['/sign-in/phone']}>
<Route path="/sign-in/:channel">
<SecondarySignIn />
</Route>
<Routes>
<Route path="/sign-in/:channel" element={<SecondarySignIn />} />
</Routes>
</MemoryRouter>
);
expect(queryByText('sign_in.sign_in')).not.toBeNull();
@ -32,9 +32,9 @@ describe('<SecondarySignIn />', () => {
test('renders email', async () => {
const { queryByText, container } = render(
<MemoryRouter initialEntries={['/sign-in/email']}>
<Route path="/sign-in/:channel">
<SecondarySignIn />
</Route>
<Routes>
<Route path="/sign-in/:channel" element={<SecondarySignIn />} />
</Routes>
</MemoryRouter>
);
expect(queryByText('sign_in.sign_in')).not.toBeNull();

View file

@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import React, { useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import NavArrowIcon from '@/components/Icons/NavArrowIcon';
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
@ -9,13 +9,19 @@ import UsernameSignin from '@/containers/UsernameSignin';
import * as styles from './index.module.scss';
type Props = {
channel?: 'phone' | 'email' | 'username';
channel?: string;
};
const SecondarySignIn = () => {
const { t } = useTranslation();
const history = useHistory();
const { channel } = useParams<Props>();
const navigate = useNavigate();
const { channel = 'username' } = useParams<Props>();
useEffect(() => {
if (channel !== 'email' && channel !== 'phone' && channel !== 'username') {
navigate('/404', { replace: true });
}
}, [channel, navigate]);
const signInForm = useMemo(() => {
if (channel === 'phone') {
@ -34,7 +40,7 @@ const SecondarySignIn = () => {
<div className={styles.navBar}>
<NavArrowIcon
onClick={() => {
history.goBack();
navigate(-1);
}}
/>
</div>

View file

@ -0,0 +1,2 @@
export type UserFlow = 'sign-in' | 'register';
export type SignInMethod = 'username' | 'email' | 'phone';

View file

@ -281,6 +281,7 @@ importers:
'@parcel/transformer-sass': ^2.3.2
'@silverhand/eslint-config': ^0.10.2
'@silverhand/eslint-config-react': ^0.10.3
'@silverhand/essentials': ^1.1.7
'@silverhand/ts-config': ^0.10.2
'@silverhand/ts-config-react': ^0.10.3
'@testing-library/react': ^12.0.0
@ -308,7 +309,7 @@ importers:
react-i18next: ^11.15.4
react-modal: ^3.14.4
react-phone-number-input: ^3.1.46
react-router-dom: ^5.2.0
react-router-dom: ^6.2.2
react-timer-hook: ^3.0.5
stylelint: ^13.13.1
ts-jest: ^27.0.5
@ -316,6 +317,7 @@ importers:
dependencies:
'@logto/phrases': link:../phrases
'@logto/schemas': link:../schemas
'@silverhand/essentials': 1.1.7
classnames: 2.3.1
i18next: 21.6.11
i18next-browser-languagedetector: 6.1.3
@ -326,7 +328,7 @@ importers:
react-i18next: 11.15.4_3fb644aa30122a07f960d67fa51d6dc1
react-modal: 3.14.4_react-dom@17.0.2+react@17.0.2
react-phone-number-input: 3.1.46_react-dom@17.0.2+react@17.0.2
react-router-dom: 5.3.0_react@17.0.2
react-router-dom: 6.2.2_react-dom@17.0.2+react@17.0.2
react-timer-hook: 3.0.5_react-dom@17.0.2+react@17.0.2
devDependencies:
'@jest/types': 27.5.1
@ -3236,6 +3238,14 @@ packages:
lodash.pick: 4.4.0
dev: false
/@silverhand/essentials/1.1.7:
resolution: {integrity: sha512-XejB+tWcQ3XyIfn5bD6hbPPT20Fkr6xC1ae/DeExQLZYr9piNSgBglaID0nQffbrCpvlojwWlJW5+hLybPf99Q==}
engines: {node: '>=14.15.0', pnpm: '>=6'}
dependencies:
lodash.orderby: 4.6.0
lodash.pick: 4.4.0
dev: false
/@silverhand/ts-config-react/0.10.3_typescript@4.6.2:
resolution: {integrity: sha512-xGOwcw1HTixfP3PSSdJT3leGnlUV0dLna9xp58bDDLul7UCnIn+PNp1VNJxUZ/HvtKbV4ZSYdGsGE6Xqmwn7Ag==}
engines: {node: '>=14.15.0'}
@ -6867,29 +6877,12 @@ packages:
engines: {node: '>=8'}
dev: true
/history/4.10.1:
resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
dependencies:
'@babel/runtime': 7.16.3
loose-envify: 1.4.0
resolve-pathname: 3.0.0
tiny-invariant: 1.2.0
tiny-warning: 1.0.3
value-equal: 1.0.1
dev: false
/history/5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
dependencies:
'@babel/runtime': 7.16.3
dev: false
/hoist-non-react-statics/3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
react-is: 16.13.1
dev: false
/hosted-git-info/2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
@ -9924,18 +9917,6 @@ packages:
engines: {node: '>=4'}
dev: true
/mini-create-react-context/0.4.1_prop-types@15.8.1+react@17.0.2:
resolution: {integrity: sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==}
peerDependencies:
prop-types: ^15.0.0
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
dependencies:
'@babel/runtime': 7.16.3
prop-types: 15.8.1
react: 17.0.2
tiny-warning: 1.0.3
dev: false
/minimatch/3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
@ -12015,21 +11996,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/react-router-dom/5.3.0_react@17.0.2:
resolution: {integrity: sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==}
peerDependencies:
react: '>=15'
dependencies:
'@babel/runtime': 7.16.3
history: 4.10.1
loose-envify: 1.4.0
prop-types: 15.8.1
react: 17.0.2
react-router: 5.2.1_react@17.0.2
tiny-invariant: 1.2.0
tiny-warning: 1.0.3
dev: false
/react-router-dom/6.2.2_react-dom@17.0.2+react@17.0.2:
resolution: {integrity: sha512-AtYEsAST7bDD4dLSQHDnk/qxWLJdad5t1HFa1qJyUrCeGgEuCSw0VB/27ARbF9Fi/W5598ujvJOm3ujUCVzuYQ==}
peerDependencies:
@ -12042,24 +12008,6 @@ packages:
react-router: 6.2.2_react@17.0.2
dev: false
/react-router/5.2.1_react@17.0.2:
resolution: {integrity: sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==}
peerDependencies:
react: '>=15'
dependencies:
'@babel/runtime': 7.16.3
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
mini-create-react-context: 0.4.1_prop-types@15.8.1+react@17.0.2
path-to-regexp: 1.8.0
prop-types: 15.8.1
react: 17.0.2
react-is: 16.13.1
tiny-invariant: 1.2.0
tiny-warning: 1.0.3
dev: false
/react-router/6.2.2_react@17.0.2:
resolution: {integrity: sha512-/MbxyLzd7Q7amp4gDOGaYvXwhEojkJD5BtExkuKmj39VEE0m3l/zipf6h2WIB2jyAO0lI6NGETh4RDcktRm4AQ==}
peerDependencies:
@ -12386,10 +12334,6 @@ packages:
path-is-absolute: 1.0.1
dev: false
/resolve-pathname/3.0.0:
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
dev: false
/resolve.exports/1.1.0:
resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==}
engines: {node: '>=10'}
@ -13536,14 +13480,6 @@ packages:
resolution: {integrity: sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=}
dev: true
/tiny-invariant/1.2.0:
resolution: {integrity: sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==}
dev: false
/tiny-warning/1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/tmp/0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
@ -14110,10 +14046,6 @@ packages:
builtins: 1.0.3
dev: true
/value-equal/1.0.1:
resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==}
dev: false
/vary/1.1.2:
resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
engines: {node: '>= 0.8'}