0
Fork 0
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:
Xiao Yijun 2022-11-24 13:37:18 +08:00 committed by GitHub
parent 746077ec79
commit db9a054786
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 109 additions and 118 deletions

View file

@ -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;
}
}
}
}

View file

@ -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)}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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