mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
Merge pull request #554 from logto-io/sijie--log-1730-methods
feat(console): sign-in-methods form setup
This commit is contained in:
commit
4ad95c9eb1
9 changed files with 175 additions and 17 deletions
|
@ -1,4 +1,4 @@
|
|||
import { BrandingStyle, SignInExperience } from '@logto/schemas';
|
||||
import { BrandingStyle } from '@logto/schemas';
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -8,11 +8,12 @@ import RadioGroup, { Radio } from '@/components/RadioGroup';
|
|||
import Switch from '@/components/Switch';
|
||||
import TextInput from '@/components/TextInput';
|
||||
|
||||
import { SignInExperienceForm } from '../types';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const BrandingForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { watch, register, control } = useFormContext<SignInExperience>();
|
||||
const { watch, register, control } = useFormContext<SignInExperienceForm>();
|
||||
|
||||
const isDarkModeEnabled = watch('branding.isDarkModeEnabled');
|
||||
const style = watch('branding.style');
|
||||
|
@ -39,7 +40,6 @@ const BrandingForm = () => {
|
|||
<TextInput {...register('branding.darkPrimaryColor', { required: isDarkModeEnabled })} />
|
||||
</FormField>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.branding.ui_style">
|
||||
{/* TODO: LOG-2153 plain radio */}
|
||||
<Controller
|
||||
name="branding.style"
|
||||
control={control}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import { SignInMethodKey } from '@logto/schemas';
|
||||
import React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FormField from '@/components/FormField';
|
||||
import Switch from '@/components/Switch';
|
||||
|
||||
import { SignInExperienceForm } from '../types';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const signInMethods = Object.values(SignInMethodKey);
|
||||
|
||||
const SignInMethodsForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { register, watch } = useFormContext<SignInExperienceForm>();
|
||||
const primaryMethod = watch('signInMethods.primary');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.title}>{t('sign_in_exp.sign_in_methods.title')}</div>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.sign_in_methods.primary">
|
||||
{/* TODO: LOG-2191 select component */}
|
||||
<select {...register('signInMethods.primary')}>
|
||||
{signInMethods.map((method) => (
|
||||
<option key={method} value={method}>
|
||||
{t('sign_in_exp.sign_in_methods.methods', { context: method })}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.sign_in_methods.enable_secondary">
|
||||
<Switch
|
||||
{...register('signInMethods.enableSecondary', { required: true })}
|
||||
label={t('sign_in_exp.sign_in_methods.enable_secondary_description')}
|
||||
/>
|
||||
{signInMethods.map((method) => (
|
||||
<div key={method}>
|
||||
{/* TODO: LOG-2187 checkbox component */}
|
||||
<input
|
||||
type="checkbox"
|
||||
id={method}
|
||||
value={method}
|
||||
disabled={primaryMethod === method}
|
||||
{...register(`signInMethods.${method}`)}
|
||||
/>
|
||||
<label htmlFor={method}>
|
||||
{t('sign_in_exp.sign_in_methods.methods', { context: method })}
|
||||
{primaryMethod === method && (
|
||||
<span className={styles.primaryTag}>
|
||||
{t('sign_in_exp.sign_in_methods.methods_primary_tag')}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</FormField>
|
||||
<FormField title="admin_console.sign_in_exp.sign_in_methods.define_social_methods">
|
||||
{/* TODO: LOG-1735 transfer component */}
|
||||
<input {...register('socialSignInConnectorIds')} />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInMethodsForm;
|
|
@ -1,4 +1,3 @@
|
|||
import { SignInExperience } from '@logto/schemas';
|
||||
import React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -7,11 +6,12 @@ import FormField from '@/components/FormField';
|
|||
import Switch from '@/components/Switch';
|
||||
import TextInput from '@/components/TextInput';
|
||||
|
||||
import { SignInExperienceForm } from '../types';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const TermsForm = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { watch, register } = useFormContext<SignInExperience>();
|
||||
const { watch, register } = useFormContext<SignInExperienceForm>();
|
||||
const enabled = watch('termsOfUse.enabled');
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
font: var(--font-subhead-cap);
|
||||
margin-top: _.unit(8);
|
||||
}
|
||||
|
||||
.primaryTag {
|
||||
color: var(--color-caption);
|
||||
}
|
||||
|
|
|
@ -14,14 +14,17 @@ import useApi, { RequestError } from '@/hooks/use-api';
|
|||
import * as detailsStyles from '@/scss/details.module.scss';
|
||||
|
||||
import BrandingForm from './components/BrandingForm';
|
||||
import SignInMethodsForm from './components/SignInMethodsForm';
|
||||
import TermsForm from './components/TermsForm';
|
||||
import * as styles from './index.module.scss';
|
||||
import { SignInExperienceForm } from './types';
|
||||
import { signInExperienceParser } from './utilities';
|
||||
|
||||
const SignInExperience = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { tab } = useParams();
|
||||
const { data, error, mutate } = useSWR<SignInExperienceType, RequestError>('/api/sign-in-exp');
|
||||
const methods = useForm<SignInExperienceType>();
|
||||
const methods = useForm<SignInExperienceForm>();
|
||||
const {
|
||||
reset,
|
||||
handleSubmit,
|
||||
|
@ -31,7 +34,7 @@ const SignInExperience = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset(data);
|
||||
reset(signInExperienceParser.toLocalForm(data));
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
|
@ -78,6 +81,7 @@ const SignInExperience = () => {
|
|||
<TermsForm />
|
||||
</>
|
||||
)}
|
||||
{tab === 'methods' && <SignInMethodsForm />}
|
||||
<div className={detailsStyles.footer}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
|
|
12
packages/console/src/pages/SignInExperience/types.ts
Normal file
12
packages/console/src/pages/SignInExperience/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { SignInExperience, SignInMethods } from '@logto/schemas';
|
||||
|
||||
export type SignInExperienceForm = Omit<SignInExperience, 'signInMethods'> & {
|
||||
signInMethods: {
|
||||
primary?: keyof SignInMethods;
|
||||
enableSecondary: boolean;
|
||||
username: boolean;
|
||||
sms: boolean;
|
||||
email: boolean;
|
||||
social: boolean;
|
||||
};
|
||||
};
|
60
packages/console/src/pages/SignInExperience/utilities.ts
Normal file
60
packages/console/src/pages/SignInExperience/utilities.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import {
|
||||
SignInExperience,
|
||||
SignInMethodKey,
|
||||
SignInMethods,
|
||||
SignInMethodState,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import { SignInExperienceForm } from './types';
|
||||
|
||||
const findMethodState = (
|
||||
setup: SignInExperienceForm,
|
||||
method: keyof SignInMethods
|
||||
): SignInMethodState => {
|
||||
const { signInMethods } = setup;
|
||||
|
||||
if (signInMethods.primary === method) {
|
||||
return SignInMethodState.Primary;
|
||||
}
|
||||
|
||||
if (signInMethods[method]) {
|
||||
return SignInMethodState.Secondary;
|
||||
}
|
||||
|
||||
return SignInMethodState.Disabled;
|
||||
};
|
||||
|
||||
export const signInExperienceParser = {
|
||||
toLocalForm: (signInExperience: SignInExperience): SignInExperienceForm => {
|
||||
const methodKeys = Object.values(SignInMethodKey);
|
||||
const primaryMethod = methodKeys.find(
|
||||
(key) => signInExperience.signInMethods[key] === SignInMethodState.Primary
|
||||
);
|
||||
const secondaryMethods = methodKeys.filter(
|
||||
(key) => signInExperience.signInMethods[key] === SignInMethodState.Secondary
|
||||
);
|
||||
|
||||
return {
|
||||
...signInExperience,
|
||||
signInMethods: {
|
||||
primary: primaryMethod,
|
||||
enableSecondary: secondaryMethods.length > 0,
|
||||
username: secondaryMethods.includes(SignInMethodKey.Username),
|
||||
sms: secondaryMethods.includes(SignInMethodKey.SMS),
|
||||
email: secondaryMethods.includes(SignInMethodKey.Email),
|
||||
social: secondaryMethods.includes(SignInMethodKey.Social),
|
||||
},
|
||||
};
|
||||
},
|
||||
toRemoteModel: (setup: SignInExperienceForm): SignInExperience => {
|
||||
return {
|
||||
...setup,
|
||||
signInMethods: {
|
||||
username: findMethodState(setup, 'username'),
|
||||
sms: findMethodState(setup, 'sms'),
|
||||
email: findMethodState(setup, 'email'),
|
||||
social: findMethodState(setup, 'social'),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -355,11 +355,17 @@ const translation = {
|
|||
enable_secondary: 'Enable secondary sign in',
|
||||
enable_secondary_description:
|
||||
"Once it's turned on, you app will support more sign in method(s) besides the primary one. ",
|
||||
methods: {
|
||||
sms: 'Phone number sign in',
|
||||
email: 'Email sign in',
|
||||
social: 'Social sign in',
|
||||
username_password: 'Username-with-password sign in',
|
||||
methods: 'Sign in method',
|
||||
methods_sms: 'Phone number sign in',
|
||||
methods_email: 'Email sign in',
|
||||
methods_social: 'Social sign in',
|
||||
methods_username: 'Username-with-password sign in',
|
||||
methods_primary_tag: '(Primary)',
|
||||
define_social_methods: 'Define social sign in methods',
|
||||
transfer: {
|
||||
title: 'Social connectors',
|
||||
added: 'added',
|
||||
footer: 'Not in the list? Set up more social connectors or go to “Connectors” section.',
|
||||
},
|
||||
},
|
||||
others: {
|
||||
|
|
|
@ -351,11 +351,17 @@ const translation = {
|
|||
enable_secondary: 'Enable secondary sign in',
|
||||
enable_secondary_description:
|
||||
"Once it's turned on, you app will support more sign in method(s) besides the primary one. ",
|
||||
methods: {
|
||||
sms: 'Phone number sign in',
|
||||
email: 'Email sign in',
|
||||
social: 'Social sign in',
|
||||
username_password: 'Username-with-password sign in',
|
||||
methods: 'Sign in method',
|
||||
methods_sms: 'Phone number sign in',
|
||||
methods_email: 'Email sign in',
|
||||
methods_social: 'Social sign in',
|
||||
methods_username: 'Username-with-password sign in',
|
||||
methods_primary_tag: '(Primary)',
|
||||
define_social_methods: 'Define social sign in methods',
|
||||
transfer: {
|
||||
title: 'Social connectors',
|
||||
added: 'added',
|
||||
footer: 'Not in the list? Set up more social connectors or go to “Connectors” section.',
|
||||
},
|
||||
},
|
||||
others: {
|
||||
|
|
Loading…
Add table
Reference in a new issue