0
Fork 0
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:
Wang Sijie 2022-04-18 15:46:22 +08:00 committed by GitHub
commit 4ad95c9eb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 175 additions and 17 deletions

View file

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

View file

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

View file

@ -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 (

View file

@ -5,3 +5,7 @@
font: var(--font-subhead-cap);
margin-top: _.unit(8);
}
.primaryTag {
color: var(--color-caption);
}

View file

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

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

View 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'),
},
};
},
};

View file

@ -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: {

View file

@ -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: {