From bc5c10c9b36f1f9eb8e8b01cc822540543d5d7ab Mon Sep 17 00:00:00 2001 From: I Nyoman Jyotisa Date: Mon, 11 Nov 2024 19:07:43 +0800 Subject: [PATCH 01/11] Create SMS delivery failure sign in flow in homepage --- src/languages/en.ts | 7 ++ src/languages/es.ts | 8 +++ src/pages/signin/SMSDeliveryFailurePage.tsx | 75 +++++++++++++++++++++ src/pages/signin/SignInPage.tsx | 18 +++-- src/types/onyx/Account.ts | 1 + 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/pages/signin/SMSDeliveryFailurePage.tsx diff --git a/src/languages/en.ts b/src/languages/en.ts index 0fdf0b8fdee4..43cbc8aaa865 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -472,6 +472,7 @@ const translations = { links: 'Links', days: 'days', rename: 'Rename', + validate: 'Validate', }, location: { useCurrent: 'Use current location', @@ -1816,6 +1817,12 @@ const translations = { onceTheAbove: 'Once the above steps are completed, please reach out to ', toUnblock: ' to unblock your login.', }, + smsDeliveryFailurePage: { + smsDeliveryFailureMessage: ({login}: OurEmailProviderParams) => + `We have temporarily suspended sending SMS to ${login} because we were unable to deliver SMS messages to your phone number. To try again, please click the button:`, + validateFailed: ({time}: UntilTimeParams) => `Reset failed because it hasn’t been 24 hours since our last attempt. Please wait ${time} before trying again.`, + validateSuccess: 'Your number has been cleared successfully and we can send you a new magic code to sign in', + }, welcomeSignUpForm: { join: 'Join', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 712423cf0776..a4fa00364a6e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -462,6 +462,7 @@ const translations = { sent: 'Enviado', links: 'Enlaces', days: 'días', + validate: 'Validar', }, connectionComplete: { title: 'Conexión completa', @@ -1819,6 +1820,13 @@ const translations = { onceTheAbove: 'Una vez completados los pasos anteriores, ponte en contacto con ', toUnblock: ' para desbloquear el inicio de sesión.', }, + smsDeliveryFailurePage: { + smsDeliveryFailureMessage: ({login}: OurEmailProviderParams) => + `Hemos suspendido temporalmente el envío de SMS a ${login} porque no pudimos entregar los mensajes SMS a tu número de teléfono. Para intentarlo de nuevo, por favor haz clic en el botón:`, + validateFailed: ({time}: UntilTimeParams) => + `El restablecimiento falló porque no han pasado 24 horas desde nuestro último intento. Por favor espera ${time} antes de intentarlo nuevamente.`, + validateSuccess: 'Tu número ha sido restablecido exitosamente y podemos enviarte un nuevo código mágico para iniciar sesión', + }, welcomeSignUpForm: { join: 'Unirse', }, diff --git a/src/pages/signin/SMSDeliveryFailurePage.tsx b/src/pages/signin/SMSDeliveryFailurePage.tsx new file mode 100644 index 000000000000..f068500163f8 --- /dev/null +++ b/src/pages/signin/SMSDeliveryFailurePage.tsx @@ -0,0 +1,75 @@ +import {Str} from 'expensify-common'; +import React, {useEffect, useMemo} from 'react'; +import {Keyboard, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import Text from '@components/Text'; +import useKeyboardState from '@hooks/useKeyboardState'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Credentials} from '@src/types/onyx'; + +type SMSDeliveryFailurePageOnyxProps = { + /** The credentials of the logged in person */ + credentials: OnyxEntry; +}; +type SMSDeliveryFailurePageProps = SMSDeliveryFailurePageOnyxProps; +function SMSDeliveryFailurePage({credentials}: SMSDeliveryFailurePageProps) { + const styles = useThemeStyles(); + const {isKeyboardShown} = useKeyboardState(); + const {translate} = useLocalize(); + const login = useMemo(() => { + if (!credentials?.login) { + return ''; + } + return Str.isSMSLogin(credentials.login) ? Str.removeSMSDomain(credentials.login) : credentials.login; + }, [credentials?.login]); + // This view doesn't have a field for user input, so dismiss the device keyboard if shown + useEffect(() => { + if (!isKeyboardShown) { + return; + } + Keyboard.dismiss(); + }, [isKeyboardShown]); + return ( + <> + + + {translate('smsDeliveryFailurePage.smsDeliveryFailureMessage', {login})} + + + + {}} + message={''} + isAlertVisible={false} + buttonStyles={styles.mt3} + containerStyles={[styles.mh0]} + /> + + + Session.clearSignInData()} + role="button" + accessibilityLabel={translate('common.back')} + // disable hover dim for switch + hoverDimmingValue={1} + pressDimmingValue={0.2} + > + {translate('common.back')} + + + + ); +} +SMSDeliveryFailurePage.displayName = 'SMSDeliveryFailurePage'; +export default withOnyx({ + credentials: {key: ONYXKEYS.CREDENTIALS}, +})(SMSDeliveryFailurePage); diff --git a/src/pages/signin/SignInPage.tsx b/src/pages/signin/SignInPage.tsx index 1068cf97197e..64a052e6a3ef 100644 --- a/src/pages/signin/SignInPage.tsx +++ b/src/pages/signin/SignInPage.tsx @@ -33,6 +33,7 @@ import type {InputHandle} from './LoginForm/types'; import SignInPageLayout from './SignInPageLayout'; import type {SignInPageLayoutRef} from './SignInPageLayout/types'; import SignUpWelcomeForm from './SignUpWelcomeForm'; +import SMSDeliveryFailurePage from './SMSDeliveryFailurePage'; import UnlinkLoginForm from './UnlinkLoginForm'; import ValidateCodeForm from './ValidateCodeForm'; import type {BaseValidateCodeFormRef} from './ValidateCodeForm/BaseValidateCodeForm'; @@ -62,6 +63,7 @@ type SignInPageRef = { type RenderOption = { shouldShowLoginForm: boolean; shouldShowEmailDeliveryFailurePage: boolean; + shouldShowSMSDeliveryFailurePage: boolean; shouldShowUnlinkLoginForm: boolean; shouldShowValidateCodeForm: boolean; shouldShowChooseSSOOrMagicCode: boolean; @@ -90,6 +92,7 @@ type GetRenderOptionsParams = { * @param isUsingMagicCode * @param hasInitiatedSAMLLogin * @param hasEmailDeliveryFailure + * @param hasSMSDeliveryFailure */ function getRenderOptions({ hasLogin, @@ -105,6 +108,7 @@ function getRenderOptions({ const isSAMLEnabled = !!account?.isSAMLEnabled; const isSAMLRequired = !!account?.isSAMLRequired; const hasEmailDeliveryFailure = !!account?.hasEmailDeliveryFailure; + const hasSMSDeliveryFailure = !!account?.hasSMSDeliveryFailure; // True, if the user has SAML required, and we haven't yet initiated SAML for their account const shouldInitiateSAMLLogin = hasAccount && hasLogin && isSAMLRequired && !hasInitiatedSAMLLogin && !!account.isLoading; @@ -121,13 +125,15 @@ function getRenderOptions({ const shouldShouldSignUpWelcomeForm = !!credentials?.login && !account?.validated && !account?.accountExists && !account?.domainControlled; const shouldShowLoginForm = !shouldShowAnotherLoginPageOpenedMessage && !hasLogin && !hasValidateCode; const shouldShowEmailDeliveryFailurePage = hasLogin && hasEmailDeliveryFailure && !shouldShowChooseSSOOrMagicCode && !shouldInitiateSAMLLogin; - const isUnvalidatedSecondaryLogin = hasLogin && !isPrimaryLogin && !account?.validated && !hasEmailDeliveryFailure; + const shouldShowSMSDeliveryFailurePage = hasLogin && hasSMSDeliveryFailure && !shouldShowChooseSSOOrMagicCode && !shouldInitiateSAMLLogin; + const isUnvalidatedSecondaryLogin = hasLogin && !isPrimaryLogin && !account?.validated && !hasEmailDeliveryFailure && !hasSMSDeliveryFailure; const shouldShowValidateCodeForm = !shouldShouldSignUpWelcomeForm && hasAccount && (hasLogin || hasValidateCode) && !isUnvalidatedSecondaryLogin && !hasEmailDeliveryFailure && + !hasSMSDeliveryFailure && !shouldShowChooseSSOOrMagicCode && !isSAMLRequired; const shouldShowWelcomeHeader = shouldShowLoginForm || shouldShowValidateCodeForm || shouldShowChooseSSOOrMagicCode || isUnvalidatedSecondaryLogin || shouldShouldSignUpWelcomeForm; @@ -137,6 +143,7 @@ function getRenderOptions({ return { shouldShowLoginForm, shouldShowEmailDeliveryFailurePage, + shouldShowSMSDeliveryFailurePage, shouldShowUnlinkLoginForm: !shouldShouldSignUpWelcomeForm && isUnvalidatedSecondaryLogin, shouldShowValidateCodeForm, shouldShowChooseSSOOrMagicCode, @@ -200,6 +207,7 @@ function SignInPage({credentials, account, activeClients = [], preferredLocale, const { shouldShowLoginForm, shouldShowEmailDeliveryFailurePage, + shouldShowSMSDeliveryFailurePage, shouldShowUnlinkLoginForm, shouldShowValidateCodeForm, shouldShowChooseSSOOrMagicCode, @@ -249,11 +257,11 @@ function SignInPage({credentials, account, activeClients = [], preferredLocale, ? `${translate('welcomeText.welcome')} ${translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` : translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); } - } else if (shouldShowUnlinkLoginForm || shouldShowEmailDeliveryFailurePage || shouldShowChooseSSOOrMagicCode) { + } else if (shouldShowUnlinkLoginForm || shouldShowEmailDeliveryFailurePage || shouldShowChooseSSOOrMagicCode || shouldShowSMSDeliveryFailurePage) { welcomeHeader = shouldUseNarrowLayout ? headerText : translate('welcomeText.welcome'); // Don't show any welcome text if we're showing the user the email delivery failed view - if (shouldShowEmailDeliveryFailurePage || shouldShowChooseSSOOrMagicCode) { + if (shouldShowEmailDeliveryFailurePage || shouldShowChooseSSOOrMagicCode || shouldShowSMSDeliveryFailurePage) { welcomeText = ''; } } else if (shouldShouldSignUpWelcomeForm) { @@ -273,7 +281,8 @@ function SignInPage({credentials, account, activeClients = [], preferredLocale, const navigateBack = () => { if ( shouldShouldSignUpWelcomeForm || - (!shouldShowAnotherLoginPageOpenedMessage && (shouldShowEmailDeliveryFailurePage || shouldShowUnlinkLoginForm || shouldShowChooseSSOOrMagicCode)) + (!shouldShowAnotherLoginPageOpenedMessage && + (shouldShowEmailDeliveryFailurePage || shouldShowUnlinkLoginForm || shouldShowChooseSSOOrMagicCode || shouldShowSMSDeliveryFailurePage)) ) { Session.clearSignInData(); return; @@ -332,6 +341,7 @@ function SignInPage({credentials, account, activeClients = [], preferredLocale, {shouldShowUnlinkLoginForm && } {shouldShowChooseSSOOrMagicCode && } {shouldShowEmailDeliveryFailurePage && } + {shouldShowSMSDeliveryFailurePage && } )} diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index 3902d67882c4..d31c23b2e5a5 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -70,6 +70,7 @@ type Account = { /** Is this account having trouble receiving emails? */ hasEmailDeliveryFailure?: boolean; + hasSMSDeliveryFailure?: boolean; /** URL to the assigned guide's appointment booking calendar */ guideCalendarLink?: string; From 9710a7995ef09b67ed68ad0394209ae0ccd6b551 Mon Sep 17 00:00:00 2001 From: I Nyoman Jyotisa Date: Tue, 12 Nov 2024 13:56:00 +0800 Subject: [PATCH 02/11] SMS delivery failure flow --- src/languages/en.ts | 2 - src/languages/es.ts | 3 - .../ResetPhoneNumberFailureParams.ts | 5 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Session/index.ts | 14 +++ src/pages/signin/SMSDeliveryFailurePage.tsx | 95 +++++++++++++------ src/pages/signin/SignInPage.tsx | 4 +- src/types/onyx/Account.ts | 8 ++ 9 files changed, 100 insertions(+), 34 deletions(-) create mode 100644 src/libs/API/parameters/ResetPhoneNumberFailureParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 43cbc8aaa865..f86e20178dfb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1820,8 +1820,6 @@ const translations = { smsDeliveryFailurePage: { smsDeliveryFailureMessage: ({login}: OurEmailProviderParams) => `We have temporarily suspended sending SMS to ${login} because we were unable to deliver SMS messages to your phone number. To try again, please click the button:`, - validateFailed: ({time}: UntilTimeParams) => `Reset failed because it hasn’t been 24 hours since our last attempt. Please wait ${time} before trying again.`, - validateSuccess: 'Your number has been cleared successfully and we can send you a new magic code to sign in', }, welcomeSignUpForm: { join: 'Join', diff --git a/src/languages/es.ts b/src/languages/es.ts index a4fa00364a6e..4552532dbc06 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1823,9 +1823,6 @@ const translations = { smsDeliveryFailurePage: { smsDeliveryFailureMessage: ({login}: OurEmailProviderParams) => `Hemos suspendido temporalmente el envío de SMS a ${login} porque no pudimos entregar los mensajes SMS a tu número de teléfono. Para intentarlo de nuevo, por favor haz clic en el botón:`, - validateFailed: ({time}: UntilTimeParams) => - `El restablecimiento falló porque no han pasado 24 horas desde nuestro último intento. Por favor espera ${time} antes de intentarlo nuevamente.`, - validateSuccess: 'Tu número ha sido restablecido exitosamente y podemos enviarte un nuevo código mágico para iniciar sesión', }, welcomeSignUpForm: { join: 'Unirse', diff --git a/src/libs/API/parameters/ResetPhoneNumberFailureParams.ts b/src/libs/API/parameters/ResetPhoneNumberFailureParams.ts new file mode 100644 index 000000000000..dfc4751510a2 --- /dev/null +++ b/src/libs/API/parameters/ResetPhoneNumberFailureParams.ts @@ -0,0 +1,5 @@ +type ResetPhoneNumberFailureParams = { + email: string; +}; + +export default ResetPhoneNumberFailureParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index fb5558fb0350..2d4bb7e97480 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -349,3 +349,4 @@ export type {default as UpdateQuickbooksDesktopCompanyCardExpenseAccountTypePara export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemParams'; export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams'; export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; +export type {default as ResetPhoneNumberFailureParams} from './ResetPhoneNumberFailureParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b8b4bb749701..c907ae5ecc91 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -438,6 +438,7 @@ const WRITE_COMMANDS = { SELF_TOUR_VIEWED: 'SelfTourViewed', UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName', UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite', + RESET_PHONE_NUMBER_FAILURE: 'ResetPhoneNumberFailure', } as const; type WriteCommand = ValueOf; @@ -762,6 +763,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams; [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; + [WRITE_COMMANDS.RESET_PHONE_NUMBER_FAILURE]: Parameters.ResetPhoneNumberFailureParams; [WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams; [WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index d75c5064f93a..4c96824c2afd 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -16,6 +16,7 @@ import type { RequestAccountValidationLinkParams, RequestNewValidateCodeParams, RequestUnlinkValidationLinkParams, + ResetPhoneNumberFailureParams, SignInUserWithLinkParams, SignUpUserParams, UnlinkLoginParams, @@ -376,6 +377,9 @@ function signInAttemptState(): OnyxData { isLoading: true, message: null, loadingForm: CONST.FORMS.LOGIN_FORM, + hasSMSDeliveryFailure: null, + isResetPhoneNumberFailureSuccess: null, + resetPhoneNumberFailureMessage: null, }, }, ], @@ -1126,6 +1130,15 @@ const canAnonymousUserAccessRoute = (route: string) => { return false; }; +/** + * To reset SMS delivery failure + */ +function resetPhoneNumberFailure(email: string) { + const params: ResetPhoneNumberFailureParams = {email}; + + API.write(WRITE_COMMANDS.RESET_PHONE_NUMBER_FAILURE, params); +} + export { beginSignIn, beginAppleSignIn, @@ -1163,4 +1176,5 @@ export { hasStashedSession, signUpUser, signInAfterTransitionFromOldDot, + resetPhoneNumberFailure, }; diff --git a/src/pages/signin/SMSDeliveryFailurePage.tsx b/src/pages/signin/SMSDeliveryFailurePage.tsx index f068500163f8..57e3fc10608b 100644 --- a/src/pages/signin/SMSDeliveryFailurePage.tsx +++ b/src/pages/signin/SMSDeliveryFailurePage.tsx @@ -1,41 +1,83 @@ import {Str} from 'expensify-common'; import React, {useEffect, useMemo} from 'react'; import {Keyboard, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; -import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import {useOnyx} from 'react-native-onyx'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@userActions/Session'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Credentials} from '@src/types/onyx'; +import Button from '@components/Button'; -type SMSDeliveryFailurePageOnyxProps = { - /** The credentials of the logged in person */ - credentials: OnyxEntry; -}; -type SMSDeliveryFailurePageProps = SMSDeliveryFailurePageOnyxProps; -function SMSDeliveryFailurePage({credentials}: SMSDeliveryFailurePageProps) { +function SMSDeliveryFailurePage() { const styles = useThemeStyles(); const {isKeyboardShown} = useKeyboardState(); const {translate} = useLocalize(); + const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const login = useMemo(() => { if (!credentials?.login) { return ''; } return Str.isSMSLogin(credentials.login) ? Str.removeSMSDomain(credentials.login) : credentials.login; }, [credentials?.login]); - // This view doesn't have a field for user input, so dismiss the device keyboard if shown + + const isResetPhoneNumberFailureSuccess = account?.isResetPhoneNumberFailureSuccess; + const resetPhoneNumberFailureMessage = account?.resetPhoneNumberFailureMessage; + useEffect(() => { if (!isKeyboardShown) { return; } Keyboard.dismiss(); }, [isKeyboardShown]); + + if(isResetPhoneNumberFailureSuccess){ + return ( + <> + + + {resetPhoneNumberFailureMessage} + + + +