0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-03 21:48:55 -05:00

feat(console): sie sign-in form (#2229)

This commit is contained in:
Xiao Yijun 2022-10-24 19:57:50 +08:00 committed by GitHub
parent 569069524e
commit 75b6d3946a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 622 additions and 97 deletions

View file

@ -0,0 +1,3 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.5915 7.7415L15.2581 4.40816C15.1804 4.33046 15.0882 4.26883 14.9867 4.22678C14.8851 4.18473 14.7763 4.16309 14.6665 4.16309C14.4445 4.16309 14.2317 4.25124 14.0748 4.40816C13.9179 4.56508 13.8297 4.77791 13.8297 4.99983C13.8297 5.22175 13.9179 5.43458 14.0748 5.5915L15.9915 7.49983H6.33312C6.11211 7.49983 5.90015 7.58763 5.74387 7.74391C5.58759 7.90019 5.49979 8.11215 5.49979 8.33316C5.49979 8.55418 5.58759 8.76614 5.74387 8.92242C5.90015 9.0787 6.11211 9.1665 6.33312 9.1665H17.9998C18.1643 9.16567 18.3249 9.11617 18.4613 9.02423C18.5978 8.93228 18.7039 8.80201 18.7665 8.64983C18.8303 8.49807 18.8477 8.33081 18.8166 8.16915C18.7854 8.00749 18.7071 7.85868 18.5915 7.7415ZM14.6665 10.8332H2.99979C2.83527 10.834 2.67467 10.8835 2.53824 10.9754C2.40181 11.0674 2.29564 11.1976 2.23312 11.3498C2.16931 11.5016 2.15187 11.6688 2.18302 11.8305C2.21416 11.9922 2.29249 12.141 2.40812 12.2582L5.74146 15.5915C5.81893 15.6696 5.91109 15.7316 6.01264 15.7739C6.11419 15.8162 6.22311 15.838 6.33312 15.838C6.44313 15.838 6.55206 15.8162 6.6536 15.7739C6.75515 15.7316 6.84732 15.6696 6.92479 15.5915C7.0029 15.514 7.06489 15.4219 7.1072 15.3203C7.14951 15.2188 7.17129 15.1098 7.17129 14.9998C7.17129 14.8898 7.14951 14.7809 7.1072 14.6793C7.06489 14.5778 7.0029 14.4856 6.92479 14.4082L5.00812 12.4998H14.6665C14.8875 12.4998 15.0994 12.412 15.2557 12.2558C15.412 12.0995 15.4998 11.8875 15.4998 11.6665C15.4998 11.4455 15.412 11.2335 15.2557 11.0772C15.0994 10.921 14.8875 10.8332 14.6665 10.8332Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,61 +0,0 @@
import type { ConnectorResponse } from '@logto/schemas';
import { ConnectorType, SignUpIdentifier } from '@logto/schemas';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { snakeCase } from 'snake-case';
import useSWR from 'swr';
import Alert from '@/components/Alert';
import type { RequestError } from '@/hooks/use-api';
type Props = {
signUpIdentifier: SignUpIdentifier;
};
const ConnectorSetupWarning = ({ signUpIdentifier }: Props) => {
const { data: connectors } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const connectorTypes = useMemo(() => {
if (signUpIdentifier === SignUpIdentifier.Username) {
return [];
}
if (signUpIdentifier === SignUpIdentifier.Email) {
return [ConnectorType.Email];
}
if (signUpIdentifier === SignUpIdentifier.Phone) {
return [ConnectorType.Sms];
}
if (signUpIdentifier === SignUpIdentifier.EmailOrPhone) {
return [ConnectorType.Email, ConnectorType.Sms];
}
return [ConnectorType.Social];
}, [signUpIdentifier]);
if (connectorTypes.length === 0 || !connectors) {
return null;
}
if (
connectorTypes.every((connectorType) =>
connectors.some(({ type, enabled }) => type === connectorType && enabled)
)
) {
return null;
}
return (
<Alert
action="general.set_up"
href={connectorTypes.includes(ConnectorType.Social) ? '/connectors/social' : '/connectors'}
>
{t('sign_in_exp.setup_warning.no_connector', { context: snakeCase(signUpIdentifier) })}
</Alert>
);
};
export default ConnectorSetupWarning;

View file

@ -0,0 +1,46 @@
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import FormField from '@/components/FormField';
import type { SignInExperienceForm } from '../../types';
import SignInMethodEditBox from './components/SignInMethodEditBox';
import { signUpToSignInIdentifierMapping } from './constants';
import * as styles from './index.module.scss';
const SignInForm = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { control, watch } = useFormContext<SignInExperienceForm>();
const signUpIdentifier = watch('signUp.identifier');
const requirePassword = watch('signUp.password');
const requireVerificationCode = watch('signUp.verify');
return (
<>
<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.signInDescription}>
{t('sign_in_exp.sign_up_and_sign_in.sign_in.description')}
</div>
<Controller
control={control}
name="signIn.methods"
render={({ field: { value, onChange } }) => {
return (
<SignInMethodEditBox
value={value}
requiredSignInIdentifiers={signUpToSignInIdentifierMapping[signUpIdentifier]}
isPasswordRequired={requirePassword}
isVerificationRequired={requireVerificationCode}
onChange={onChange}
/>
);
}}
/>
</FormField>
</>
);
};
export default SignInForm;

View file

@ -9,17 +9,14 @@ import FormField from '@/components/FormField';
import Select from '@/components/Select';
import type { SignInExperienceForm } from '../../types';
import ConnectorSetupWarning from './ConnectorSetupWarning';
import ConnectorSetupWarning from './components/ConnectorSetupWarning';
import {
requiredVerifySignUpIdentifiers,
signUpIdentifiers,
signUpIdentifierToRequiredConnectorMapping,
} from './constants';
import * as styles from './index.module.scss';
const signUpIdentifiers = Object.values(SignUpIdentifier);
const requireVerifyIdentifiers = new Set([
SignUpIdentifier.Email,
SignUpIdentifier.Phone,
SignUpIdentifier.EmailOrPhone,
]);
const SignUpForm = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { control, setValue, resetField, watch } = useFormContext<SignInExperienceForm>();
@ -40,7 +37,7 @@ const SignUpForm = () => {
return;
}
if (requireVerifyIdentifiers.has(signUpIdentifier)) {
if (requiredVerifySignUpIdentifiers.includes(signUpIdentifier)) {
resetField('signUp.password');
setValue('signUp.verify', true);
}
@ -80,7 +77,9 @@ const SignUpForm = () => {
)}
/>
{signUpIdentifier !== SignUpIdentifier.None && (
<ConnectorSetupWarning signUpIdentifier={signUpIdentifier} />
<ConnectorSetupWarning
requiredConnectors={signUpIdentifierToRequiredConnectorMapping[signUpIdentifier]}
/>
)}
</FormField>
{signUpIdentifier !== SignUpIdentifier.None && (
@ -108,7 +107,7 @@ const SignUpForm = () => {
<Checkbox
label={t('sign_in_exp.sign_up_and_sign_in.sign_up.verify_at_sign_up_option')}
value={value}
disabled={requireVerifyIdentifiers.has(signUpIdentifier)}
disabled={requiredVerifySignUpIdentifiers.includes(signUpIdentifier)}
onChange={onChange}
/>
)}

View file

@ -0,0 +1,48 @@
import type { ConnectorResponse } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import Alert from '@/components/Alert';
import type { RequestError } from '@/hooks/use-api';
type Props = {
requiredConnectors: ConnectorType[];
};
const ConnectorSetupWarning = ({ requiredConnectors }: Props) => {
const { data: connectors } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
if (!connectors) {
return null;
}
const missingConnectors = (
requiredConnectors.length === 0 ? [ConnectorType.Social] : requiredConnectors
).filter(
(connectorType) => !connectors.some(({ type, enabled }) => type === connectorType && enabled)
);
if (missingConnectors.length === 0) {
return null;
}
return (
<>
{missingConnectors.map((connectorType) => (
<Alert
key={connectorType}
action="general.set_up"
href={connectorType === ConnectorType.Social ? '/connectors/social' : '/connectors'}
>
{t('sign_in_exp.setup_warning.no_connector', {
context: connectorType.toLowerCase(),
})}
</Alert>
))}
</>
);
};
export default ConnectorSetupWarning;

View file

@ -0,0 +1,64 @@
import type { SignInIdentifier } from '@logto/schemas';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { snakeCase } from 'snake-case';
import Button from '@/components/Button';
import Dropdown, { DropdownItem } from '@/components/Dropdown';
type Props = {
options: SignInIdentifier[];
onSelected: (signInIdentifier: SignInIdentifier) => void;
};
// TODO: @yijun extract this component to share with the future add social button
const AddSignInMethodButton = ({ options, onSelected }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const anchorRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
if (options.length === 0) {
return null;
}
const candidates = options.map((identifier) => ({
value: identifier,
title: t('sign_in_exp.sign_up_and_sign_in.identifiers', { context: snakeCase(identifier) }),
}));
return (
<>
<div ref={anchorRef}>
<Button
type="text"
size="small"
title="general.add_another"
onClick={() => {
setIsOpen(true);
}}
/>
</div>
<Dropdown
anchorRef={anchorRef}
isOpen={isOpen}
horizontalAlign="start"
onClose={() => {
setIsOpen(false);
}}
>
{candidates.map(({ value, title }) => (
<DropdownItem
key={value}
onClick={() => {
onSelected(value);
}}
>
{title}
</DropdownItem>
))}
</Dropdown>
</>
);
};
export default AddSignInMethodButton;

View file

@ -0,0 +1,97 @@
import { SignInIdentifier } from '@logto/schemas';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { snakeCase } from 'snake-case';
import Draggable from '@/assets/images/draggable.svg';
import Minus from '@/assets/images/minus.svg';
import SwitchArrowIcon from '@/assets/images/switch-arrow.svg';
import Checkbox from '@/components/Checkbox';
import IconButton from '@/components/IconButton';
import * as styles from './index.module.scss';
import type { SignInMethod } from './types';
type Props = {
signInMethod: SignInMethod;
isPasswordRequired: boolean;
isVerificationRequired: boolean;
isDeletable: boolean;
onVerificationStateChange: (
identifier: SignInIdentifier,
verification: 'password' | 'verificationCode',
checked: boolean
) => void;
onToggleVerificationPrimary: (identifier: SignInIdentifier) => void;
onDelete: (identifier: SignInIdentifier) => void;
};
const SignInMethodItem = ({
signInMethod: { identifier, password, verificationCode, isPasswordPrimary },
isPasswordRequired,
isVerificationRequired,
isDeletable,
onVerificationStateChange,
onToggleVerificationPrimary,
onDelete,
}: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<div key={snakeCase(identifier)} className={styles.signInMethodItem}>
<div className={styles.signInMethod}>
<div className={styles.identifier}>
<Draggable className={styles.draggableIcon} />
{t('sign_in_exp.sign_up_and_sign_in.identifiers', {
context: snakeCase(identifier),
})}
</div>
<div
className={classNames(
styles.authentication,
!isPasswordPrimary && styles.verifyCodePrimary
)}
>
<Checkbox
label={t('sign_in_exp.sign_up_and_sign_in.sign_in.password_auth')}
value={password}
disabled={isPasswordRequired}
onChange={(checked) => {
onVerificationStateChange(identifier, 'password', checked);
}}
/>
{identifier !== SignInIdentifier.Username && (
<>
<IconButton
tooltip="sign_in_exp.sign_up_and_sign_in.sign_in.auth_swap_tip"
onClick={() => {
onToggleVerificationPrimary(identifier);
}}
>
<SwitchArrowIcon />
</IconButton>
<Checkbox
label={t('sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth')}
value={verificationCode}
disabled={isVerificationRequired && !isPasswordRequired}
onChange={(checked) => {
onVerificationStateChange(identifier, 'verificationCode', checked);
}}
/>
</>
)}
</div>
</div>
<IconButton
disabled={isDeletable}
onClick={() => {
onDelete(identifier);
}}
>
<Minus />
</IconButton>
</div>
);
};
export default SignInMethodItem;

View file

@ -0,0 +1,43 @@
@use '@/scss/underscore' as _;
.signInMethodItem {
display: flex;
align-items: center;
margin: _.unit(2) 0;
}
.signInMethod {
display: flex;
align-items: center;
height: 44px;
width: 100%;
margin-right: _.unit(2);
padding: _.unit(3) _.unit(2);
background-color: var(--color-layer-2);
border-radius: 8px;
cursor: move;
color: var(--color-text);
.identifier {
width: 130px;
display: flex;
font: var(--font-label-large);
align-items: center;
}
.authentication {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 _.unit(2);
flex-grow: 1;
&.verifyCodePrimary {
flex-direction: row-reverse;
}
}
.draggableIcon {
color: var(--color-text-secondary);
}
}

View file

@ -0,0 +1,145 @@
import type { ConnectorType, SignInIdentifier } from '@logto/schemas';
import { useCallback, useEffect, useRef } from 'react';
import DragDropProvider from '@/components/Transfer/DragDropProvider';
import DraggableItem from '@/components/Transfer/DraggableItem';
import { signInIdentifiers, signInIdentifierToRequiredConnectorMapping } from '../../constants';
import ConnectorSetupWarning from '../ConnectorSetupWarning';
import AddSignInMethodButton from './AddSignInMethodButton';
import SignInMethodItem from './SignInMethodItem';
import type { SignInMethod } from './types';
import {
computeOnSignInMethodAppended as appendSignInMethodIfNotExist,
computeOnVerificationStateChanged,
computeOnPasswordPrimaryFlagToggled,
} from './utilities';
type Props = {
value: SignInMethod[];
onChange: (value: SignInMethod[]) => void;
requiredSignInIdentifiers: SignInIdentifier[];
isPasswordRequired: boolean;
isVerificationRequired: boolean;
};
const SignInMethodEditBox = ({
value,
onChange,
requiredSignInIdentifiers,
isPasswordRequired,
isVerificationRequired,
}: Props) => {
const signInIdentifierOptions = signInIdentifiers.filter((candidateIdentifier) =>
value.every(({ identifier }) => identifier !== candidateIdentifier)
);
// Note: add a reference to avoid infinite loop when change the value by `useEffect`
const signInMethods = useRef(value);
const handleChange = useCallback(
(value: SignInMethod[]) => {
// eslint-disable-next-line @silverhand/fp/no-mutation
signInMethods.current = value;
onChange(value);
},
[onChange]
);
const addSignInMethod = useCallback(
(identifier: SignInIdentifier) => {
handleChange(
appendSignInMethodIfNotExist(value, identifier, isPasswordRequired, isVerificationRequired)
);
},
[handleChange, value, isPasswordRequired, isVerificationRequired]
);
useEffect(() => {
const requiredSignInMethods = requiredSignInIdentifiers.reduce(
(previous, current) =>
appendSignInMethodIfNotExist(previous, current, isPasswordRequired, isVerificationRequired),
signInMethods.current
);
handleChange(
requiredSignInMethods.map((method) => ({
...method,
password: isPasswordRequired,
verificationCode: isVerificationRequired,
}))
);
}, [handleChange, isPasswordRequired, isVerificationRequired, requiredSignInIdentifiers]);
const onMoveItem = (dragIndex: number, hoverIndex: number) => {
const dragItem = value[dragIndex];
const hoverItem = value[hoverIndex];
if (!dragItem || !hoverItem) {
return;
}
handleChange(
value.map((value_, index) => {
if (index === dragIndex) {
return hoverItem;
}
if (index === hoverIndex) {
return dragItem;
}
return value_;
})
);
};
return (
<div>
<DragDropProvider>
{value.map((signInMethod, index) => (
<DraggableItem
key={signInMethod.identifier}
id={signInMethod.identifier}
sortIndex={index}
moveItem={onMoveItem}
>
<SignInMethodItem
signInMethod={signInMethod}
isPasswordRequired={isPasswordRequired}
isVerificationRequired={isVerificationRequired}
isDeletable={requiredSignInIdentifiers.includes(signInMethod.identifier)}
onVerificationStateChange={(identifier, verification, checked) => {
handleChange(
computeOnVerificationStateChanged(value, identifier, verification, checked)
);
}}
onToggleVerificationPrimary={(identifier) => {
handleChange(computeOnPasswordPrimaryFlagToggled(value, identifier));
}}
onDelete={(identifier) => {
handleChange(value.filter((method) => method.identifier !== identifier));
}}
/>
</DraggableItem>
))}
</DragDropProvider>
{requiredSignInIdentifiers.length > 0 && (
<ConnectorSetupWarning
requiredConnectors={requiredSignInIdentifiers.reduce<ConnectorType[]>(
(connectors, signInIdentifier) => {
return [
...connectors,
...signInIdentifierToRequiredConnectorMapping[signInIdentifier],
];
},
[]
)}
/>
)}
<AddSignInMethodButton options={signInIdentifierOptions} onSelected={addSignInMethod} />
</div>
);
};
export default SignInMethodEditBox;

View file

@ -0,0 +1,3 @@
import type { SignInExperience } from '@logto/schemas';
export type SignInMethod = SignInExperience['signIn']['methods'][number];

View file

@ -0,0 +1,52 @@
import type { SignInIdentifier } from '@logto/schemas';
import type { SignInMethod } from './types';
export const computeOnVerificationStateChanged = (
oldValue: SignInMethod[],
identifier: SignInIdentifier,
verification: 'password' | 'verificationCode',
checked: boolean
) =>
oldValue.map((method) =>
method.identifier === identifier
? {
...method,
[verification]: checked,
}
: method
);
export const computeOnSignInMethodAppended = (
oldValue: SignInMethod[],
signInIdentifier: SignInIdentifier,
requirePassword: boolean,
requireVerificationCode: boolean
) => {
if (oldValue.some((method) => method.identifier === signInIdentifier)) {
return oldValue;
}
return [
...oldValue,
{
identifier: signInIdentifier,
password: requirePassword,
verificationCode: requireVerificationCode,
isPasswordPrimary: true,
},
];
};
export const computeOnPasswordPrimaryFlagToggled = (
oldValue: SignInMethod[],
identifier: SignInIdentifier
) =>
oldValue.map((method) =>
method.identifier === identifier
? {
...method,
isPasswordPrimary: !method.isPasswordPrimary,
}
: method
);

View file

@ -0,0 +1,37 @@
import { SignUpIdentifier, SignInIdentifier, ConnectorType } from '@logto/schemas';
export const signUpIdentifiers = Object.values(SignUpIdentifier);
export const signInIdentifiers = Object.values(SignInIdentifier);
export const requiredVerifySignUpIdentifiers = [
SignUpIdentifier.Email,
SignUpIdentifier.Phone,
SignUpIdentifier.EmailOrPhone,
];
export const signUpToSignInIdentifierMapping: { [key in SignUpIdentifier]: SignInIdentifier[] } = {
[SignUpIdentifier.Username]: [SignInIdentifier.Username],
[SignUpIdentifier.Email]: [SignInIdentifier.Email],
[SignUpIdentifier.Phone]: [SignInIdentifier.Phone],
[SignUpIdentifier.EmailOrPhone]: [SignInIdentifier.Email, SignInIdentifier.Phone],
[SignUpIdentifier.None]: [],
};
export const signUpIdentifierToRequiredConnectorMapping: {
[key in SignUpIdentifier]: ConnectorType[];
} = {
[SignUpIdentifier.Username]: [],
[SignUpIdentifier.Email]: [ConnectorType.Email],
[SignUpIdentifier.Phone]: [ConnectorType.Sms],
[SignUpIdentifier.EmailOrPhone]: [ConnectorType.Email, ConnectorType.Sms],
[SignUpIdentifier.None]: [],
};
export const signInIdentifierToRequiredConnectorMapping: {
[key in SignInIdentifier]: ConnectorType[];
} = {
[SignInIdentifier.Username]: [],
[SignInIdentifier.Email]: [ConnectorType.Email],
[SignInIdentifier.Phone]: [ConnectorType.Sms],
};

View file

@ -20,3 +20,8 @@
margin-top: _.unit(3);
}
}
.signInDescription {
font: var(--font-body-medium);
color: var(--color-text-secondary);
}

View file

@ -4,6 +4,7 @@ import { useFormContext } from 'react-hook-form';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import type { SignInExperienceForm } from '../../types';
import SignInForm from './SignInForm';
import SignUpForm from './SignUpForm';
type Props = {
@ -23,6 +24,7 @@ const SignUpAndSignInTab = ({ defaultData, isDataDirty }: Props) => {
return (
<>
<SignUpForm />
<SignInForm />
<UnsavedChangesAlertModal hasUnsavedChanges={isDataDirty} />
</>
);

View file

@ -53,6 +53,15 @@ const sign_in_exp = {
verify_at_sign_up_option: 'Verify at sign up',
social_only_creation_description: '(This apply to social only account creation)',
},
sign_in: {
title: 'SIGN IN',
sign_in_identifier_and_auth: 'Sign in identifier and authentication',
description:
'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.',
password_auth: 'Password',
verification_code_auth: 'Verification code',
auth_swap_tip: 'Swap to change the priority',
},
},
sign_in_methods: {
title: 'SIGN-IN METHODS',
@ -126,13 +135,11 @@ const sign_in_exp = {
},
setup_warning: {
no_connector: '',
no_connector_phone:
no_connector_sms:
'You havent set up a SMS connector yet. Your sign in experience wont go live until you finish the settings first. ',
no_connector_email:
'You havent set up an Email connector yet. Your sign in experience wont go live until you finish the settings first. ',
no_connector_email_or_phone:
'You havent set up both Email and SMS connectors yet. Your sign in experience wont go live until you finish the settings first. ',
no_connector_none:
no_connector_social:
'You havent set up any social connectors yet. Your sign in experience wont go live until you finish the settings first. ',
no_added_social_connector:
'Youve set up a few social connectors now. Make sure to add some to your sign in experience.',

View file

@ -55,6 +55,15 @@ const sign_in_exp = {
verify_at_sign_up_option: 'Verify at sign up', // UNTRANSLATED
social_only_creation_description: '(This apply to social only account creation)', // UNTRANSLATED
},
sign_in: {
title: 'SIGN IN', // UNTRANSLATED
sign_in_identifier_and_auth: 'Sign in identifier and authentication', // UNTRANSLATED
description:
'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.', // UNTRANSLATED
password_auth: 'Password', // UNTRANSLATED
verification_code_auth: 'Verification code', // UNTRANSLATED
auth_swap_tip: 'Swap to change the priority', // UNTRANSLATED
},
},
sign_in_methods: {
title: 'METHODES DE CONNEXION',
@ -128,13 +137,11 @@ const sign_in_exp = {
},
setup_warning: {
no_connector: '',
no_connector_phone:
no_connector_sms:
"Vous n'avez pas encore configuré de connecteur SMS. Votre expérience de connexion ne sera pas disponible tant que vous n'aurez pas terminé les paramètres. ",
no_connector_email:
"Vous n'avez pas encore configuré de connecteur Email. Votre expérience de connexion ne sera pas disponible tant que vous n'aurez pas terminé les paramètres. ",
no_connector_email_or_phone:
'You havent set up both Email and SMS connectors yet. Your sign in experience wont go live until you finish the settings first. ', // UNTRANSLATED
no_connector_none:
no_connector_social:
"Vous n'avez pas encore configuré de connecteurs sociaux. Votre expérience de connexion ne sera pas disponible tant que vous n'aurez pas terminé les paramètres. ",
no_added_social_connector:
"Vous avez maintenant configuré quelques connecteurs sociaux. Assurez-vous d'en ajouter quelques-uns à votre expérience de connexion.",

View file

@ -50,6 +50,15 @@ const sign_in_exp = {
verify_at_sign_up_option: 'Verify at sign up', // UNTRANSLATED
social_only_creation_description: '(This apply to social only account creation)', // UNTRANSLATED
},
sign_in: {
title: 'SIGN IN', // UNTRANSLATED
sign_in_identifier_and_auth: 'Sign in identifier and authentication', // UNTRANSLATED
description:
'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.', // UNTRANSLATED
password_auth: 'Password', // UNTRANSLATED
verification_code_auth: 'Verification code', // UNTRANSLATED
auth_swap_tip: 'Swap to change the priority', // UNTRANSLATED
},
},
sign_in_methods: {
title: '로그인 방법',
@ -123,13 +132,11 @@ const sign_in_exp = {
},
setup_warning: {
no_connector: '',
no_connector_phone:
no_connector_sms:
'SMS 연동이 아직 설정되지 않았어요. 설정이 완료될 때 까지, 사용자는 이 로그인 방법을 사용할 수 없어요.',
no_connector_email:
'이메일 연동이 아직 설정되지 않았어요. 설정이 완료될 때 까지, 사용자는 이 로그인 방법을 사용할 수 없어요.',
no_connector_email_or_phone:
'You havent set up both Email and SMS connectors yet. Your sign in experience wont go live until you finish the settings first. ', // UNTRANSLATED
no_connector_none:
no_connector_social:
'소셜 연동이 아직 설정되지 않았어요. 설정이 완료될 때 까지, 사용자는 이 로그인 방법을 사용할 수 없어요.',
no_added_social_connector:
'보다 많은 소셜 연동들을 설정하여, 고객에게 보다 나은 경험을 제공해보세요.',

View file

@ -53,6 +53,15 @@ const sign_in_exp = {
verify_at_sign_up_option: 'Verify at sign up', // UNTRANSLATED
social_only_creation_description: '(This apply to social only account creation)', // UNTRANSLATED
},
sign_in: {
title: 'SIGN IN', // UNTRANSLATED
sign_in_identifier_and_auth: 'Sign in identifier and authentication', // UNTRANSLATED
description:
'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.', // UNTRANSLATED
password_auth: 'Password', // UNTRANSLATED
verification_code_auth: 'Verification code', // UNTRANSLATED
auth_swap_tip: 'Swap to change the priority', // UNTRANSLATED
},
},
sign_in_methods: {
title: 'MÉTODOS DE LOGIN',
@ -126,13 +135,11 @@ const sign_in_exp = {
},
setup_warning: {
no_connector: '',
no_connector_phone:
no_connector_sms:
'Ainda não configurou um conector de SMS. A experiência de login não será ativada até que conclua as configurações primeiro. ',
no_connector_email:
'Ainda não configurou um conector de email. A experiência de login não será ativada até que conclua as configurações primeiro. ',
no_connector_email_or_phone:
'You havent set up both Email and SMS connectors yet. Your sign in experience wont go live until you finish the settings first. ', // UNTRANSLATED
no_connector_none:
no_connector_social:
'Ainda não configurou um conector social. A experiência de login não será ativada até que conclua as configurações primeiro. ',
no_added_social_connector:
'Configurou alguns conectores sociais agora. Certifique-se de adicionar alguns a experiência de login.',

View file

@ -54,6 +54,15 @@ const sign_in_exp = {
verify_at_sign_up_option: 'Verify at sign up', // UNTRANSLATED
social_only_creation_description: '(This apply to social only account creation)', // UNTRANSLATED
},
sign_in: {
title: 'SIGN IN', // UNTRANSLATED
sign_in_identifier_and_auth: 'Sign in identifier and authentication', // UNTRANSLATED
description:
'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.', // UNTRANSLATED
password_auth: 'Password', // UNTRANSLATED
verification_code_auth: 'Verification code', // UNTRANSLATED
auth_swap_tip: 'Swap to change the priority', // UNTRANSLATED
},
},
sign_in_methods: {
title: 'OTURUM AÇMA YÖNTEMLERİ',
@ -127,13 +136,11 @@ const sign_in_exp = {
},
setup_warning: {
no_connector: '',
no_connector_phone:
no_connector_sms:
'Henüz bir SMS bağlayıcısı kurmadınız. Öncelikle ayarları tamamlayana kadar oturum açma deneyiminiz yayınlanmayacaktır. ',
no_connector_email:
'Henüz bir e-posta adresi bağlayıcısı kurmadınız. Öncelikle ayarları tamamlayana kadar oturum açma deneyiminiz yayınlanmayacaktır. ',
no_connector_email_or_phone:
'You havent set up both Email and SMS connectors yet. Your sign in experience wont go live until you finish the settings first. ', // UNTRANSLATED
no_connector_none:
no_connector_social:
'Henüz herhangi bir social connector kurmadınız. Öncelikle ayarları tamamlayana kadar oturum açma deneyiminiz yayınlanmayacaktır. ',
no_added_social_connector:
'Şimdi birkaç social connector kurdunuz. Oturum açma deneyiminize bazı şeyler eklediğinizden emin olun.',

View file

@ -51,6 +51,15 @@ const sign_in_exp = {
verify_at_sign_up_option: 'Verify at sign up', // UNTRANSLATED
social_only_creation_description: '(This apply to social only account creation)', // UNTRANSLATED
},
sign_in: {
title: 'SIGN IN', // UNTRANSLATED
sign_in_identifier_and_auth: 'Sign in identifier and authentication', // UNTRANSLATED
description:
'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.', // UNTRANSLATED
password_auth: 'Password', // UNTRANSLATED
verification_code_auth: 'Verification code', // UNTRANSLATED
auth_swap_tip: 'Swap to change the priority', // UNTRANSLATED
},
},
sign_in_methods: {
title: '登录方式',
@ -121,11 +130,9 @@ const sign_in_exp = {
},
setup_warning: {
no_connector: '',
no_connector_phone: '你还没有设置 SMS 连接器。你需完成设置后登录体验才会生效。',
no_connector_sms: '你还没有设置 SMS 连接器。你需完成设置后登录体验才会生效。',
no_connector_email: '你还没有设置 email 连接器。你需完成设置后登录体验才会生效。',
no_connector_email_or_phone:
'你还没有设置 email 和 SMS 连接器。你需完成设置后登录体验才会生效。',
no_connector_none: '你还没有设置社交连接器。你需完成设置后登录体验才会生效。',
no_connector_social: '你还没有设置社交连接器。你需完成设置后登录体验才会生效。',
no_added_social_connector: '你已经成功设置了一些社交连接器。点按「+」添加一些到你的登录体验。',
},
save_alert: {