mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(console): sign up and sign in change alert (#2273)
This commit is contained in:
parent
de870d6def
commit
bf6bd75e0f
20 changed files with 407 additions and 36 deletions
|
@ -36,6 +36,7 @@
|
|||
"@silverhand/ts-config-react": "1.2.1",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/lodash.get": "^4.4.7",
|
||||
"@types/lodash.kebabcase": "^4.1.6",
|
||||
"@types/mdx": "^2.0.1",
|
||||
"@types/mdx-js__react": "^1.5.5",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"cross-env": "^7.0.3",
|
||||
"csstype": "^3.0.11",
|
||||
"dayjs": "^1.10.5",
|
||||
"deep-object-diff": "^1.1.7",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dnd-core": "^16.0.0",
|
||||
"eslint": "^8.21.0",
|
||||
|
@ -56,6 +58,7 @@
|
|||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"ky": "^0.31.0",
|
||||
"lint-staged": "^13.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"nanoid": "^3.1.23",
|
||||
"parcel": "2.7.0",
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
min-width: 552px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font: var(--font-body-medium);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: _.unit(6);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: _.unit(3);
|
||||
border-radius: 8px;
|
||||
padding: _.unit(5);
|
||||
background: var(--color-layer-2);
|
||||
font: var(--font-body-medium);
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
column-gap: _.unit(3);
|
||||
|
||||
.section {
|
||||
&:not(:first-child) {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
flex: 1;
|
||||
background: var(--color-layer-2);
|
||||
border-radius: 8px;
|
||||
padding: _.unit(5);
|
||||
color: var(--color-text);
|
||||
|
||||
.title {
|
||||
font: var(--font-subhead-2);
|
||||
font: var(--font-title-medium);
|
||||
margin: _.unit(1) 0;
|
||||
}
|
||||
|
||||
.connector {
|
||||
margin-left: _.unit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SignUpAndSignInDiffSection from '../tabs/SignUpAndSignInTab/components/SignUpAndSignInDiffSection';
|
||||
import * as styles from './SignInMethodsChangePreview.module.scss';
|
||||
import SignInMethodsPreview from './SignInMethodsPreview';
|
||||
|
||||
type Props = {
|
||||
before: SignInExperience;
|
||||
|
@ -13,16 +13,16 @@ const SignInMethodsChangePreview = ({ before, after }: Props) => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.description}>{t('sign_in_exp.save_alert.description')}</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.section}>
|
||||
<div className={styles.title}>{t('sign_in_exp.save_alert.before')}</div>
|
||||
<SignInMethodsPreview data={before} />
|
||||
<SignUpAndSignInDiffSection before={before} after={after} />
|
||||
</div>
|
||||
<div className={styles.section}>
|
||||
<div className={styles.title}>{t('sign_in_exp.save_alert.after')}</div>
|
||||
<SignInMethodsPreview data={after} />
|
||||
<SignUpAndSignInDiffSection isAfter before={before} after={after} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,7 @@ import BrandingTab from './tabs/BrandingTab';
|
|||
import OthersTab from './tabs/OthersTab';
|
||||
import SignUpAndSignInTab from './tabs/SignUpAndSignInTab';
|
||||
import type { SignInExperienceForm } from './types';
|
||||
import { compareSignInMethods, signInExperienceParser } from './utilities';
|
||||
import { compareSignUpAndSignInConfigs, signInExperienceParser } from './utilities';
|
||||
|
||||
const SignInExperience = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
@ -84,7 +84,7 @@ const SignInExperience = () => {
|
|||
const formatted = signInExperienceParser.toRemoteModel(formData);
|
||||
|
||||
// Sign-in methods changed, need to show confirm modal first.
|
||||
if (!compareSignInMethods(data, formatted)) {
|
||||
if (!compareSignUpAndSignInConfigs(data, formatted)) {
|
||||
setDataToCompare(formatted);
|
||||
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import type { ReactNode } from 'react';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
hasChanged: boolean;
|
||||
isAfter?: boolean;
|
||||
};
|
||||
|
||||
const DiffSegment = ({ children, hasChanged, isAfter = false }: Props) => {
|
||||
if (!hasChanged) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return <span className={isAfter ? styles.green : styles.red}>{children}</span>;
|
||||
};
|
||||
|
||||
export default DiffSegment;
|
|
@ -0,0 +1,88 @@
|
|||
import type { SignInIdentifier } from '@logto/schemas';
|
||||
import { detailedDiff } from 'deep-object-diff';
|
||||
import get from 'lodash.get';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { SignInMethod } from '../SignInMethodEditBox/types';
|
||||
import DiffSegment from './DiffSegment';
|
||||
import * as styles from './index.module.scss';
|
||||
import type { SignInMethodsObject } from './types';
|
||||
import { convertToSignInMethodsObject } from './utilities';
|
||||
|
||||
type Props = {
|
||||
before: SignInMethod[];
|
||||
after: SignInMethod[];
|
||||
isAfter?: boolean;
|
||||
};
|
||||
|
||||
const SignInDiffSection = ({ before, after, isAfter = false }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const beforeSignInMethodsObject = convertToSignInMethodsObject(before);
|
||||
const afterSignInMethodsObject = convertToSignInMethodsObject(after);
|
||||
|
||||
const signInDiff = isAfter
|
||||
? detailedDiff(beforeSignInMethodsObject, afterSignInMethodsObject)
|
||||
: detailedDiff(afterSignInMethodsObject, beforeSignInMethodsObject);
|
||||
|
||||
const displaySignInMethodsObject = isAfter ? afterSignInMethodsObject : beforeSignInMethodsObject;
|
||||
|
||||
const hasIdentifierChanged = (identifierKey: SignInIdentifier) =>
|
||||
get(signInDiff, `added.${identifierKey.toLocaleLowerCase()}`) !== undefined;
|
||||
|
||||
const hasAuthenticationChanged = (
|
||||
identifierKey: SignInIdentifier,
|
||||
authenticationKey: keyof SignInMethodsObject[SignInIdentifier]
|
||||
) =>
|
||||
get(signInDiff, `updated.${identifierKey.toLocaleLowerCase()}.${authenticationKey}`) !==
|
||||
undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.title}>{t('sign_in_exp.save_alert.sign_in')}</div>
|
||||
<ul className={styles.list}>
|
||||
{
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
(Object.keys(displaySignInMethodsObject).slice().sort() as SignInIdentifier[]).map(
|
||||
(identifierKey) => {
|
||||
const { password, verificationCode } = displaySignInMethodsObject[identifierKey];
|
||||
const hasAuthentication = password || verificationCode;
|
||||
const needDisjunction = password && verificationCode;
|
||||
|
||||
return (
|
||||
<li key={identifierKey}>
|
||||
<DiffSegment hasChanged={hasIdentifierChanged(identifierKey)} isAfter={isAfter}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.identifiers', {
|
||||
context: identifierKey.toLocaleLowerCase(),
|
||||
})}
|
||||
{hasAuthentication && ' ('}
|
||||
{password && (
|
||||
<DiffSegment
|
||||
hasChanged={hasAuthenticationChanged(identifierKey, 'password')}
|
||||
isAfter={isAfter}
|
||||
>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_in.password_auth')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{needDisjunction && ` ${String(t('sign_in_exp.sign_up_and_sign_in.or'))} `}
|
||||
{verificationCode && (
|
||||
<DiffSegment
|
||||
hasChanged={hasAuthenticationChanged(identifierKey, 'verificationCode')}
|
||||
isAfter={isAfter}
|
||||
>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_in.verification_code_auth')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{hasAuthentication && ')'}
|
||||
</DiffSegment>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInDiffSection;
|
|
@ -0,0 +1,54 @@
|
|||
import type { SignUp } from '@logto/schemas';
|
||||
import { diff } from 'deep-object-diff';
|
||||
import get from 'lodash.get';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import DiffSegment from './DiffSegment';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
before: SignUp;
|
||||
after: SignUp;
|
||||
isAfter?: boolean;
|
||||
};
|
||||
|
||||
const SignUpDiffSection = ({ before, after, isAfter = false }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const signUpDiff = isAfter ? diff(before, after) : diff(after, before);
|
||||
const signUp = isAfter ? after : before;
|
||||
const hasChanged = (path: keyof SignUp) => get(signUpDiff, path) !== undefined;
|
||||
|
||||
const { identifier, password, verify } = signUp;
|
||||
const hasAuthentication = password || verify;
|
||||
const needConjunction = password && verify;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.title}>{t('sign_in_exp.save_alert.sign_up')}</div>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<DiffSegment hasChanged={hasChanged('identifier')} isAfter={isAfter}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.identifiers', {
|
||||
context: identifier.toLowerCase(),
|
||||
})}
|
||||
</DiffSegment>
|
||||
{hasAuthentication && ' ('}
|
||||
{password && (
|
||||
<DiffSegment hasChanged={hasChanged('password')} isAfter={isAfter}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_up.set_a_password_option')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{needConjunction && ` ${String(t('sign_in_exp.sign_up_and_sign_in.and'))} `}
|
||||
{verify && (
|
||||
<DiffSegment hasChanged={hasChanged('verify')} isAfter={isAfter}>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_up.verify_at_sign_up_option')}
|
||||
</DiffSegment>
|
||||
)}
|
||||
{hasAuthentication && ')'}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpDiffSection;
|
|
@ -0,0 +1,63 @@
|
|||
import { isLanguageTag } from '@logto/language-kit';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import i18next from 'i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import useConnectorGroups from '@/hooks/use-connector-groups';
|
||||
|
||||
import DiffSegment from './DiffSegment';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
before: string[];
|
||||
after: string[];
|
||||
isAfter?: boolean;
|
||||
};
|
||||
|
||||
const SocialTargetsDiffSection = ({ before, after, isAfter = false }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { data: groups, error } = useConnectorGroups();
|
||||
const { language } = i18next;
|
||||
const sortedBeforeTargets = before.slice().sort();
|
||||
const sortedAfterTargets = after.slice().sort();
|
||||
|
||||
const displayTargets = isAfter ? sortedAfterTargets : sortedBeforeTargets;
|
||||
|
||||
const hasChanged = (target: string) => !(before.includes(target) && after.includes(target));
|
||||
|
||||
if (!groups) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.title}>{t('sign_in_exp.save_alert.social')}</div>
|
||||
<ul className={styles.list}>
|
||||
{displayTargets.map((target) => {
|
||||
const connectorDetail = groups.find(
|
||||
({ target: connectorTarget }) => connectorTarget === target
|
||||
);
|
||||
|
||||
if (!connectorDetail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={target}>
|
||||
<DiffSegment hasChanged={hasChanged(target)} isAfter={isAfter}>
|
||||
{conditional(isLanguageTag(language) && connectorDetail.name[language]) ??
|
||||
connectorDetail.name.en}
|
||||
</DiffSegment>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialTargetsDiffSection;
|
|
@ -0,0 +1,17 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.title {
|
||||
font: var(--font-title-small);
|
||||
}
|
||||
|
||||
.list {
|
||||
padding-left: _.unit(6);
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: rgba(221, 55, 48, 30%);
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: rgb(104, 190, 108, 40%);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
|
||||
import SignInDiffSection from './SignInDiffSection';
|
||||
import SignUpDiffSection from './SignUpDiffSection';
|
||||
import SocialTargetsDiffSection from './SocialTargetsDiffSection';
|
||||
import { isSignInMethodsDifferent, isSignUpDifferent, isSocialTargetsDifferent } from './utilities';
|
||||
|
||||
type Props = {
|
||||
before: SignInExperience;
|
||||
after: SignInExperience;
|
||||
isAfter?: boolean;
|
||||
};
|
||||
|
||||
const SignUpAndSignInDiffSection = ({ before, after, isAfter = false }: Props) => {
|
||||
const showSignUpDiff = isSignUpDifferent(before.signUp, after.signUp);
|
||||
const showSignInDiff = isSignInMethodsDifferent(before.signIn.methods, after.signIn.methods);
|
||||
const showSocialDiff = isSocialTargetsDifferent(
|
||||
before.socialSignInConnectorTargets,
|
||||
after.socialSignInConnectorTargets
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showSignUpDiff && (
|
||||
<SignUpDiffSection before={before.signUp} after={after.signUp} isAfter={isAfter} />
|
||||
)}
|
||||
{showSignInDiff && (
|
||||
<SignInDiffSection
|
||||
before={before.signIn.methods}
|
||||
after={after.signIn.methods}
|
||||
isAfter={isAfter}
|
||||
/>
|
||||
)}
|
||||
{showSocialDiff && (
|
||||
<SocialTargetsDiffSection
|
||||
before={before.socialSignInConnectorTargets}
|
||||
after={after.socialSignInConnectorTargets}
|
||||
isAfter={isAfter}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpAndSignInDiffSection;
|
|
@ -0,0 +1,6 @@
|
|||
import type { SignInIdentifier } from '@logto/schemas';
|
||||
|
||||
export type SignInMethodsObject = Record<
|
||||
SignInIdentifier,
|
||||
{ password: boolean; verificationCode: boolean }
|
||||
>;
|
|
@ -0,0 +1,27 @@
|
|||
import type { SignInExperience } from '@logto/schemas';
|
||||
import { diff } from 'deep-object-diff';
|
||||
|
||||
import type { SignInMethod } from '../SignInMethodEditBox/types';
|
||||
import type { SignInMethodsObject } from './types';
|
||||
|
||||
export const isSignUpDifferent = (
|
||||
before: SignInExperience['signUp'],
|
||||
after: SignInExperience['signUp']
|
||||
) => Object.keys(diff(before, after)).length > 0;
|
||||
|
||||
export const convertToSignInMethodsObject = (signInMethods: SignInMethod[]): SignInMethodsObject =>
|
||||
signInMethods.reduce<SignInMethodsObject>(
|
||||
(methodsObject, { identifier, password, verificationCode }) => ({
|
||||
...methodsObject,
|
||||
[identifier]: { password, verificationCode },
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, no-restricted-syntax
|
||||
{} as SignInMethodsObject
|
||||
);
|
||||
|
||||
export const isSignInMethodsDifferent = (before: SignInMethod[], after: SignInMethod[]) =>
|
||||
Object.keys(diff(convertToSignInMethodsObject(before), convertToSignInMethodsObject(after)))
|
||||
.length > 0;
|
||||
|
||||
export const isSocialTargetsDifferent = (before: string[], after: string[]) =>
|
||||
Object.keys(diff(before.slice().sort(), after.slice().sort())).length > 0;
|
|
@ -3,6 +3,11 @@ import type { SignInExperience, SignInMethods, Translation } from '@logto/schema
|
|||
import { SignUpIdentifier, SignInMethodKey, SignInMethodState, SignInMode } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
||||
import {
|
||||
isSignInMethodsDifferent,
|
||||
isSignUpDifferent,
|
||||
isSocialTargetsDifferent,
|
||||
} from './tabs/SignUpAndSignInTab/components/SignUpAndSignInDiffSection/utilities';
|
||||
import type { SignInExperienceForm } from './types';
|
||||
|
||||
const findMethodState = (
|
||||
|
@ -78,26 +83,18 @@ export const signInExperienceParser = {
|
|||
},
|
||||
};
|
||||
|
||||
export const compareSignInMethods = (
|
||||
export const compareSignUpAndSignInConfigs = (
|
||||
before: SignInExperience,
|
||||
after: SignInExperience
|
||||
): boolean => {
|
||||
if (before.socialSignInConnectorTargets.length !== after.socialSignInConnectorTargets.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
before.socialSignInConnectorTargets.some(
|
||||
(target) => !after.socialSignInConnectorTargets.includes(target)
|
||||
return (
|
||||
!isSignUpDifferent(before.signUp, after.signUp) &&
|
||||
!isSignInMethodsDifferent(before.signIn.methods, after.signIn.methods) &&
|
||||
!isSocialTargetsDifferent(
|
||||
before.socialSignInConnectorTargets,
|
||||
after.socialSignInConnectorTargets
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { signInMethods: beforeMethods } = before;
|
||||
const { signInMethods: afterMethods } = after;
|
||||
|
||||
return Object.values(SignInMethodKey).every((key) => beforeMethods[key] === afterMethods[key]);
|
||||
);
|
||||
};
|
||||
|
||||
export const flattenTranslation = (
|
||||
|
|
|
@ -45,6 +45,8 @@ const sign_in_exp = {
|
|||
identifiers_username: 'Username',
|
||||
identifiers_email_or_sms: 'Email address or phone number',
|
||||
identifiers_none: 'None',
|
||||
and: 'and',
|
||||
or: 'or',
|
||||
sign_up: {
|
||||
title: 'SIGN UP',
|
||||
sign_up_identifier: 'Sign up identifier',
|
||||
|
@ -162,6 +164,9 @@ const sign_in_exp = {
|
|||
'You are changing sign-in methods. This will impact some of your users. Are you sure you want to do that?',
|
||||
before: 'Before',
|
||||
after: 'After',
|
||||
sign_up: 'Sign up',
|
||||
sign_in: 'Sign in',
|
||||
social: 'Social',
|
||||
},
|
||||
preview: {
|
||||
title: 'Sign-in preview',
|
||||
|
|
|
@ -47,6 +47,8 @@ const sign_in_exp = {
|
|||
identifiers_username: 'Username', // UNTRANSLATED
|
||||
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
|
||||
identifiers_none: 'None', // UNTRANSLATED
|
||||
and: 'and', // UNTRANSLATED
|
||||
or: 'or', // UNTRANSLATED
|
||||
sign_up: {
|
||||
title: 'SIGN UP', // UNTRANSLATED
|
||||
sign_up_identifier: 'Sign up identifier', // UNTRANSLATED
|
||||
|
@ -164,6 +166,9 @@ const sign_in_exp = {
|
|||
'Vous changez de méthode de connexion. Cela aura un impact sur certains de vos utilisateurs. Êtes-vous sûr de vouloir faire cela ?',
|
||||
before: 'Avant',
|
||||
after: 'Après',
|
||||
sign_up: 'Sign up', // UNTRANSLATED
|
||||
sign_in: 'Sign in', // UNTRANSLATED
|
||||
social: 'Social', // UNTRANSLATED
|
||||
},
|
||||
preview: {
|
||||
title: "Aperçu de l'expérience de connexion",
|
||||
|
|
|
@ -42,6 +42,8 @@ const sign_in_exp = {
|
|||
identifiers_username: 'Username', // UNTRANSLATED
|
||||
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
|
||||
identifiers_none: 'None', // UNTRANSLATED
|
||||
and: 'and', // UNTRANSLATED
|
||||
or: 'or', // UNTRANSLATED
|
||||
sign_up: {
|
||||
title: 'SIGN UP', // UNTRANSLATED
|
||||
sign_up_identifier: 'Sign up identifier', // UNTRANSLATED
|
||||
|
@ -159,6 +161,9 @@ const sign_in_exp = {
|
|||
'로그인 방법이 수정되었어요. 일부 사용자에게 영향을 미칠 수 있어요. 정말로 진행할까요?',
|
||||
before: '이전',
|
||||
after: '이후',
|
||||
sign_up: 'Sign up', // UNTRANSLATED
|
||||
sign_in: 'Sign in', // UNTRANSLATED
|
||||
social: 'Social', // UNTRANSLATED
|
||||
},
|
||||
preview: {
|
||||
title: '로그인 화면 미리보기',
|
||||
|
|
|
@ -45,6 +45,8 @@ const sign_in_exp = {
|
|||
identifiers_username: 'Username', // UNTRANSLATED
|
||||
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
|
||||
identifiers_none: 'None', // UNTRANSLATED
|
||||
and: 'and', // UNTRANSLATED
|
||||
or: 'or', // UNTRANSLATED
|
||||
sign_up: {
|
||||
title: 'SIGN UP', // UNTRANSLATED
|
||||
sign_up_identifier: 'Sign up identifier', // UNTRANSLATED
|
||||
|
@ -162,6 +164,9 @@ const sign_in_exp = {
|
|||
'Está alterando os métodos de login. Isso afetará alguns dos seus utilizadoress. Tem a certeza que deseja fazer isso?',
|
||||
before: 'Antes',
|
||||
after: 'Depois',
|
||||
sign_up: 'Sign up', // UNTRANSLATED
|
||||
sign_in: 'Sign in', // UNTRANSLATED
|
||||
social: 'Social', // UNTRANSLATED
|
||||
},
|
||||
preview: {
|
||||
title: 'Pre-visualização do login',
|
||||
|
|
|
@ -46,6 +46,8 @@ const sign_in_exp = {
|
|||
identifiers_username: 'Username', // UNTRANSLATED
|
||||
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
|
||||
identifiers_none: 'None', // UNTRANSLATED
|
||||
and: 'and', // UNTRANSLATED
|
||||
or: 'or', // UNTRANSLATED
|
||||
sign_up: {
|
||||
title: 'SIGN UP', // UNTRANSLATED
|
||||
sign_up_identifier: 'Sign up identifier', // UNTRANSLATED
|
||||
|
@ -163,6 +165,9 @@ const sign_in_exp = {
|
|||
'Oturum açma yöntemlerini değiştiriyorsunuz. Bu, bazı kullanıcılarınızı etkileyecektir. Bunu yapmak istediğine emin misin?',
|
||||
before: 'Önce',
|
||||
after: 'Sonra',
|
||||
sign_up: 'Sign up', // UNTRANSLATED
|
||||
sign_in: 'Sign in', // UNTRANSLATED
|
||||
social: 'Social', // UNTRANSLATED
|
||||
},
|
||||
preview: {
|
||||
title: 'Oturum Açma Önizlemesi',
|
||||
|
|
|
@ -43,6 +43,8 @@ const sign_in_exp = {
|
|||
identifiers_username: 'Username', // UNTRANSLATED
|
||||
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
|
||||
identifiers_none: 'None', // UNTRANSLATED
|
||||
and: 'and', // UNTRANSLATED
|
||||
or: 'or', // UNTRANSLATED
|
||||
sign_up: {
|
||||
title: 'SIGN UP', // UNTRANSLATED
|
||||
sign_up_identifier: 'Sign up identifier', // UNTRANSLATED
|
||||
|
@ -152,6 +154,9 @@ const sign_in_exp = {
|
|||
description: '你正在修改登录方式,这可能会影响部分用户。是否继续保存修改?',
|
||||
before: '修改前',
|
||||
after: '修改后',
|
||||
sign_up: 'Sign up', // UNTRANSLATED
|
||||
sign_in: 'Sign in', // UNTRANSLATED
|
||||
social: 'Social', // UNTRANSLATED
|
||||
},
|
||||
preview: {
|
||||
title: '登录预览',
|
||||
|
|
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
|
@ -123,6 +123,7 @@ importers:
|
|||
'@silverhand/ts-config-react': 1.2.1
|
||||
'@tsconfig/docusaurus': ^1.0.5
|
||||
'@types/color': ^3.0.3
|
||||
'@types/lodash.get': ^4.4.7
|
||||
'@types/lodash.kebabcase': ^4.1.6
|
||||
'@types/mdx': ^2.0.1
|
||||
'@types/mdx-js__react': ^1.5.5
|
||||
|
@ -135,6 +136,7 @@ importers:
|
|||
cross-env: ^7.0.3
|
||||
csstype: ^3.0.11
|
||||
dayjs: ^1.10.5
|
||||
deep-object-diff: ^1.1.7
|
||||
deepmerge: ^4.2.2
|
||||
dnd-core: ^16.0.0
|
||||
eslint: ^8.21.0
|
||||
|
@ -143,6 +145,7 @@ importers:
|
|||
i18next-browser-languagedetector: ^6.1.4
|
||||
ky: ^0.31.0
|
||||
lint-staged: ^13.0.0
|
||||
lodash.get: ^4.4.2
|
||||
lodash.kebabcase: ^4.1.1
|
||||
nanoid: ^3.1.23
|
||||
parcel: 2.7.0
|
||||
|
@ -191,6 +194,7 @@ importers:
|
|||
'@silverhand/ts-config-react': 1.2.1_typescript@4.7.4
|
||||
'@tsconfig/docusaurus': 1.0.5
|
||||
'@types/color': 3.0.3
|
||||
'@types/lodash.get': 4.4.7
|
||||
'@types/lodash.kebabcase': 4.1.6
|
||||
'@types/mdx': 2.0.1
|
||||
'@types/mdx-js__react': 1.5.5
|
||||
|
@ -203,6 +207,7 @@ importers:
|
|||
cross-env: 7.0.3
|
||||
csstype: 3.0.11
|
||||
dayjs: 1.10.7
|
||||
deep-object-diff: 1.1.7
|
||||
deepmerge: 4.2.2
|
||||
dnd-core: 16.0.0
|
||||
eslint: 8.21.0
|
||||
|
@ -211,6 +216,7 @@ importers:
|
|||
i18next-browser-languagedetector: 6.1.4
|
||||
ky: 0.31.0
|
||||
lint-staged: 13.0.0
|
||||
lodash.get: 4.4.2
|
||||
lodash.kebabcase: 4.1.1
|
||||
nanoid: 3.3.1
|
||||
parcel: 2.7.0_postcss@8.4.6
|
||||
|
@ -4055,6 +4061,12 @@ packages:
|
|||
'@types/node': 17.0.23
|
||||
dev: true
|
||||
|
||||
/@types/lodash.get/4.4.7:
|
||||
resolution: {integrity: sha512-af34Mj+KdDeuzsJBxc/XeTtOx0SZHZNLd+hdrn+PcKGQs0EG2TJTzQAOTCZTgDJCArahlCzLWSy8c2w59JRz7Q==}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.178
|
||||
dev: true
|
||||
|
||||
/@types/lodash.kebabcase/4.1.6:
|
||||
resolution: {integrity: sha512-+RAD9pCAa8kuVyCYTeDNiwBXwD/0u0p+hos3NSqD+tXTjJextbfF3farfYB+ssAKgEssoewXEtBsfwBpsI7gsA==}
|
||||
dependencies:
|
||||
|
@ -5843,6 +5855,10 @@ packages:
|
|||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
|
||||
/deep-object-diff/1.1.7:
|
||||
resolution: {integrity: sha512-QkgBca0mL08P6HiOjoqvmm6xOAl2W6CT2+34Ljhg0OeFan8cwlcdq8jrLKsBBuUFAZLsN5b6y491KdKEoSo9lg==}
|
||||
dev: true
|
||||
|
||||
/deepmerge/4.2.2:
|
||||
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -5950,8 +5966,8 @@ packages:
|
|||
engines: {node: '>=0.3.1'}
|
||||
dev: true
|
||||
|
||||
/diff/5.0.0:
|
||||
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
|
||||
/diff/5.1.0:
|
||||
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: true
|
||||
|
||||
|
@ -13945,7 +13961,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
dequal: 2.0.2
|
||||
diff: 5.0.0
|
||||
diff: 5.1.0
|
||||
kleur: 4.1.4
|
||||
sade: 1.8.1
|
||||
dev: true
|
||||
|
|
Loading…
Add table
Reference in a new issue