mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
refactor(ui): extract secondary page layout (#2285)
This commit is contained in:
parent
a397a28148
commit
d81c497751
13 changed files with 69 additions and 252 deletions
39
packages/ui/src/components/SecondaryPageWrapper/index.tsx
Normal file
39
packages/ui/src/components/SecondaryPageWrapper/index.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import type { TFuncKey } from 'react-i18next';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
title?: TFuncKey;
|
||||
description?: TFuncKey;
|
||||
titleProps?: Record<string, unknown>;
|
||||
descriptionProps?: Record<string, unknown>;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const SecondaryPageWrapper = ({
|
||||
title,
|
||||
description,
|
||||
titleProps,
|
||||
descriptionProps,
|
||||
children,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar />
|
||||
<div className={styles.container}>
|
||||
{title && <div className={styles.title}>{t(title, titleProps)}</div>}
|
||||
{description && (
|
||||
<div className={styles.description}>{t(description, descriptionProps)}</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecondaryPageWrapper;
|
|
@ -1,18 +1,14 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import { EmailPasswordless, PhonePasswordless } from '@/containers/Passwordless';
|
||||
import ErrorPage from '@/pages/ErrorPage';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
method?: string;
|
||||
};
|
||||
|
||||
const ForgotPassword = () => {
|
||||
const { t } = useTranslation();
|
||||
const { method = '' } = useParams<Props>();
|
||||
|
||||
// TODO: @simeng LOG-4486 apply supported method guard validation. Including the form hasSwitch validation bellow
|
||||
|
@ -23,17 +19,13 @@ const ForgotPassword = () => {
|
|||
const PasswordlessForm = method === 'email' ? EmailPasswordless : PhonePasswordless;
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>{t('description.reset_password')}</div>
|
||||
<div className={styles.description}>
|
||||
{t(`description.reset_password_description_${method === 'email' ? 'email' : 'sms'}`)}
|
||||
</div>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
|
||||
<PasswordlessForm autoFocus hasSwitch type="forgot-password" hasTerms={false} />
|
||||
</div>
|
||||
</div>
|
||||
<SecondaryPageWrapper
|
||||
title="description.reset_password"
|
||||
description={`description.reset_password_description_${method === 'email' ? 'email' : 'sms'}`}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
|
||||
<PasswordlessForm autoFocus hasSwitch type="forgot-password" hasTerms={false} />
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
@include _.full-page;
|
||||
}
|
||||
|
||||
.container {
|
||||
@include _.full-width;
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: _.unit(1);
|
||||
}
|
||||
|
||||
.detail {
|
||||
margin-bottom: _.unit(6);
|
||||
@include _.text-hint;
|
||||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
.container {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title;
|
||||
}
|
||||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
.container {
|
||||
margin-top: _.unit(12);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title-desktop;
|
||||
}
|
||||
}
|
|
@ -1,24 +1,21 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useLocation } from 'react-router-dom';
|
||||
import { is } from 'superstruct';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import PasscodeValidation from '@/containers/PasscodeValidation';
|
||||
import ErrorPage from '@/pages/ErrorPage';
|
||||
import type { UserFlow } from '@/types';
|
||||
import { passcodeStateGuard, passcodeMethodGuard, userFlowGuard } from '@/types/guard';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Parameters = {
|
||||
type: UserFlow;
|
||||
method: string;
|
||||
};
|
||||
|
||||
const Passcode = () => {
|
||||
const { t } = useTranslation();
|
||||
const { method, type = '' } = useParams<Parameters>();
|
||||
const { state } = useLocation();
|
||||
|
||||
const invalidType = !is(type, userFlowGuard);
|
||||
const invalidMethod = !is(method, passcodeMethodGuard);
|
||||
const invalidState = !is(state, passcodeStateGuard);
|
||||
|
@ -34,18 +31,13 @@ const Passcode = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>{t('action.enter_passcode')}</div>
|
||||
<div className={styles.detail}>
|
||||
{t('description.enter_passcode', {
|
||||
address: t(`description.${method === 'email' ? 'email' : 'phone_number'}`),
|
||||
})}
|
||||
</div>
|
||||
<PasscodeValidation type={type} method={method} target={target} />
|
||||
</div>
|
||||
</div>
|
||||
<SecondaryPageWrapper
|
||||
title="action.enter_passcode"
|
||||
description="description.enter_passcode"
|
||||
descriptionProps={{ address: `description.${method === 'email' ? 'email' : 'phone_number'}` }}
|
||||
>
|
||||
<PasscodeValidation type={type} method={method} target={target} />
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
@include _.full-page;
|
||||
}
|
||||
|
||||
.container {
|
||||
@include _.full-width;
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
|
||||
:global(body.mobile) {
|
||||
.container {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title;
|
||||
margin-bottom: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
.container {
|
||||
margin-top: _.unit(12);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title-desktop;
|
||||
margin-bottom: _.unit(4);
|
||||
}
|
||||
}
|
|
@ -1,22 +1,12 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import ResetPasswordForm from '@/containers/ResetPassword';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const ResetPassword = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>{t('description.new_password')}</div>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
|
||||
<ResetPasswordForm autoFocus />
|
||||
</div>
|
||||
</div>
|
||||
<SecondaryPageWrapper title="description.new_password">
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
|
||||
<ResetPasswordForm autoFocus />
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
@include _.full-page;
|
||||
}
|
||||
|
||||
.container {
|
||||
@include _.full-width;
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
.container {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title;
|
||||
margin-bottom: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
.container {
|
||||
margin-top: _.unit(12);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title-desktop;
|
||||
margin-bottom: _.unit(4);
|
||||
}
|
||||
}
|
|
@ -1,20 +1,16 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import CreateAccount from '@/containers/CreateAccount';
|
||||
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
||||
import ErrorPage from '@/pages/ErrorPage';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Parameters = {
|
||||
method?: string;
|
||||
};
|
||||
|
||||
const SecondaryRegister = () => {
|
||||
const { t } = useTranslation();
|
||||
const { method = 'username' } = useParams<Parameters>();
|
||||
|
||||
const registerForm = useMemo(() => {
|
||||
|
@ -36,15 +32,7 @@ const SecondaryRegister = () => {
|
|||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>{t('action.create_account')}</div>
|
||||
{registerForm}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <SecondaryPageWrapper title="action.create_account">{registerForm}</SecondaryPageWrapper>;
|
||||
};
|
||||
|
||||
export default SecondaryRegister;
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
@include _.full-page;
|
||||
}
|
||||
|
||||
.container {
|
||||
@include _.full-width;
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
.container {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title;
|
||||
margin-bottom: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
.container {
|
||||
margin-top: _.unit(12);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title-desktop;
|
||||
margin-bottom: _.unit(4);
|
||||
}
|
||||
}
|
|
@ -1,20 +1,16 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
||||
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||
import ErrorPage from '@/pages/ErrorPage';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
method?: string;
|
||||
};
|
||||
|
||||
const SecondarySignIn = () => {
|
||||
const { t } = useTranslation();
|
||||
const { method = 'username' } = useParams<Props>();
|
||||
|
||||
const signInForm = useMemo(() => {
|
||||
|
@ -36,15 +32,7 @@ const SecondarySignIn = () => {
|
|||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>{t('action.sign_in')}</div>
|
||||
{signInForm}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <SecondaryPageWrapper title="action.sign_in">{signInForm}</SecondaryPageWrapper>;
|
||||
};
|
||||
|
||||
export default SecondarySignIn;
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
@include _.full-page;
|
||||
}
|
||||
|
||||
.container {
|
||||
@include _.full-width;
|
||||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
.container {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
.container {
|
||||
margin-top: _.unit(12);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include _.title-desktop;
|
||||
margin-bottom: _.unit(8);
|
||||
}
|
||||
}
|
|
@ -1,33 +1,23 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import NavBar from '@/components/NavBar';
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import SocialCreateAccount from '@/containers/SocialCreateAccount';
|
||||
import usePlatform from '@/hooks/use-platform';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Parameters = {
|
||||
connector: string;
|
||||
};
|
||||
|
||||
const SocialRegister = () => {
|
||||
const { t } = useTranslation();
|
||||
const { connector } = useParams<Parameters>();
|
||||
const { isMobile } = usePlatform();
|
||||
|
||||
if (!connector) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<NavBar title={isMobile ? t('description.bind_account_title') : undefined} />
|
||||
<div className={styles.container}>
|
||||
{!isMobile && <div className={styles.title}>{t('description.bind_account_title')}</div>}
|
||||
<SocialCreateAccount connectorId={connector} />
|
||||
</div>
|
||||
</div>
|
||||
<SecondaryPageWrapper title="description.bind_account_title">
|
||||
<SocialCreateAccount connectorId={connector} />
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue