mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(console): sie details page (#2496)
This commit is contained in:
parent
746077ec79
commit
db9a054786
12 changed files with 109 additions and 118 deletions
|
@ -1,38 +1,41 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
min-width: 950px;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.setup {
|
||||
.tabs {
|
||||
margin: _.unit(4) 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin-right: _.unit(3);
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tabs {
|
||||
padding-top: _.unit(2);
|
||||
margin-top: _.unit(4);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding-bottom: 0;
|
||||
flex: 1;
|
||||
.contentTop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.formWrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding-bottom: _.unit(8);
|
||||
flex: 1;
|
||||
.form {
|
||||
flex: 1;
|
||||
margin: 0 _.unit(3) _.unit(6) 0;
|
||||
|
||||
&.withSubmitActionBar {
|
||||
margin-bottom: _.unit(3);
|
||||
}
|
||||
|
||||
> :not(:first-child) {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
position: sticky;
|
||||
top: _.unit(4);
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import type { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import CardTitle from '@/components/CardTitle';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import SubmitFormChangesActionBar from '@/components/SubmitFormChangesActionBar';
|
||||
import TabNav, { TabNavItem } from '@/components/TabNav';
|
||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useSettings from '@/hooks/use-settings';
|
||||
import useUiLanguages from '@/hooks/use-ui-languages';
|
||||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
|
||||
import Preview from './components/Preview';
|
||||
import SignUpAndSignInChangePreview from './components/SignUpAndSignInChangePreview';
|
||||
|
@ -39,6 +38,7 @@ const SignInExperience = () => {
|
|||
const { error: languageError, isLoading: isLoadingLanguages } = useUiLanguages();
|
||||
const [dataToCompare, setDataToCompare] = useState<SignInExperienceType>();
|
||||
|
||||
const { current: formId } = useRef(nanoid());
|
||||
const methods = useForm<SignInExperienceForm>();
|
||||
const {
|
||||
reset,
|
||||
|
@ -78,7 +78,7 @@ const SignInExperience = () => {
|
|||
toast.success(t('general.saved'));
|
||||
};
|
||||
|
||||
const onSubmit = handleSubmit(async (formData) => {
|
||||
const onSubmit = handleSubmit(async (formData: SignInExperienceForm) => {
|
||||
if (!data || isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
@ -119,47 +119,43 @@ const SignInExperience = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={classNames(styles.setup, detailsStyles.container)}>
|
||||
<Card className={styles.card}>
|
||||
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
||||
<TabNav className={styles.tabs}>
|
||||
<TabNavItem href="/sign-in-experience/branding">
|
||||
{t('sign_in_exp.tabs.branding')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href="/sign-in-experience/sign-up-and-sign-in">
|
||||
{t('sign_in_exp.tabs.sign_up_and_sign_in')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href="/sign-in-experience/others">
|
||||
{t('sign_in_exp.tabs.others')}
|
||||
</TabNavItem>
|
||||
</TabNav>
|
||||
{!data && error && <div>{`error occurred: ${error.body?.message ?? error.message}`}</div>}
|
||||
{data && defaultFormData && (
|
||||
<div className={styles.container}>
|
||||
<CardTitle title="sign_in_exp.title" subtitle="sign_in_exp.description" />
|
||||
<TabNav className={styles.tabs}>
|
||||
<TabNavItem href="/sign-in-experience/branding">
|
||||
{t('sign_in_exp.tabs.branding')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href="/sign-in-experience/sign-up-and-sign-in">
|
||||
{t('sign_in_exp.tabs.sign_up_and_sign_in')}
|
||||
</TabNavItem>
|
||||
<TabNavItem href="/sign-in-experience/others">{t('sign_in_exp.tabs.others')}</TabNavItem>
|
||||
</TabNav>
|
||||
{!data && error && <div>{`error occurred: ${error.body?.message ?? error.message}`}</div>}
|
||||
{data && defaultFormData && (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.contentTop}>
|
||||
<FormProvider {...methods}>
|
||||
<form className={styles.formWrapper} onSubmit={onSubmit}>
|
||||
<div className={classNames(detailsStyles.body, styles.form)}>
|
||||
{tab === 'branding' && <Branding />}
|
||||
{tab === 'sign-up-and-sign-in' && <SignUpAndSignIn />}
|
||||
{tab === 'others' && <Others />}
|
||||
</div>
|
||||
<div className={detailsStyles.footer}>
|
||||
<div className={detailsStyles.footerMain}>
|
||||
<Button
|
||||
isLoading={isSubmitting}
|
||||
type="primary"
|
||||
size="large"
|
||||
htmlType="submit"
|
||||
title="general.save_changes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
id={formId}
|
||||
className={classNames(styles.form, isDirty && styles.withSubmitActionBar)}
|
||||
>
|
||||
{tab === 'branding' && <Branding />}
|
||||
{tab === 'sign-up-and-sign-in' && <SignUpAndSignIn />}
|
||||
{tab === 'others' && <Others />}
|
||||
</form>
|
||||
</FormProvider>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
{formData.id && <Preview signInExperience={previewConfigs} />}
|
||||
{formData.id && (
|
||||
<Preview signInExperience={previewConfigs} className={styles.preview} />
|
||||
)}
|
||||
</div>
|
||||
<SubmitFormChangesActionBar
|
||||
isOpen={isDirty}
|
||||
isSubmitting={isSubmitting}
|
||||
onDiscard={reset}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{data && (
|
||||
<ConfirmModal
|
||||
isOpen={Boolean(dataToCompare)}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BrandingStyle } from '@logto/schemas';
|
|||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import FormField from '@/components/FormField';
|
||||
import RadioGroup, { Radio } from '@/components/RadioGroup';
|
||||
import TextInput from '@/components/TextInput';
|
||||
|
@ -24,7 +25,7 @@ const BrandingForm = () => {
|
|||
const isSloganRequired = style === BrandingStyle.Logo_Slogan;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.branding.title')}</div>
|
||||
<FormField title="sign_in_exp.branding.ui_style">
|
||||
<Controller
|
||||
|
@ -74,7 +75,7 @@ const BrandingForm = () => {
|
|||
/>
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import ColorPicker from '@/components/ColorPicker';
|
||||
import FormField from '@/components/FormField';
|
||||
import Switch from '@/components/Switch';
|
||||
|
@ -45,7 +46,7 @@ const ColorForm = () => {
|
|||
}, [handleResetColor, isDarkModeEnabled, isDirty, primaryColor, setValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.color.title')}</div>
|
||||
<FormField title="sign_in_exp.color.primary_color">
|
||||
<Controller
|
||||
|
@ -86,7 +87,7 @@ const ColorForm = () => {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import FormField from '@/components/FormField';
|
||||
import Switch from '@/components/Switch';
|
||||
|
||||
|
@ -12,7 +13,7 @@ const AuthenticationForm = () => {
|
|||
const { register } = useFormContext<SignInExperienceForm>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.others.advanced_options.title')}</div>
|
||||
<FormField title="sign_in_exp.others.advanced_options.enable_user_registration">
|
||||
<Switch
|
||||
|
@ -20,7 +21,7 @@ const AuthenticationForm = () => {
|
|||
label={t('sign_in_exp.others.advanced_options.enable_user_registration_description')}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import FormField from '@/components/FormField';
|
||||
import Select from '@/components/Select';
|
||||
import Switch from '@/components/Switch';
|
||||
|
@ -44,7 +45,7 @@ const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
|
|||
}, [languages, selectedDefaultLanguage, setValue, signInExperience]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.others.languages.title')}</div>
|
||||
<FormField title="sign_in_exp.others.languages.enable_auto_detect">
|
||||
<Switch
|
||||
|
@ -69,7 +70,7 @@ const LanguagesForm = ({ isManageLanguageVisible = false }: Props) => {
|
|||
: t('sign_in_exp.others.languages.default_language_description_fixed')}
|
||||
</div>
|
||||
</FormField>
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import FormField from '@/components/FormField';
|
||||
import Switch from '@/components/Switch';
|
||||
import TextInput from '@/components/TextInput';
|
||||
|
@ -19,7 +20,7 @@ const TermsForm = () => {
|
|||
const enabled = watch('termsOfUse.enabled');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.others.terms_of_use.title')}</div>
|
||||
<FormField title="sign_in_exp.others.terms_of_use.enable">
|
||||
<Switch
|
||||
|
@ -44,7 +45,7 @@ const TermsForm = () => {
|
|||
/>
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import FormField from '@/components/FormField';
|
||||
|
||||
import * as styles from '../index.module.scss';
|
||||
import SignInMethodEditBox from './components/SignInMethodEditBox';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const SignInForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.sign_up_and_sign_in.sign_in.title')}</div>
|
||||
<FormField title="sign_in_exp.sign_up_and_sign_in.sign_in.sign_in_identifier_and_auth">
|
||||
<div className={styles.formFieldDescription}>
|
||||
|
@ -17,7 +18,7 @@ const SignInForm = () => {
|
|||
</div>
|
||||
<SignInMethodEditBox />
|
||||
</FormField>
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,12 +3,14 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { snakeCase } from 'snake-case';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import FormField from '@/components/FormField';
|
||||
import Select from '@/components/Select';
|
||||
import useEnabledConnectorTypes from '@/hooks/use-enabled-connector-types';
|
||||
|
||||
import type { SignInExperienceForm } from '../../types';
|
||||
import * as styles from '../index.module.scss';
|
||||
import ConnectorSetupWarning from './components/ConnectorSetupWarning';
|
||||
import {
|
||||
getSignInMethodPasswordCheckState,
|
||||
|
@ -20,7 +22,6 @@ import {
|
|||
signUpIdentifierToRequiredConnectorMapping,
|
||||
signUpToSignInIdentifierMapping,
|
||||
} from './constants';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const SignUpForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
@ -108,7 +109,7 @@ const SignUpForm = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>{t('sign_in_exp.sign_up_and_sign_in.sign_up.title')}</div>
|
||||
<FormField title="sign_in_exp.sign_up_and_sign_in.sign_up.sign_up_identifier">
|
||||
<div className={styles.formFieldDescription}>
|
||||
|
@ -203,7 +204,7 @@ const SignUpForm = () => {
|
|||
</div>
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/components/Card';
|
||||
import FormField from '@/components/FormField';
|
||||
|
||||
import type { SignInExperienceForm } from '../../types';
|
||||
import * as styles from '../index.module.scss';
|
||||
import SocialConnectorEditBox from './components/SocialConnectorEditBox';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const SocialSignInForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { control } = useFormContext<SignInExperienceForm>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div className={styles.title}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.social_sign_in.title')}
|
||||
</div>
|
||||
|
@ -30,7 +31,7 @@ const SocialSignInForm = () => {
|
|||
}}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.title {
|
||||
@include _.subhead-cap;
|
||||
color: var(--color-neutral-variant-60);
|
||||
margin-top: _.unit(12);
|
||||
|
||||
&:first-child {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
.formFieldDescription {
|
||||
font: var(--font-body-medium);
|
||||
color: var(--color-text-secondary);
|
||||
margin: _.unit(1) 0 _.unit(2);
|
||||
}
|
||||
|
||||
.socialOnlyDescription {
|
||||
margin-left: _.unit(1);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.selections {
|
||||
> :not(:first-child) {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
}
|
|
@ -3,10 +3,22 @@
|
|||
.title {
|
||||
@include _.subhead-cap;
|
||||
color: var(--color-neutral-variant-60);
|
||||
margin-top: _.unit(12);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: _.unit(6);
|
||||
.formFieldDescription {
|
||||
font: var(--font-body-medium);
|
||||
color: var(--color-text-secondary);
|
||||
margin: _.unit(1) 0 _.unit(2);
|
||||
}
|
||||
|
||||
.socialOnlyDescription {
|
||||
margin-left: _.unit(1);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.selections {
|
||||
> :not(:first-child) {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue