0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-17 22:31:28 -05:00

refactor(core,ui,schemas): replace sms sign-in method with phone (#2825)

This commit is contained in:
simeng-li 2023-01-06 17:43:28 +08:00 committed by GitHub
parent 27c0cade79
commit 4af325af03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
101 changed files with 468 additions and 270 deletions

View file

@ -21,7 +21,7 @@ const useConnectorInUse = () => {
}
const relatedIdentifier =
type === ConnectorType.Email ? SignInIdentifier.Email : SignInIdentifier.Sms;
type === ConnectorType.Email ? SignInIdentifier.Email : SignInIdentifier.Phone;
const usedInSignUp =
data.signUp.identifiers.includes(relatedIdentifier) && data.signUp.verify;

View file

@ -9,8 +9,8 @@ export const signInIdentifiers = Object.values(SignInIdentifier);
export const signUpIdentifiersMapping: { [key in SignUpIdentifier]: SignInIdentifier[] } = {
[SignUpIdentifier.Username]: [SignInIdentifier.Username],
[SignUpIdentifier.Email]: [SignInIdentifier.Email],
[SignUpIdentifier.Sms]: [SignInIdentifier.Sms],
[SignUpIdentifier.EmailOrSms]: [SignInIdentifier.Email, SignInIdentifier.Sms],
[SignUpIdentifier.Sms]: [SignInIdentifier.Phone],
[SignUpIdentifier.EmailOrSms]: [SignInIdentifier.Email, SignInIdentifier.Phone],
[SignUpIdentifier.None]: [],
};
@ -18,5 +18,5 @@ export const identifierRequiredConnectorMapping: {
[key in SignInIdentifier]?: ConnectorType;
} = {
[SignInIdentifier.Email]: ConnectorType.Email,
[SignInIdentifier.Sms]: ConnectorType.Sms,
[SignInIdentifier.Phone]: ConnectorType.Sms,
};

View file

@ -8,7 +8,9 @@ import type { SignUpIdentifier } from '../types';
export const isVerificationRequiredSignUpIdentifiers = (signUpIdentifier: SignUpIdentifier) => {
const identifiers = signUpIdentifiersMapping[signUpIdentifier];
return identifiers.includes(SignInIdentifier.Email) || identifiers.includes(SignInIdentifier.Sms);
return (
identifiers.includes(SignInIdentifier.Email) || identifiers.includes(SignInIdentifier.Phone)
);
};
export const mapIdentifiersToSignUpIdentifier = (

View file

@ -81,7 +81,7 @@ export const mockSignInExperience: SignInExperience = {
isPasswordPrimary: true,
},
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: true,
isPasswordPrimary: true,

View file

@ -26,14 +26,14 @@ describe('validate sign-in', () => {
},
{
...mockSignInMethod,
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
verificationCode: true,
},
],
},
{
...mockSignUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: false,
verify: true,
},
@ -96,7 +96,7 @@ describe('validate sign-in', () => {
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
verificationCode: true,
},
],
@ -121,7 +121,7 @@ describe('validate sign-in', () => {
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
},
],
},
@ -175,7 +175,7 @@ describe('validate sign-in', () => {
},
{
...mockSignUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
},
enabledConnectors
);
@ -199,7 +199,7 @@ describe('validate sign-in', () => {
},
{
...mockSignUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
},
enabledConnectors
);
@ -278,7 +278,7 @@ describe('validate sign-in', () => {
},
{
...mockSignInMethod,
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
verificationCode: true,
password: false,
},
@ -286,7 +286,7 @@ describe('validate sign-in', () => {
},
{
...mockSignUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: false,
verify: true,
},

View file

@ -27,7 +27,8 @@ export const validateSignIn = (
if (
signIn.methods.some(
({ identifier, verificationCode }) => verificationCode && identifier === SignInIdentifier.Sms
({ identifier, verificationCode }) =>
verificationCode && identifier === SignInIdentifier.Phone
)
) {
assertThat(
@ -65,9 +66,9 @@ export const validateSignIn = (
);
}
if (identifier === SignInIdentifier.Sms) {
if (identifier === SignInIdentifier.Phone) {
assertThat(
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Sms),
signIn.methods.some(({ identifier }) => identifier === SignInIdentifier.Phone),
new RequestError({
code: 'sign_in_experiences.miss_sign_up_identifier_in_sign_in',
})

View file

@ -33,7 +33,7 @@ describe('validate sign-up', () => {
validateSignUp(
{
...mockSignUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
},
[]
);
@ -47,7 +47,7 @@ describe('validate sign-up', () => {
test('should throw when there is no sms connector and identifier is phone', async () => {
expect(() => {
validateSignUp({ ...mockSignUp, identifiers: [SignInIdentifier.Sms] }, []);
validateSignUp({ ...mockSignUp, identifiers: [SignInIdentifier.Phone] }, []);
}).toMatchError(
new RequestError({
code: 'sign_in_experiences.enabled_connector_not_found',
@ -62,7 +62,7 @@ describe('validate sign-up', () => {
{
...mockSignUp,
verify: true,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
},
[mockAliyunSmsConnector]
);
@ -120,7 +120,7 @@ describe('validate sign-up', () => {
validateSignUp(
{
...mockSignUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
verify: false,
},
enabledConnectors

View file

@ -17,7 +17,7 @@ export const validateSignUp = (signUp: SignUp, enabledConnectors: LogtoConnector
);
}
if (identifier === SignInIdentifier.Sms) {
if (identifier === SignInIdentifier.Phone) {
assertThat(
enabledConnectors.some((item) => item.type === ConnectorType.Sms),
new RequestError({
@ -36,7 +36,7 @@ export const validateSignUp = (signUp: SignUp, enabledConnectors: LogtoConnector
);
}
if (identifier === SignInIdentifier.Email || identifier === SignInIdentifier.Sms) {
if (identifier === SignInIdentifier.Email || identifier === SignInIdentifier.Phone) {
assertThat(
signUp.verify,
new RequestError({

View file

@ -188,7 +188,7 @@ describe('identifier validation', () => {
...mockSignInExperience,
signIn: {
methods: mockSignInExperience.signIn.methods.filter(
({ identifier }) => identifier !== SignInIdentifier.Sms
({ identifier }) => identifier !== SignInIdentifier.Phone
),
},
});
@ -200,7 +200,7 @@ describe('identifier validation', () => {
signIn: {
methods: [
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: false,
verificationCode: true,
isPasswordPrimary: true,
@ -223,7 +223,7 @@ describe('identifier validation', () => {
...mockSignInExperience,
signIn: {
methods: mockSignInExperience.signIn.methods.filter(
({ identifier }) => identifier !== SignInIdentifier.Sms
({ identifier }) => identifier !== SignInIdentifier.Phone
),
},
});
@ -235,7 +235,7 @@ describe('identifier validation', () => {
signIn: {
methods: [
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: false,
isPasswordPrimary: true,
@ -249,14 +249,14 @@ describe('identifier validation', () => {
verifyIdentifierSettings(identifier, {
...mockSignInExperience,
signUp: {
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: false,
verify: true,
},
signIn: {
methods: [
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: false,
isPasswordPrimary: true,
@ -297,7 +297,7 @@ describe('profile validation', () => {
{ phone: '123456' },
{
...mockSignInExperience,
signUp: { identifiers: [SignInIdentifier.Sms], password: false, verify: true },
signUp: { identifiers: [SignInIdentifier.Phone], password: false, verify: true },
}
);
}).not.toThrow();
@ -308,7 +308,7 @@ describe('profile validation', () => {
{
...mockSignInExperience,
signUp: {
identifiers: [SignInIdentifier.Sms, SignInIdentifier.Email],
identifiers: [SignInIdentifier.Phone, SignInIdentifier.Email],
password: false,
verify: true,
},
@ -322,7 +322,7 @@ describe('profile validation', () => {
{
...mockSignInExperience,
signUp: {
identifiers: [SignInIdentifier.Sms, SignInIdentifier.Email],
identifiers: [SignInIdentifier.Phone, SignInIdentifier.Email],
password: false,
verify: true,
},
@ -336,7 +336,7 @@ describe('profile validation', () => {
{
...mockSignInExperience,
signUp: {
identifiers: [SignInIdentifier.Sms, SignInIdentifier.Email],
identifiers: [SignInIdentifier.Phone, SignInIdentifier.Email],
password: false,
verify: true,
},

View file

@ -77,7 +77,7 @@ export const verifyIdentifierSettings = (
if ('phone' in identifier) {
assertThat(
signIn.methods.some(({ identifier: method, password, verificationCode }) => {
if (method !== SignInIdentifier.Sms) {
if (method !== SignInIdentifier.Phone) {
return false;
}
@ -90,7 +90,7 @@ export const verifyIdentifierSettings = (
if (
'passcode' in identifier &&
!verificationCode &&
!signUp.identifiers.includes(SignInIdentifier.Sms) &&
!signUp.identifiers.includes(SignInIdentifier.Phone) &&
!signUp.verify
) {
return false;
@ -107,7 +107,7 @@ export const verifyIdentifierSettings = (
export const verifyProfileSettings = (profile: Profile, { signUp }: SignInExperience) => {
if (profile.phone) {
assertThat(signUp.identifiers.includes(SignInIdentifier.Sms), forbiddenIdentifierError);
assertThat(signUp.identifiers.includes(SignInIdentifier.Phone), forbiddenIdentifierError);
}
if (profile.email) {

View file

@ -114,7 +114,7 @@ describe('validateMandatoryUserProfile', () => {
...baseCtx,
signInExperience: {
...mockSignInExperience,
signUp: { identifiers: [SignInIdentifier.Sms], password: false, verify: true },
signUp: { identifiers: [SignInIdentifier.Phone], password: false, verify: true },
},
};
@ -135,7 +135,7 @@ describe('validateMandatoryUserProfile', () => {
...baseCtx,
signInExperience: {
...mockSignInExperience,
signUp: { identifiers: [SignInIdentifier.Sms], password: false, verify: true },
signUp: { identifiers: [SignInIdentifier.Phone], password: false, verify: true },
},
};
@ -148,7 +148,7 @@ describe('validateMandatoryUserProfile', () => {
signInExperience: {
...mockSignInExperience,
signUp: {
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: false,
verify: true,
},

View file

@ -49,7 +49,7 @@ const getMissingProfileBySignUpIdentifiers = ({
// Email or phone
if (
signUpIdentifiersSet.has(SignInIdentifier.Email) &&
signUpIdentifiersSet.has(SignInIdentifier.Sms)
signUpIdentifiersSet.has(SignInIdentifier.Phone)
) {
if (!user?.primaryPhone && !user?.primaryEmail && !profile?.phone && !profile?.email) {
missingProfile.add(MissingProfile.emailOrPhone);
@ -66,7 +66,7 @@ const getMissingProfileBySignUpIdentifiers = ({
}
// Phone only
if (signUpIdentifiersSet.has(SignInIdentifier.Sms) && !user?.primaryPhone && !profile?.phone) {
if (signUpIdentifiersSet.has(SignInIdentifier.Phone) && !user?.primaryPhone && !profile?.phone) {
missingProfile.add(MissingProfile.phone);
return missingProfile;

View file

@ -38,7 +38,7 @@ export const smsSignInAction = <StateT, ContextT extends WithLogContextLegacy, R
assertThat(
signInExperience.signIn.methods.some(
({ identifier, verificationCode }) =>
identifier === SignInIdentifier.Sms && verificationCode
identifier === SignInIdentifier.Phone && verificationCode
),
new RequestError({
code: 'user.sign_in_method_not_enabled',
@ -127,7 +127,7 @@ export const smsRegisterAction = <StateT, ContextT extends WithLogContextLegacy,
);
assertThat(
signInExperience.signUp.identifiers.includes(SignInIdentifier.Sms),
signInExperience.signUp.identifiers.includes(SignInIdentifier.Phone),
new RequestError({
code: 'user.sign_up_method_not_enabled',
status: 422,

View file

@ -88,7 +88,7 @@ export default function passwordRoutes<T extends AnonymousRouterLegacy>(
const { phone, password } = ctx.guard.body;
const type = 'SignInSmsPassword';
await signInWithPassword(ctx, provider, {
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password,
logType: type,
logPayload: { phone },

View file

@ -711,7 +711,7 @@ describe('session -> passwordlessRoutes', () => {
...mockSignInExperience,
signUp: {
...mockSignInExperience.signUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: false,
},
});
@ -952,7 +952,7 @@ describe('session -> passwordlessRoutes', () => {
...mockSignInExperience,
signUp: {
...mockSignInExperience.signUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
},
});

View file

@ -216,7 +216,7 @@ describe('checkRequiredProfile', () => {
...mockSignInExperience,
signUp: {
...mockSignInExperience.signUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: true,
verify: true,
},
@ -245,7 +245,7 @@ describe('checkRequiredProfile', () => {
...mockSignInExperience,
signUp: {
...mockSignInExperience.signUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: true,
verify: true,
},
@ -275,7 +275,7 @@ describe('checkRequiredProfile', () => {
...mockSignInExperience,
signUp: {
...mockSignInExperience.signUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: true,
verify: true,
},
@ -357,7 +357,7 @@ describe('signInWithPassword()', () => {
methods: [
{
...mockSignInMethod,
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: false,
},
],

View file

@ -190,13 +190,13 @@ export const checkRequiredProfile = async (
throw new RequestError({ code: 'user.email_required_in_profile', status: 422 });
}
if (isSameArray(signUp.identifiers, [SignInIdentifier.Sms]) && !primaryPhone) {
if (isSameArray(signUp.identifiers, [SignInIdentifier.Phone]) && !primaryPhone) {
await assignContinueSignInResult(ctx, provider, { userId: id });
throw new RequestError({ code: 'user.phone_required_in_profile', status: 422 });
}
if (
isSameArray(signUp.identifiers, [SignInIdentifier.Email, SignInIdentifier.Sms]) &&
isSameArray(signUp.identifiers, [SignInIdentifier.Email, SignInIdentifier.Phone]) &&
!primaryEmail &&
!primaryPhone
) {

View file

@ -22,7 +22,7 @@ export default function wellKnownRoutes<T extends AnonymousRouter>(router: T, pr
]);
const forgotPassword = {
sms: logtoConnectors.some(({ type }) => type === ConnectorType.Sms),
phone: logtoConnectors.some(({ type }) => type === ConnectorType.Sms),
email: logtoConnectors.some(({ type }) => type === ConnectorType.Email),
};

View file

@ -11,7 +11,7 @@ export const adminConsoleRedirectUri = `${logtoUrl}/console/callback`;
export const signUpIdentifiers = {
username: [SignInIdentifier.Username],
email: [SignInIdentifier.Email],
sms: [SignInIdentifier.Sms],
emailOrSms: [SignInIdentifier.Email, SignInIdentifier.Sms],
sms: [SignInIdentifier.Phone],
emailOrSms: [SignInIdentifier.Email, SignInIdentifier.Phone],
none: [],
};

View file

@ -22,7 +22,7 @@ describe('reset password', () => {
await setEmailConnector();
await setSmsConnector();
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
password: true,
verify: true,
});

View file

@ -164,7 +164,7 @@ describe('Register with passwordless identifier', () => {
it('register with phone', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: false,
verify: true,
});
@ -207,7 +207,7 @@ describe('Register with passwordless identifier', () => {
it('register with phone and fulfill password', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: true,
verify: true,
});
@ -323,7 +323,7 @@ describe('Register with passwordless identifier', () => {
} = await generateNewUser({ primaryPhone: true });
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
password: false,
verify: true,
});

View file

@ -138,7 +138,7 @@ describe('Sign-In flow using passcode identifiers', () => {
// Enable phone sign-up
await updateSignInExperience({
signUp: { identifiers: [SignInIdentifier.Sms], password: false, verify: true },
signUp: { identifiers: [SignInIdentifier.Phone], password: false, verify: true },
});
const client = await initClient();

View file

@ -151,7 +151,7 @@ describe('Sign-In flow using password identifiers', () => {
// Fulfill the phone number
it('sign-in with username and password and fulfill the phone number', async () => {
await enableAllPasscodeSignInMethods({
identifiers: [SignInIdentifier.Sms, SignInIdentifier.Email],
identifiers: [SignInIdentifier.Phone, SignInIdentifier.Email],
password: true,
verify: true,
});

View file

@ -23,7 +23,7 @@ const defaultPasswordSignInMethods = [
isPasswordPrimary: false,
},
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: false,
isPasswordPrimary: false,
@ -44,7 +44,7 @@ const defaultPasscodeSignInMethods = [
isPasswordPrimary: false,
},
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: true,
isPasswordPrimary: false,

View file

@ -215,7 +215,7 @@ describe('sms passwordless flow', () => {
isPasswordPrimary: true,
},
{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: false,
verificationCode: true,
isPasswordPrimary: false,

View file

@ -63,7 +63,7 @@ const translation = {
reset_password: 'Passwort zurücksetzen',
reset_password_description_email:
'Gib die Email Adresse deines Kontos ein und wir senden dir einen Bestätigungscode um dein Passwort zurückzusetzen.',
reset_password_description_sms:
reset_password_description_phone:
'Gib die Telefonnummer deines Kontos ein und wir senden dir einen Bestätigungscode um dein Passwort zurückzusetzen.',
new_password: 'Neues Passwort',
set_password: 'Set password', // UNTRANSLATED

View file

@ -59,7 +59,7 @@ const translation = {
reset_password: 'Reset password',
reset_password_description_email:
'Enter the email address associated with your account, and well email you the verification code to reset your password.',
reset_password_description_sms:
reset_password_description_phone:
'Enter the phone number associated with your account, and well message you the verification code to reset your password.',
new_password: 'New password',
set_password: 'Set password',

View file

@ -63,7 +63,7 @@ const translation = {
reset_password: 'Réinitialiser le mot de passe',
reset_password_description_email:
"Entrez l'adresse e-mail associée à votre compte et nous vous enverrons par e-mail le code de vérification pour réinitialiser votre mot de passe.",
reset_password_description_sms:
reset_password_description_phone:
'Entrez le numéro de téléphone associé à votre compte et nous vous enverrons le code de vérification par SMS pour réinitialiser votre mot de passe.',
new_password: 'Nouveau mot de passe',
set_password: 'Set password', // UNTRANSLATED

View file

@ -59,7 +59,7 @@ const translation = {
reset_password: '암호를 재설정',
reset_password_description_email:
'계정과 연결된 이메일 주소를 입력하면 비밀번호 재설정을 위한 인증 코드를 이메일로 보내드립니다.',
reset_password_description_sms:
reset_password_description_phone:
'계정과 연결된 전화번호를 입력하면 비밀번호 재설정을 위한 인증 코드를 문자로 보내드립니다.',
new_password: '새 비밀번호',
set_password: '비밀번호 설정',

View file

@ -61,7 +61,7 @@ const translation = {
reset_password: 'Redefinir senha',
reset_password_description_email:
'Digite o endereço de e-mail associado à sua conta e enviaremos por e-mail o código de verificação para redefinir sua senha.',
reset_password_description_sms:
reset_password_description_phone:
'Digite o número de telefone associado à sua conta e enviaremos a você o código de verificação para redefinir sua senha.',
new_password: 'Nova senha',
set_password: 'Configurar senha',

View file

@ -59,7 +59,7 @@ const translation = {
reset_password: 'Redefinir Password',
reset_password_description_email:
'Digite o endereço de email associado à sua conta e enviaremos um email com o código de verificação para redefinir sua senha.',
reset_password_description_sms:
reset_password_description_phone:
'Digite o número de telefone associado à sua conta e enviaremos uma mensagem de texto com o código de verificação para redefinir sua senha.',
new_password: 'Nova Senha',
set_password: 'Set password', // UNTRANSLATED

View file

@ -60,7 +60,7 @@ const translation = {
reset_password: 'Şifre yenile',
reset_password_description_email:
'Hesabınızla ilişkili e-posta adresini girin, şifrenizi sıfırlamak için size doğrulama kodunu e-posta ile gönderelim.',
reset_password_description_sms:
reset_password_description_phone:
'Hesabınızla ilişkili telefon numarasını girin, şifrenizi sıfırlamak için size doğrulama kodunu kısa mesajla gönderelim.',
new_password: 'Yeni Şifre',
set_password: 'Set password', // UNTRANSLATED

View file

@ -58,7 +58,7 @@ const translation = {
social_bind_with_existing: '找到了一个匹配的帐号,你可以直接绑定。',
reset_password: '重设密码',
reset_password_description_email: '输入邮件地址,领取验证码以重设密码。',
reset_password_description_sms: '输入手机号,领取验证码以重设密码。',
reset_password_description_phone: '输入手机号,领取验证码以重设密码。',
new_password: '新密码',
set_password: '设置密码',
password_changed: '已重置密码!',

View file

@ -18,7 +18,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: 'Sign-up identifiers', // UNTRANSLATED
identifiers_email: 'Email address', // UNTRANSLATED
identifiers_sms: 'Phone number', // UNTRANSLATED
identifiers_phone: 'Phone number', // UNTRANSLATED
identifiers_username: 'Username', // UNTRANSLATED
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
identifiers_none: 'Not applicable', // UNTRANSLATED

View file

@ -40,7 +40,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: 'Sign-up identifiers',
identifiers_email: 'Email address',
identifiers_sms: 'Phone number',
identifiers_phone: 'Phone number',
identifiers_username: 'Username',
identifiers_email_or_sms: 'Email address or phone number',
identifiers_none: 'Not applicable',

View file

@ -42,7 +42,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: 'Sign-up identifiers', // UNTRANSLATED
identifiers_email: 'Email address', // UNTRANSLATED
identifiers_sms: 'Phone number', // UNTRANSLATED
identifiers_phone: 'Phone number', // UNTRANSLATED
identifiers_username: 'Username', // UNTRANSLATED
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
identifiers_none: 'Not applicable', // UNTRANSLATED

View file

@ -37,7 +37,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: '회원가입 ID',
identifiers_email: '이메일 주소',
identifiers_sms: '휴대전화번호',
identifiers_phone: '휴대전화번호',
identifiers_username: '사용자 이름',
identifiers_email_or_sms: '이메일 주소 또는 휴대전화번호',
identifiers_none: '해당없음',

View file

@ -41,7 +41,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: 'Identificadores de inscrição',
identifiers_email: 'Endereço de e-mail',
identifiers_sms: 'Número de telefone',
identifiers_phone: 'Número de telefone',
identifiers_username: 'Nome de usuário',
identifiers_email_or_sms: 'Endereço de e-mail ou número de telefone',
identifiers_none: 'Não aplicável',

View file

@ -40,7 +40,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: 'Sign-up identifiers', // UNTRANSLATED
identifiers_email: 'Email address', // UNTRANSLATED
identifiers_sms: 'Phone number', // UNTRANSLATED
identifiers_phone: 'Phone number', // UNTRANSLATED
identifiers_username: 'Username', // UNTRANSLATED
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
identifiers_none: 'Not applicable', // UNTRANSLATED

View file

@ -41,7 +41,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: 'Sign-up identifiers', // UNTRANSLATED
identifiers_email: 'Email address', // UNTRANSLATED
identifiers_sms: 'Phone number', // UNTRANSLATED
identifiers_phone: 'Phone number', // UNTRANSLATED
identifiers_username: 'Username', // UNTRANSLATED
identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED
identifiers_none: 'Not applicable', // UNTRANSLATED

View file

@ -38,7 +38,7 @@ const sign_in_exp = {
sign_up_and_sign_in: {
identifiers: '注册标识',
identifiers_email: '邮件地址',
identifiers_sms: '手机号码',
identifiers_phone: '手机号码',
identifiers_username: '用户名',
identifiers_email_or_sms: '邮件地址或手机号码',
identifiers_none: '无',

View file

@ -0,0 +1,184 @@
import { sql } from 'slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
enum OldSignInIdentifier {
Email = 'email',
Sms = 'Sms',
Username = 'username',
}
type OldSignIn = {
methods: Array<{
password: boolean;
identifier: OldSignInIdentifier;
verificationCode: boolean;
isPasswordPrimary: boolean;
}>;
};
type OldSignUp = {
identifiers: OldSignInIdentifier[];
password: boolean;
verify: boolean;
};
type OldSignInExperience = {
[k: string]: unknown;
id: string;
signIn: OldSignIn;
signUp: OldSignUp;
};
enum SignInIdentifier {
Email = 'email',
Phone = 'Phone',
Username = 'username',
}
type SignIn = {
methods: Array<{
password: boolean;
identifier: SignInIdentifier;
verificationCode: boolean;
isPasswordPrimary: boolean;
}>;
};
type SignUp = {
identifiers: SignInIdentifier[];
password: boolean;
verify: boolean;
};
type SignInExperience = {
[k: string]: unknown;
id: string;
signIn: SignIn;
signUp: SignUp;
};
const alterSignUp = (signUp: OldSignUp) => {
const { identifiers, password, verify } = signUp;
const newIdentifiers = identifiers.map((identifier) => {
if (identifier === OldSignInIdentifier.Sms) {
return SignInIdentifier.Phone;
}
return identifier;
});
return {
identifiers: newIdentifiers,
password,
verify,
};
};
const alterSignIn = (signIn: OldSignIn) => {
const { methods } = signIn;
const newMethods = methods.map((method) => {
const { identifier, ...rest } = method;
if (identifier === OldSignInIdentifier.Sms) {
return {
identifier: SignInIdentifier.Phone,
...rest,
};
}
return method;
});
return {
methods: newMethods,
};
};
const rollbackSignUp = (signUp: SignUp) => {
const { identifiers, password, verify } = signUp;
const newIdentifiers = identifiers.map((identifier) => {
if (identifier === SignInIdentifier.Phone) {
return OldSignInIdentifier.Sms;
}
return identifier;
});
return {
identifiers: newIdentifiers,
password,
verify,
};
};
const rollbackSignIn = (signIn: SignIn) => {
const { methods } = signIn;
const newMethods = methods.map((method) => {
const { identifier, ...rest } = method;
if (identifier === SignInIdentifier.Phone) {
return {
identifier: OldSignInIdentifier.Sms,
...rest,
};
}
return method;
});
return {
methods: newMethods,
};
};
const alteration: AlterationScript = {
up: async (pool) => {
const id = 'default';
const data = await pool.maybeOne<OldSignInExperience>(
sql`select * from sign_in_experiences where id = ${id}`
);
if (data) {
const { signUp, signIn } = data;
const newSignUp = alterSignUp(signUp);
const newSignIn = alterSignIn(signIn);
await pool.query(
sql`update sign_in_experiences set sign_up = ${JSON.stringify(newSignUp)} where id = ${id}`
);
await pool.query(
sql`update sign_in_experiences set sign_in = ${JSON.stringify(newSignIn)} where id = ${id}`
);
}
},
down: async (pool) => {
const id = 'default';
const data = await pool.maybeOne<SignInExperience>(
sql`select * from sign_in_experiences where id = ${id}`
);
if (data) {
const { signUp, signIn } = data;
const oldSignUp = rollbackSignUp(signUp);
const oldSignIn = rollbackSignIn(signIn);
await pool.query(
sql`update sign_in_experiences set sign_up = ${JSON.stringify(oldSignUp)} where id = ${id}`
);
await pool.query(
sql`update sign_in_experiences set sign_in = ${JSON.stringify(oldSignIn)} where id = ${id}`
);
}
},
};
export default alteration;

View file

@ -127,7 +127,7 @@ export type LanguageInfo = z.infer<typeof languageInfoGuard>;
export enum SignInIdentifier {
Username = 'username',
Email = 'email',
Sms = 'sms',
Phone = 'phone',
}
export const signUpGuard = z.object({

View file

@ -99,7 +99,7 @@ const App = () => {
{/* Continue set up missing profile */}
<Route
path="/continue/email-or-sms/:method"
path="/continue/email-or-phone/:method"
element={<ContinueWithEmailOrPhone />}
/>
<Route path="/continue/:method" element={<Continue />} />

View file

@ -166,8 +166,8 @@ export const emailSignInMethod = {
isPasswordPrimary: true,
};
export const smsSignInMethod = {
identifier: SignInIdentifier.Sms,
export const phoneSignInMethod = {
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: true,
isPasswordPrimary: true,
@ -203,7 +203,7 @@ export const mockSignInExperience: SignInExperience = {
verify: true,
},
signIn: {
methods: [usernameSignInMethod, emailSignInMethod, smsSignInMethod],
methods: [usernameSignInMethod, emailSignInMethod, phoneSignInMethod],
},
socialSignInConnectorTargets: ['BE8QXN0VsrOH7xdWFDJZ9', 'lcXT4o2GSjbV9kg2shZC7'],
signInMode: SignInMode.SignInAndRegister,
@ -225,6 +225,6 @@ export const mockSignInExperienceSettings: SignInExperienceResponse = {
signInMode: SignInMode.SignInAndRegister,
forgotPassword: {
email: true,
sms: true,
phone: true,
},
};

View file

@ -4,7 +4,7 @@ import TextLink from '@/components/TextLink';
import { UserFlow } from '@/types';
type Props = {
method: SignInIdentifier.Email | SignInIdentifier.Sms;
method: SignInIdentifier.Email | SignInIdentifier.Phone;
className?: string;
};

View file

@ -87,7 +87,7 @@ const EmailForm = ({
}}
/>
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
{hasSwitch && <PasswordlessSwitch target="sms" className={styles.switch} />}
{hasSwitch && <PasswordlessSwitch target="phone" className={styles.switch} />}
{hasTerms && <TermsOfUse className={styles.terms} />}
<Button title={submitButtonText} onClick={async () => onSubmitHandler()} />

View file

@ -87,7 +87,7 @@ const EmailPassword = ({ className, autoFocus }: Props) => {
{isForgotPasswordEnabled && (
<ForgotPasswordLink
className={styles.link}
method={email ? SignInIdentifier.Email : SignInIdentifier.Sms}
method={email ? SignInIdentifier.Email : SignInIdentifier.Phone}
/>
)}

View file

@ -25,7 +25,7 @@ const SignInMethodsKeyMap: {
} = {
[SignInIdentifier.Username]: 'username',
[SignInIdentifier.Email]: 'email',
[SignInIdentifier.Sms]: 'phone_number',
[SignInIdentifier.Phone]: 'phone_number',
};
const OtherMethodsLink = ({ methods, template, search, flow, className }: Props) => {

View file

@ -1,11 +1,11 @@
import { SignInIdentifier } from '@logto/schemas';
import type { SignInIdentifier } from '@logto/schemas';
import TextLink from '@/components/TextLink';
import { UserFlow } from '@/types';
type Props = {
className?: string;
method: SignInIdentifier.Email | SignInIdentifier.Sms;
method: SignInIdentifier.Email | SignInIdentifier.Phone;
target: string;
};
@ -16,7 +16,7 @@ const PasswordSignInLink = ({ className, method, target }: Props) => {
className={className}
text="action.sign_in_via_password"
to={`/${UserFlow.signIn}/${method}/password`}
state={method === SignInIdentifier.Email ? { email: target } : { phone: target }}
state={{ [method]: target }}
/>
);
};

View file

@ -114,13 +114,13 @@ describe('<PasscodeValidation />', () => {
});
});
it('fire sms sign-in validate passcode event', async () => {
it('fire phone sign-in validate passcode event', async () => {
(signInWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
redirectTo: 'foo.com',
}));
const { container } = renderWithPageContext(
<PasscodeValidation type={UserFlow.signIn} method={SignInIdentifier.Sms} target={phone} />
<PasscodeValidation type={UserFlow.signIn} method={SignInIdentifier.Phone} target={phone} />
);
const inputs = container.querySelectorAll('input');
@ -179,13 +179,17 @@ describe('<PasscodeValidation />', () => {
});
});
it('fire sms register validate passcode event', async () => {
it('fire phone register validate passcode event', async () => {
(addProfileWithPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
redirectTo: 'foo.com',
}));
const { container } = renderWithPageContext(
<PasscodeValidation type={UserFlow.register} method={SignInIdentifier.Sms} target={phone} />
<PasscodeValidation
type={UserFlow.register}
method={SignInIdentifier.Phone}
target={phone}
/>
);
const inputs = container.querySelectorAll('input');
@ -237,7 +241,7 @@ describe('<PasscodeValidation />', () => {
// TODO: @simeng test exception flow to fulfill the password
});
it('fire sms forgot-password validate passcode event', async () => {
it('fire phone forgot-password validate passcode event', async () => {
(verifyForgotPasswordPasscodeIdentifier as jest.Mock).mockImplementationOnce(() => ({
success: true,
}));
@ -245,7 +249,7 @@ describe('<PasscodeValidation />', () => {
const { container } = renderWithPageContext(
<PasscodeValidation
type={UserFlow.forgotPassword}
method={SignInIdentifier.Sms}
method={SignInIdentifier.Phone}
target={phone}
/>
);
@ -312,7 +316,11 @@ describe('<PasscodeValidation />', () => {
}));
const { container } = renderWithPageContext(
<PasscodeValidation type={UserFlow.continue} method={SignInIdentifier.Sms} target={phone} />
<PasscodeValidation
type={UserFlow.continue}
method={SignInIdentifier.Phone}
target={phone}
/>
);
const inputs = container.querySelectorAll('input');

View file

@ -14,7 +14,7 @@ import { getPasscodeValidationHook } from './utils';
type Props = {
type: UserFlow;
method: SignInIdentifier.Email | SignInIdentifier.Sms;
method: SignInIdentifier.Email | SignInIdentifier.Phone;
target: string;
hasPasswordButton?: boolean;
className?: string;

View file

@ -11,14 +11,14 @@ import { getSearchParameters } from '@/utils';
import useIdentifierErrorAlert from './use-identifier-error-alert';
import useSharedErrorHandler from './use-shared-error-handler';
const useContinueSetSmsPasscodeValidation = (phone: string, errorCallback?: () => void) => {
const useContinueSetPhonePasscodeValidation = (phone: string, errorCallback?: () => void) => {
const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler();
const requiredProfileErrorHandler = useRequiredProfileErrorHandler(true);
const identifierExistErrorHandler = useIdentifierErrorAlert(
UserFlow.continue,
SignInIdentifier.Sms,
SignInIdentifier.Phone,
phone
);
@ -56,4 +56,4 @@ const useContinueSetSmsPasscodeValidation = (phone: string, errorCallback?: () =
};
};
export default useContinueSetSmsPasscodeValidation;
export default useContinueSetPhonePasscodeValidation;

View file

@ -10,13 +10,13 @@ import { UserFlow } from '@/types';
import useIdentifierErrorAlert from './use-identifier-error-alert';
import useSharedErrorHandler from './use-shared-error-handler';
const useForgotPasswordSmsPasscodeValidation = (phone: string, errorCallback?: () => void) => {
const useForgotPasswordPhonePasscodeValidation = (phone: string, errorCallback?: () => void) => {
const navigate = useNavigate();
const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler();
const identifierNotExistErrorHandler = useIdentifierErrorAlert(
UserFlow.forgotPassword,
SignInIdentifier.Sms,
SignInIdentifier.Phone,
phone
);
@ -57,4 +57,4 @@ const useForgotPasswordSmsPasscodeValidation = (phone: string, errorCallback?: (
};
};
export default useForgotPasswordSmsPasscodeValidation;
export default useForgotPasswordPhonePasscodeValidation;

View file

@ -8,7 +8,7 @@ import { UserFlow } from '@/types';
const useIdentifierErrorAlert = (
flow: UserFlow,
method: SignInIdentifier.Email | SignInIdentifier.Sms,
method: SignInIdentifier.Email | SignInIdentifier.Phone,
value: string
) => {
const { show } = useConfirmModal();

View file

@ -15,7 +15,7 @@ import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code';
import useIdentifierErrorAlert from './use-identifier-error-alert';
import useSharedErrorHandler from './use-shared-error-handler';
const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: () => void) => {
const useRegisterWithPhonePasscodeValidation = (phone: string, errorCallback?: () => void) => {
const { t } = useTranslation();
const { show } = useConfirmModal();
const navigate = useNavigate();
@ -24,14 +24,14 @@ const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: ()
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true);
const { run: signInWithSmsAsync } = useApi(
const { run: signInWithPhoneAsync } = useApi(
signInWithVerifierIdentifier,
requiredProfileErrorHandlers
);
const identifierExistErrorHandler = useIdentifierErrorAlert(
UserFlow.register,
SignInIdentifier.Sms,
SignInIdentifier.Phone,
formatPhoneNumberWithCountryCallingCode(phone)
);
@ -50,12 +50,12 @@ const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: ()
return;
}
const result = await signInWithSmsAsync();
const result = await signInWithPhoneAsync();
if (result?.redirectTo) {
window.location.replace(result.redirectTo);
}
}, [phone, navigate, show, signInWithSmsAsync, t]);
}, [phone, navigate, show, signInWithPhoneAsync, t]);
const errorHandlers = useMemo<ErrorHandlers>(
() => ({
@ -102,4 +102,4 @@ const useRegisterWithSmsPasscodeValidation = (phone: string, errorCallback?: ()
};
};
export default useRegisterWithSmsPasscodeValidation;
export default useRegisterWithPhonePasscodeValidation;

View file

@ -19,7 +19,7 @@ const getTimeout = () => {
const useResendPasscode = (
type: UserFlow,
method: SignInIdentifier.Email | SignInIdentifier.Sms,
method: SignInIdentifier.Email | SignInIdentifier.Phone,
target: string
) => {
const { setToast } = useContext(PageContext);

View file

@ -15,7 +15,7 @@ import { getSearchParameters } from '@/utils';
import useIdentifierErrorAlert from './use-identifier-error-alert';
import useSharedErrorHandler from './use-shared-error-handler';
const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () => void) => {
const useSignInWithPhonePasscodeValidation = (phone: string, errorCallback?: () => void) => {
const { t } = useTranslation();
const { show } = useConfirmModal();
const navigate = useNavigate();
@ -25,7 +25,7 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true);
const { run: registerWithSmsAsync } = useApi(
const { run: registerWithPhoneAsync } = useApi(
registerWithVerifiedIdentifier,
requiredProfileErrorHandlers
);
@ -34,7 +34,7 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
const identifierNotExistErrorHandler = useIdentifierErrorAlert(
UserFlow.signIn,
SignInIdentifier.Sms,
SignInIdentifier.Phone,
phone
);
@ -53,12 +53,12 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
return;
}
const result = await registerWithSmsAsync({ phone });
const result = await registerWithPhoneAsync({ phone });
if (result?.redirectTo) {
window.location.replace(result.redirectTo);
}
}, [phone, navigate, show, registerWithSmsAsync, t]);
}, [phone, navigate, show, registerWithPhoneAsync, t]);
const errorHandlers = useMemo<ErrorHandlers>(
() => ({
@ -113,4 +113,4 @@ const useSignInWithSmsPasscodeValidation = (phone: string, errorCallback?: () =>
};
};
export default useSignInWithSmsPasscodeValidation;
export default useSignInWithPhonePasscodeValidation;

View file

@ -3,34 +3,34 @@ import { SignInIdentifier } from '@logto/schemas';
import { UserFlow } from '@/types';
import useContinueSetEmailPasscodeValidation from './use-continue-set-email-passcode-validation';
import useContinueSetSmsPasscodeValidation from './use-continue-set-sms-passcode-validation';
import useContinueSetPhonePasscodeValidation from './use-continue-set-phone-passcode-validation';
import useForgotPasswordEmailPasscodeValidation from './use-forgot-password-email-passcode-validation';
import useForgotPasswordSmsPasscodeValidation from './use-forgot-password-sms-passcode-validation';
import useForgotPasswordPhonePasscodeValidation from './use-forgot-password-phone-passcode-validation';
import useRegisterWithEmailPasscodeValidation from './use-register-with-email-passcode-validation';
import useRegisterWithSmsPasscodeValidation from './use-register-with-sms-passcode-validation';
import useRegisterWithPhonePasscodeValidation from './use-register-with-phone-passcode-validation';
import useSignInWithEmailPasscodeValidation from './use-sign-in-with-email-passcode-validation';
import useSignInWithSmsPasscodeValidation from './use-sign-in-with-sms-passcode-validation';
import useSignInWithPhonePasscodeValidation from './use-sign-in-with-phone-passcode-validation';
export const getPasscodeValidationHook = (
type: UserFlow,
method: SignInIdentifier.Email | SignInIdentifier.Sms
method: SignInIdentifier.Email | SignInIdentifier.Phone
) => {
switch (type) {
case UserFlow.signIn:
return method === SignInIdentifier.Email
? useSignInWithEmailPasscodeValidation
: useSignInWithSmsPasscodeValidation;
: useSignInWithPhonePasscodeValidation;
case UserFlow.register:
return method === SignInIdentifier.Email
? useRegisterWithEmailPasscodeValidation
: useRegisterWithSmsPasscodeValidation;
: useRegisterWithPhonePasscodeValidation;
case UserFlow.forgotPassword:
return method === SignInIdentifier.Email
? useForgotPasswordEmailPasscodeValidation
: useForgotPasswordSmsPasscodeValidation;
: useForgotPasswordPhonePasscodeValidation;
default:
return method === SignInIdentifier.Email
? useContinueSetEmailPasscodeValidation
: useContinueSetSmsPasscodeValidation;
: useContinueSetPhonePasscodeValidation;
}
};

View file

@ -8,7 +8,7 @@ import { UserFlow } from '@/types';
type Props = {
className?: string;
method: SignInIdentifier.Email | SignInIdentifier.Sms;
method: SignInIdentifier.Email | SignInIdentifier.Phone;
value: string;
};

View file

@ -92,9 +92,9 @@ describe('PasswordSignInForm', () => {
);
});
it('SmsPasswordSignForm', async () => {
it('PhonePasswordSignForm', async () => {
const { getByText, container } = renderWithPageContext(
<PasswordSignInForm hasPasswordlessButton method={SignInIdentifier.Sms} value={phone} />
<PasswordSignInForm hasPasswordlessButton method={SignInIdentifier.Phone} value={phone} />
);
const passwordInput = container.querySelector('input[name="password"]');
@ -128,7 +128,7 @@ describe('PasswordSignInForm', () => {
expect(mockedNavigate).toBeCalledWith(
{
pathname: `/${UserFlow.signIn}/${SignInIdentifier.Sms}/passcode-validation`,
pathname: `/${UserFlow.signIn}/${SignInIdentifier.Phone}/passcode-validation`,
search: '',
},
{

View file

@ -16,7 +16,7 @@ import PasswordlessSignInLink from './PasswordlessSignInLink';
import * as styles from './index.module.scss';
type Props = {
method: SignInIdentifier.Email | SignInIdentifier.Sms;
method: SignInIdentifier.Email | SignInIdentifier.Phone;
value: string;
hasPasswordlessButton?: boolean;
className?: string;
@ -42,7 +42,7 @@ const PasswordSignInForm = ({
const { t } = useTranslation();
const { errorMessage, clearErrorMessage, onSubmit } = usePasswordSignIn();
const { fieldValue, register, validateForm } = useForm(defaultState);
const { isForgotPasswordEnabled, sms, email } = useForgotPasswordSettings();
const { isForgotPasswordEnabled, phone, email } = useForgotPasswordSettings();
const onSubmitHandler = useCallback(
async (event?: React.FormEvent<HTMLFormElement>) => {
@ -83,9 +83,9 @@ const PasswordSignInForm = ({
method === SignInIdentifier.Email
? email
? SignInIdentifier.Email
: SignInIdentifier.Sms
: sms
? SignInIdentifier.Sms
: SignInIdentifier.Phone
: phone
? SignInIdentifier.Phone
: SignInIdentifier.Email
}
/>

View file

@ -5,9 +5,9 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import PasswordlessSwitch from '.';
describe('<PasswordlessSwitch />', () => {
test('render sms passwordless switch', () => {
test('render phone passwordless switch', () => {
const { queryByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/forgot-password/sms']}>
<MemoryRouter initialEntries={['/forgot-password/phone']}>
<PasswordlessSwitch target="email" />
</MemoryRouter>
);
@ -19,11 +19,11 @@ describe('<PasswordlessSwitch />', () => {
test('render email passwordless switch', () => {
const { queryByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/forgot-password/email']}>
<PasswordlessSwitch target="sms" />
<PasswordlessSwitch target="phone" />
</MemoryRouter>
);
expect(queryByText('action.switch_to')).not.toBeNull();
expect(container.querySelector('a')?.getAttribute('href')).toBe('/forgot-password/sms');
expect(container.querySelector('a')?.getAttribute('href')).toBe('/forgot-password/phone');
});
});

View file

@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom';
import TextLink from '@/components/TextLink';
type Props = {
target: 'sms' | 'email';
target: 'phone' | 'email';
className?: string;
};

View file

@ -5,7 +5,7 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import { putInteraction, sendPasscode } from '@/apis/interaction';
import { getDefaultCountryCallingCode } from '@/utils/country-code';
import SmsContinue from './SmsContinue';
import PhoneContinue from './PhoneContinue';
const mockedNavigate = jest.fn();
@ -24,7 +24,7 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigate,
}));
describe('SmsContinue', () => {
describe('PhoneContinue', () => {
const phone = '8573333333';
const defaultCountryCallingCode = getDefaultCountryCallingCode();
const fullPhoneNumber = `${defaultCountryCallingCode}${phone}`;
@ -32,7 +32,7 @@ describe('SmsContinue', () => {
test('register form submit', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsContinue />
<PhoneContinue />
</MemoryRouter>
);
const phoneInput = container.querySelector('input[name="phone"]');
@ -51,7 +51,7 @@ describe('SmsContinue', () => {
expect(putInteraction).not.toBeCalled();
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
expect(mockedNavigate).toBeCalledWith(
{ pathname: '/continue/sms/passcode-validation', search: '' },
{ pathname: '/continue/phone/passcode-validation', search: '' },
{ state: { phone: fullPhoneNumber } }
);
});

View file

@ -3,7 +3,7 @@ import { SignInIdentifier } from '@logto/schemas';
import usePasswordlessSendCode from '@/hooks/use-passwordless-send-code';
import { UserFlow } from '@/types';
import SmsForm from './PhoneForm';
import PhoneForm from './PhoneForm';
type Props = {
className?: string;
@ -12,14 +12,14 @@ type Props = {
hasSwitch?: boolean;
};
const SmsContinue = (props: Props) => {
const PhoneContinue = (props: Props) => {
const { onSubmit, errorMessage, clearErrorMessage } = usePasswordlessSendCode(
UserFlow.continue,
SignInIdentifier.Sms
SignInIdentifier.Phone
);
return (
<SmsForm
<PhoneForm
onSubmit={onSubmit}
{...props}
errorMessage={errorMessage}
@ -29,4 +29,4 @@ const SmsContinue = (props: Props) => {
);
};
export default SmsContinue;
export default PhoneContinue;

View file

@ -6,7 +6,7 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import { putInteraction, sendPasscode } from '@/apis/interaction';
import { getDefaultCountryCallingCode } from '@/utils/country-code';
import SmsRegister from './SmsRegister';
import PhoneRegister from './PhoneRegister';
const mockedNavigate = jest.fn();
@ -25,7 +25,7 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigate,
}));
describe('SmsRegister', () => {
describe('PhoneRegister', () => {
const phone = '8573333333';
const defaultCountryCallingCode = getDefaultCountryCallingCode();
const fullPhoneNumber = `${defaultCountryCallingCode}${phone}`;
@ -33,7 +33,7 @@ describe('SmsRegister', () => {
test('register form submit', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsRegister />
<PhoneRegister />
</MemoryRouter>
);
const phoneInput = container.querySelector('input[name="phone"]');
@ -52,7 +52,7 @@ describe('SmsRegister', () => {
expect(putInteraction).toBeCalledWith(InteractionEvent.Register);
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
expect(mockedNavigate).toBeCalledWith(
{ pathname: '/register/sms/passcode-validation', search: '' },
{ pathname: '/register/phone/passcode-validation', search: '' },
{ state: { phone: fullPhoneNumber } }
);
});

View file

@ -11,10 +11,10 @@ type Props = {
autoFocus?: boolean;
};
const SmsRegister = (props: Props) => {
const PhoneRegister = (props: Props) => {
const { onSubmit, errorMessage, clearErrorMessage } = usePasswordlessSendCode(
UserFlow.register,
SignInIdentifier.Sms
SignInIdentifier.Phone
);
return (
@ -28,4 +28,4 @@ const SmsRegister = (props: Props) => {
);
};
export default SmsRegister;
export default PhoneRegister;

View file

@ -7,7 +7,7 @@ import { putInteraction, sendPasscode } from '@/apis/interaction';
import { UserFlow } from '@/types';
import { getDefaultCountryCallingCode } from '@/utils/country-code';
import SmsResetPassword from './SmsResetPassword';
import PhoneResetPassword from './PhoneResetPassword';
const mockedNavigate = jest.fn();
@ -26,7 +26,7 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigate,
}));
describe('SmsRegister', () => {
describe('PhoneRegister', () => {
const phone = '8573333333';
const defaultCountryCallingCode = getDefaultCountryCallingCode();
const fullPhoneNumber = `${defaultCountryCallingCode}${phone}`;
@ -34,7 +34,7 @@ describe('SmsRegister', () => {
test('register form submit', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsResetPassword />
<PhoneResetPassword />
</MemoryRouter>
);
const phoneInput = container.querySelector('input[name="phone"]');
@ -54,7 +54,7 @@ describe('SmsRegister', () => {
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
expect(mockedNavigate).toBeCalledWith(
{
pathname: `/${UserFlow.forgotPassword}/${SignInIdentifier.Sms}/passcode-validation`,
pathname: `/${UserFlow.forgotPassword}/${SignInIdentifier.Phone}/passcode-validation`,
search: '',
},
{ state: { phone: fullPhoneNumber } }

View file

@ -12,10 +12,10 @@ type Props = {
hasSwitch?: boolean;
};
const SmsResetPassword = (props: Props) => {
const PhoneResetPassword = (props: Props) => {
const { onSubmit, errorMessage, clearErrorMessage } = usePasswordlessSendCode(
UserFlow.forgotPassword,
SignInIdentifier.Sms
SignInIdentifier.Phone
);
return (
@ -30,4 +30,4 @@ const SmsResetPassword = (props: Props) => {
);
};
export default SmsResetPassword;
export default PhoneResetPassword;

View file

@ -6,7 +6,7 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
import { sendPasscode, putInteraction } from '@/apis/interaction';
import { getDefaultCountryCallingCode } from '@/utils/country-code';
import SmsSignIn from './SmsSignIn';
import PhoneSignIn from './PhoneSignIn';
const mockedNavigate = jest.fn();
@ -25,7 +25,7 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigate,
}));
describe('SmsSignIn', () => {
describe('PhoneSignIn', () => {
const phone = '8573333333';
const defaultCountryCallingCode = getDefaultCountryCallingCode();
const fullPhoneNumber = `${defaultCountryCallingCode}${phone}`;
@ -34,12 +34,12 @@ describe('SmsSignIn', () => {
jest.clearAllMocks();
});
test('SmsSignIn form with password as primary method', async () => {
test('PhoneSignIn form with password as primary method', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsSignIn
<PhoneSignIn
signInMethod={{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: true,
isPasswordPrimary: true,
@ -63,18 +63,18 @@ describe('SmsSignIn', () => {
expect(putInteraction).not.toBeCalled();
expect(sendPasscode).not.toBeCalled();
expect(mockedNavigate).toBeCalledWith(
{ pathname: '/sign-in/sms/password', search: '' },
{ pathname: '/sign-in/phone/password', search: '' },
{ state: { phone: fullPhoneNumber } }
);
});
});
test('SmsSignIn form with password true, primary true but verification code false', async () => {
test('PhoneSignIn form with password true, primary true but verification code false', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsSignIn
<PhoneSignIn
signInMethod={{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: false,
isPasswordPrimary: true,
@ -98,18 +98,18 @@ describe('SmsSignIn', () => {
expect(putInteraction).not.toBeCalled();
expect(sendPasscode).not.toBeCalled();
expect(mockedNavigate).toBeCalledWith(
{ pathname: '/sign-in/sms/password', search: '' },
{ pathname: '/sign-in/phone/password', search: '' },
{ state: { phone: fullPhoneNumber } }
);
});
});
test('SmsSignIn form with password true but is primary false and verification code true', async () => {
test('PhoneSignIn form with password true but is primary false and verification code true', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsSignIn
<PhoneSignIn
signInMethod={{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: true,
verificationCode: true,
isPasswordPrimary: false,
@ -134,18 +134,18 @@ describe('SmsSignIn', () => {
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
expect(mockedNavigate).toBeCalledWith(
{ pathname: '/sign-in/sms/passcode-validation', search: '' },
{ pathname: '/sign-in/phone/passcode-validation', search: '' },
{ state: { phone: fullPhoneNumber } }
);
});
});
test('SmsSignIn form with password false but primary verification code true', async () => {
test('PhoneSignIn form with password false but primary verification code true', async () => {
const { container, getByText } = renderWithPageContext(
<MemoryRouter>
<SmsSignIn
<PhoneSignIn
signInMethod={{
identifier: SignInIdentifier.Sms,
identifier: SignInIdentifier.Phone,
password: false,
verificationCode: true,
isPasswordPrimary: true,
@ -170,7 +170,7 @@ describe('SmsSignIn', () => {
expect(putInteraction).toBeCalledWith(InteractionEvent.SignIn);
expect(sendPasscode).toBeCalledWith({ phone: fullPhoneNumber });
expect(mockedNavigate).toBeCalledWith(
{ pathname: '/sign-in/sms/passcode-validation', search: '' },
{ pathname: '/sign-in/phone/passcode-validation', search: '' },
{ state: { phone: fullPhoneNumber } }
);
});

View file

@ -18,10 +18,10 @@ type Props = FormProps & {
signInMethod: ArrayElement<SignIn['methods']>;
};
const SmsSignInWithPasscode = (props: FormProps) => {
const PhoneSignInWithPasscode = (props: FormProps) => {
const { onSubmit, errorMessage, clearErrorMessage } = usePasswordlessSendCode(
UserFlow.signIn,
SignInIdentifier.Sms
SignInIdentifier.Phone
);
return (
@ -35,26 +35,26 @@ const SmsSignInWithPasscode = (props: FormProps) => {
);
};
const SmsSignInWithPassword = (props: FormProps) => {
const onSubmit = useContinueSignInWithPassword(SignInIdentifier.Sms);
const PhoneSignInWithPassword = (props: FormProps) => {
const onSubmit = useContinueSignInWithPassword(SignInIdentifier.Phone);
return <PhoneForm onSubmit={onSubmit} {...props} submitButtonText="action.sign_in" />;
};
const SmsSignIn = ({ signInMethod, ...props }: Props) => {
const PhoneSignIn = ({ signInMethod, ...props }: Props) => {
const { password, isPasswordPrimary, verificationCode } = signInMethod;
// Continue with password
if (password && (isPasswordPrimary || !verificationCode)) {
return <SmsSignInWithPassword {...props} />;
return <PhoneSignInWithPassword {...props} />;
}
// Send passcode
if (verificationCode) {
return <SmsSignInWithPasscode {...props} />;
return <PhoneSignInWithPasscode {...props} />;
}
return null;
};
export default SmsSignIn;
export default PhoneSignIn;

View file

@ -1,4 +1,4 @@
export { default as SmsRegister } from './SmsRegister';
export { default as SmsSignIn } from './SmsSignIn';
export { default as SmsResetPassword } from './SmsResetPassword';
export { default as SmsContinue } from './SmsContinue';
export { default as PhoneRegister } from './PhoneRegister';
export { default as PhoneSignIn } from './PhoneSignIn';
export { default as PhoneResetPassword } from './PhoneResetPassword';
export { default as PhoneContinue } from './PhoneContinue';

View file

@ -37,7 +37,7 @@ const PhonePassword = ({ className, autoFocus }: Props) => {
const { t } = useTranslation();
const { termsValidation } = useTerms();
const { errorMessage, clearErrorMessage, onSubmit } = usePasswordSignIn();
const { isForgotPasswordEnabled, sms } = useForgotPasswordSettings();
const { isForgotPasswordEnabled, phone } = useForgotPasswordSettings();
const { countryList, phoneNumber, setPhoneNumber, isValidPhoneNumber } = usePhoneNumber();
const { fieldValue, setFieldValue, register, validateForm } = useForm(defaultState);
@ -107,7 +107,7 @@ const PhonePassword = ({ className, autoFocus }: Props) => {
{isForgotPasswordEnabled && (
<ForgotPasswordLink
className={styles.link}
method={sms ? SignInIdentifier.Sms : SignInIdentifier.Email}
method={phone ? SignInIdentifier.Phone : SignInIdentifier.Email}
/>
)}

View file

@ -47,7 +47,10 @@ describe('<UsernameSignIn>', () => {
test('render with forgot password disabled', () => {
const { queryByText } = renderWithPageContext(
<SettingsProvider
settings={{ ...mockSignInExperienceSettings, forgotPassword: { sms: false, email: false } }}
settings={{
...mockSignInExperienceSettings,
forgotPassword: { phone: false, email: false },
}}
>
<UsernameSignIn />
</SettingsProvider>

View file

@ -84,7 +84,7 @@ const UsernameSignIn = ({ className, autoFocus }: Props) => {
{isForgotPasswordEnabled && (
<ForgotPasswordLink
className={styles.link}
method={email ? SignInIdentifier.Email : SignInIdentifier.Sms}
method={email ? SignInIdentifier.Email : SignInIdentifier.Phone}
/>
)}

View file

@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { UserFlow } from '@/types';
const useContinueSignInWithPassword = <T extends SignInIdentifier.Email | SignInIdentifier.Sms>(
const useContinueSignInWithPassword = <T extends SignInIdentifier.Email | SignInIdentifier.Phone>(
method: T
) => {
const navigate = useNavigate();

View file

@ -7,7 +7,7 @@ import type { ErrorHandlers } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import type { UserFlow } from '@/types';
const usePasswordlessSendCode = <T extends SignInIdentifier.Email | SignInIdentifier.Sms>(
const usePasswordlessSendCode = <T extends SignInIdentifier.Email | SignInIdentifier.Phone>(
flow: UserFlow,
method: T,
replaceCurrentPage?: boolean

View file

@ -34,7 +34,7 @@ const useRequiredProfileErrorHandler = (replace?: boolean) => {
case MissingProfile.phone:
navigate(
{
pathname: `/${UserFlow.continue}/sms`,
pathname: `/${UserFlow.continue}/phone`,
search: location.search,
},
{ replace }
@ -43,7 +43,7 @@ const useRequiredProfileErrorHandler = (replace?: boolean) => {
case MissingProfile.emailOrPhone:
navigate(
{
pathname: `/${UserFlow.continue}/email-or-sms/email`,
pathname: `/${UserFlow.continue}/email-or-phone/email`,
search: location.search,
},
{ replace }

View file

@ -26,7 +26,7 @@ export const useForgotPasswordSettings = () => {
return {
isForgotPasswordEnabled: Boolean(
forgotPassword && (forgotPassword.email || forgotPassword.sms)
forgotPassword && (forgotPassword.email || forgotPassword.phone)
),
...forgotPassword,
};

View file

@ -13,9 +13,9 @@ describe('EmailOrPhone', () => {
it('render set phone with email alterations', () => {
const { queryByText, container } = renderWithPageContext(
<SettingsProvider>
<MemoryRouter initialEntries={['/continue/email-or-sms/sms']}>
<MemoryRouter initialEntries={['/continue/email-or-phone/phone']}>
<Routes>
<Route path="/continue/email-or-sms/:method" element={<EmailOrPhone />} />
<Route path="/continue/email-or-phone/:method" element={<EmailOrPhone />} />
</Routes>
</MemoryRouter>
</SettingsProvider>
@ -31,9 +31,9 @@ describe('EmailOrPhone', () => {
it('render set email with phone alterations', () => {
const { queryByText, container } = renderWithPageContext(
<SettingsProvider>
<MemoryRouter initialEntries={['/continue/email-or-sms/email']}>
<MemoryRouter initialEntries={['/continue/email-or-phone/email']}>
<Routes>
<Route path="/continue/email-or-sms/:method" element={<EmailOrPhone />} />
<Route path="/continue/email-or-phone/:method" element={<EmailOrPhone />} />
</Routes>
</MemoryRouter>
</SettingsProvider>

View file

@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom';
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
import { EmailContinue } from '@/containers/EmailForm';
import { SmsContinue } from '@/containers/PhoneForm';
import { PhoneContinue } from '@/containers/PhoneForm';
import ErrorPage from '@/pages/ErrorPage';
type Parameters = {
@ -24,13 +24,13 @@ const EmailOrPhone = () => {
);
}
if (method === SignInIdentifier.Sms) {
if (method === SignInIdentifier.Phone) {
return (
<SecondaryPageWrapper
title="description.link_email_or_phone"
description="description.link_email_or_phone_description"
>
<SmsContinue autoFocus hasSwitch />
<PhoneContinue autoFocus hasSwitch />
</SecondaryPageWrapper>
);
}

View file

@ -26,7 +26,7 @@ describe('SetPhone', () => {
...mockSignInExperienceSettings,
signUp: {
...mockSignInExperienceSettings.signUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
},
}}
>

View file

@ -1,5 +1,5 @@
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
import { SmsContinue } from '@/containers/PhoneForm';
import { PhoneContinue } from '@/containers/PhoneForm';
const SetPhone = () => {
return (
@ -7,7 +7,7 @@ const SetPhone = () => {
title="description.link_phone"
description="description.link_phone_description"
>
<SmsContinue autoFocus />
<PhoneContinue autoFocus />
</SecondaryPageWrapper>
);
};

View file

@ -27,7 +27,7 @@ const Continue = () => {
return <SetEmail />;
}
if (method === SignInIdentifier.Sms) {
if (method === SignInIdentifier.Phone) {
return <SetPhone />;
}

View file

@ -11,7 +11,7 @@ jest.mock('i18next', () => ({
}));
describe('ForgotPassword', () => {
it('render email forgot password properly with sms enabled as well', () => {
it('render email forgot password properly with phone enabled as well', () => {
const { queryByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/forgot-password/email']}>
<Routes>
@ -33,9 +33,9 @@ describe('ForgotPassword', () => {
expect(queryByText('action.switch_to')).not.toBeNull();
});
it('render sms forgot password properly with email disabled', () => {
it('render phone forgot password properly with email disabled', () => {
const { queryByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/forgot-password/sms']}>
<MemoryRouter initialEntries={['/forgot-password/phone']}>
<Routes>
<Route
path="/forgot-password/:method"
@ -43,7 +43,7 @@ describe('ForgotPassword', () => {
<SettingsProvider
settings={{
...mockSignInExperienceSettings,
forgotPassword: { email: false, sms: true },
forgotPassword: { email: false, phone: true },
}}
>
<ForgotPassword />
@ -55,14 +55,14 @@ describe('ForgotPassword', () => {
);
expect(queryByText('description.reset_password')).not.toBeNull();
expect(queryByText('description.reset_password_description_sms')).not.toBeNull();
expect(queryByText('description.reset_password_description_phone')).not.toBeNull();
expect(container.querySelector('input[name="phone"]')).not.toBeNull();
expect(queryByText('action.switch_to')).toBeNull();
});
it('should return error page if forgot password is not enabled', () => {
const { queryByText } = renderWithPageContext(
<MemoryRouter initialEntries={['/forgot-password/sms']}>
<MemoryRouter initialEntries={['/forgot-password/phone']}>
<Routes>
<Route
path="/forgot-password/:method"
@ -70,7 +70,7 @@ describe('ForgotPassword', () => {
<SettingsProvider
settings={{
...mockSignInExperienceSettings,
forgotPassword: { email: true, sms: false },
forgotPassword: { email: true, phone: false },
}}
>
<ForgotPassword />
@ -82,7 +82,7 @@ describe('ForgotPassword', () => {
);
expect(queryByText('description.reset_password')).toBeNull();
expect(queryByText('description.reset_password_description_sms')).toBeNull();
expect(queryByText('description.reset_password_description_phone')).toBeNull();
expect(queryByText('description.not_found')).not.toBeNull();
});
});

View file

@ -4,7 +4,7 @@ import { is } from 'superstruct';
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
import { EmailResetPassword } from '@/containers/EmailForm';
import { SmsResetPassword } from '@/containers/PhoneForm';
import { PhoneResetPassword } from '@/containers/PhoneForm';
import { useForgotPasswordSettings } from '@/hooks/use-sie';
import ErrorPage from '@/pages/ErrorPage';
import { passcodeMethodGuard } from '@/types/guard';
@ -27,20 +27,18 @@ const ForgotPassword = () => {
}
const PasswordlessForm =
method === SignInIdentifier.Email ? EmailResetPassword : SmsResetPassword;
method === SignInIdentifier.Email ? EmailResetPassword : PhoneResetPassword;
return (
<SecondaryPageWrapper
title="description.reset_password"
description={`description.reset_password_description_${
method === SignInIdentifier.Email ? 'email' : 'sms'
}`}
description={`description.reset_password_description_${method}`}
>
<PasswordlessForm
autoFocus
hasSwitch={
forgotPassword[
method === SignInIdentifier.Email ? SignInIdentifier.Sms : SignInIdentifier.Email
method === SignInIdentifier.Email ? SignInIdentifier.Phone : SignInIdentifier.Email
]
}
/>

View file

@ -1,4 +1,3 @@
import { SignInIdentifier } from '@logto/schemas';
import { t } from 'i18next';
import { useParams, useLocation } from 'react-router-dom';
import { is } from 'superstruct';
@ -36,7 +35,7 @@ const Passcode = () => {
return <ErrorPage />;
}
const target = !invalidState && state[method === SignInIdentifier.Email ? 'email' : 'phone'];
const target = !invalidState && state[method];
if (!target) {
return <ErrorPage title={method === 'email' ? 'error.invalid_email' : 'error.invalid_phone'} />;

View file

@ -1,7 +1,7 @@
import type { SignInIdentifier, ConnectorMetadata } from '@logto/schemas';
import { EmailRegister } from '@/containers/EmailForm';
import { SmsRegister } from '@/containers/PhoneForm';
import { PhoneRegister } from '@/containers/PhoneForm';
import SocialSignIn from '@/containers/SocialSignIn';
import { UsernameRegister } from '@/containers/UsernameForm';
@ -17,8 +17,8 @@ const Main = ({ signUpMethod, socialConnectors }: Props) => {
case 'email':
return <EmailRegister className={styles.main} />;
case 'sms':
return <SmsRegister className={styles.main} />;
case 'phone':
return <PhoneRegister className={styles.main} />;
case 'username':
return <UsernameRegister className={styles.main} />;

View file

@ -47,14 +47,14 @@ describe('<Register />', () => {
expect(queryByText('action.create_account')).not.toBeNull();
});
test('renders with sms passwordless as primary', async () => {
test('renders with phone passwordless as primary', async () => {
const { queryByText, container } = renderWithPageContext(
<SettingsProvider
settings={{
...mockSignInExperienceSettings,
signUp: {
...mockSignInExperienceSettings.signUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
},
}}
>
@ -67,14 +67,14 @@ describe('<Register />', () => {
expect(queryByText('action.create_account')).not.toBeNull();
});
test('render with email and sms passwordless', async () => {
test('render with email and phone passwordless', async () => {
const { queryByText, container } = renderWithPageContext(
<SettingsProvider
settings={{
...mockSignInExperienceSettings,
signUp: {
...mockSignInExperienceSettings.signUp,
identifiers: [SignInIdentifier.Email, SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone],
},
}}
>

View file

@ -13,7 +13,7 @@ jest.mock('i18next', () => ({
describe('<SecondaryRegister />', () => {
test('renders phone', async () => {
const { queryAllByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/register/sms']}>
<MemoryRouter initialEntries={['/register/phone']}>
<Routes>
<Route
path="/register/:method"
@ -23,7 +23,7 @@ describe('<SecondaryRegister />', () => {
...mockSignInExperienceSettings,
signUp: {
...mockSignInExperienceSettings.signUp,
identifiers: [SignInIdentifier.Sms],
identifiers: [SignInIdentifier.Phone],
},
}}
>

View file

@ -5,7 +5,7 @@ import { is } from 'superstruct';
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
import CreateAccount from '@/containers/CreateAccount';
import { EmailRegister } from '@/containers/EmailForm';
import { SmsRegister } from '@/containers/PhoneForm';
import { PhoneRegister } from '@/containers/PhoneForm';
import { useSieMethods } from '@/hooks/use-sie';
import ErrorPage from '@/pages/ErrorPage';
import { SignInMethodGuard, passcodeMethodGuard } from '@/types/guard';
@ -34,8 +34,8 @@ const SecondaryRegister = () => {
return (
<SecondaryPageWrapper title="action.create_account">
{method === SignInIdentifier.Sms ? (
<SmsRegister autoFocus />
{method === SignInIdentifier.Phone ? (
<PhoneRegister autoFocus />
) : method === SignInIdentifier.Email ? (
<EmailRegister autoFocus />
) : (

View file

@ -31,7 +31,7 @@ describe('<SecondarySignIn />', () => {
test('renders phone', async () => {
const { queryAllByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/sign-in/sms']}>
<MemoryRouter initialEntries={['/sign-in/phone']}>
<Routes>
<Route
path="/sign-in/:method"

View file

@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom';
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
import { EmailSignIn } from '@/containers/EmailForm';
import { SmsSignIn } from '@/containers/PhoneForm';
import { PhoneSignIn } from '@/containers/PhoneForm';
import UsernameSignIn from '@/containers/UsernameSignIn';
import { useSieMethods } from '@/hooks/use-sie';
import ErrorPage from '@/pages/ErrorPage';
@ -27,8 +27,8 @@ const SecondarySignIn = () => {
return (
<SecondaryPageWrapper title="action.sign_in">
{signInMethod.identifier === SignInIdentifier.Sms ? (
<SmsSignIn autoFocus signInMethod={signInMethod} />
{signInMethod.identifier === SignInIdentifier.Phone ? (
<PhoneSignIn autoFocus signInMethod={signInMethod} />
) : signInMethod.identifier === SignInIdentifier.Email ? (
<EmailSignIn autoFocus signInMethod={signInMethod} />
) : (

View file

@ -3,7 +3,7 @@ import type { SignIn as SignInType, ConnectorMetadata } from '@logto/schemas';
import { EmailSignIn } from '@/containers/EmailForm';
import EmailPassword from '@/containers/EmailPassword';
import { SmsSignIn } from '@/containers/PhoneForm';
import { PhoneSignIn } from '@/containers/PhoneForm';
import PhonePassword from '@/containers/PhonePassword';
import SocialSignIn from '@/containers/SocialSignIn';
import UsernameSignIn from '@/containers/UsernameSignIn';
@ -26,12 +26,12 @@ const Main = ({ signInMethod, socialConnectors }: Props) => {
return <EmailSignIn signInMethod={signInMethod} className={styles.main} />;
}
case SignInIdentifier.Sms: {
case SignInIdentifier.Phone: {
if (signInMethod.password && !signInMethod.verificationCode) {
return <PhonePassword className={styles.main} />;
}
return <SmsSignIn signInMethod={signInMethod} className={styles.main} />;
return <PhoneSignIn signInMethod={signInMethod} className={styles.main} />;
}
case SignInIdentifier.Username: {

View file

@ -6,7 +6,7 @@ import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider
import {
mockSignInExperienceSettings,
emailSignInMethod,
smsSignInMethod,
phoneSignInMethod,
} from '@/__mocks__/logto';
import { defaultSize } from '@/containers/SocialSignIn/SocialSignInList';
import SignIn from '@/pages/SignIn';
@ -78,10 +78,10 @@ describe('<SignIn />', () => {
expect(queryByText('action.sign_in')).not.toBeNull();
});
test('renders with sms passwordless as primary', async () => {
test('renders with phone passwordless as primary', async () => {
const { queryByText, container } = renderWithPageContext(
<SettingsProvider
settings={{ ...mockSignInExperienceSettings, signIn: { methods: [smsSignInMethod] } }}
settings={{ ...mockSignInExperienceSettings, signIn: { methods: [phoneSignInMethod] } }}
>
<MemoryRouter>
<SignIn />
@ -100,7 +100,7 @@ describe('<SignIn />', () => {
signIn: {
methods: [
{
...smsSignInMethod,
...phoneSignInMethod,
verificationCode: false,
password: true,
},

View file

@ -113,9 +113,9 @@ describe('SignInPassword', () => {
expect(queryByText('action.sign_in_via_passcode')).not.toBeNull();
});
test('/sign-in/sms/password', () => {
test('/sign-in/phone/password', () => {
const { queryByText, container } = renderWithPageContext(
<MemoryRouter initialEntries={['/sign-in/sms/password']}>
<MemoryRouter initialEntries={['/sign-in/phone/password']}>
<Routes>
<Route
path="/sign-in/:method/password"

View file

@ -9,7 +9,7 @@ import { useSieMethods } from '@/hooks/use-sie';
import ErrorPage from '@/pages/ErrorPage';
import { passcodeStateGuard } from '@/types/guard';
import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code';
import { isEmailOrSmsMethod } from '@/utils/sign-in-experience';
import { isEmailOrPhoneMethod } from '@/utils/sign-in-experience';
type Parameters = {
method?: string;
@ -22,14 +22,17 @@ const SignInPassword = () => {
const { signInMethods } = useSieMethods();
const methodSetting = signInMethods.find(({ identifier }) => identifier === method);
// Only Email and Sms method should use this page
if (!methodSetting || !isEmailOrSmsMethod(methodSetting.identifier) || !methodSetting.password) {
// Only Email and Phone method should use this page
if (
!methodSetting ||
!isEmailOrPhoneMethod(methodSetting.identifier) ||
!methodSetting.password
) {
return <ErrorPage />;
}
const invalidState = !is(state, passcodeStateGuard);
const value =
!invalidState && state[methodSetting.identifier === SignInIdentifier.Email ? 'email' : 'phone'];
const value = !invalidState && state[methodSetting.identifier];
if (!value) {
return (

View file

@ -15,12 +15,12 @@ export const passcodeStateGuard = s.object({
export const passcodeMethodGuard = s.union([
s.literal(SignInIdentifier.Email),
s.literal(SignInIdentifier.Sms),
s.literal(SignInIdentifier.Phone),
]);
export const SignInMethodGuard = s.union([
s.literal(SignInIdentifier.Email),
s.literal(SignInIdentifier.Sms),
s.literal(SignInIdentifier.Phone),
s.literal(SignInIdentifier.Username),
]);
@ -35,7 +35,7 @@ export const continueMethodGuard = s.union([
s.literal('password'),
s.literal('username'),
s.literal(SignInIdentifier.Email),
s.literal(SignInIdentifier.Sms),
s.literal(SignInIdentifier.Phone),
]);
export const usernameGuard = s.object({

View file

@ -23,7 +23,7 @@ export type SignInExperienceResponse = Omit<SignInExperience, 'socialSignInConne
socialConnectors: ConnectorMetadata[];
notification?: string;
forgotPassword: {
sms: boolean;
phone: boolean;
email: boolean;
};
};

Some files were not shown because too many files have changed in this diff Show more