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:
parent
8b2f873041
commit
6f85098853
16 changed files with 163 additions and 184 deletions
|
@ -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": {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
2
packages/ui/src/types/index.ts
Normal file
2
packages/ui/src/types/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export type UserFlow = 'sign-in' | 'register';
|
||||
export type SignInMethod = 'username' | 'email' | 'phone';
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in a new issue