From 829a8af1fd5954176bd436a208be06fc5dabbc6e Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:14:48 -0700 Subject: [PATCH 01/16] Create usePermissions hook --- src/hooks/usePermissions.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/hooks/usePermissions.js diff --git a/src/hooks/usePermissions.js b/src/hooks/usePermissions.js new file mode 100644 index 000000000000..e69de29bb2d1 From 9c70f342d311831f226adee6f797285341d75cfc Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:15:35 -0700 Subject: [PATCH 02/16] Create useLocalize hook --- src/components/withLocalize.js | 2 +- src/hooks/useLocalize.js | 6 ++++++ src/hooks/usePermissions.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useLocalize.js diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 4cbdda876231..def7110c1b40 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -179,4 +179,4 @@ export default function withLocalize(WrappedComponent) { return WithLocalize; } -export {withLocalizePropTypes, Provider as LocaleContextProvider}; +export {withLocalizePropTypes, Provider as LocaleContextProvider, LocaleContext}; diff --git a/src/hooks/useLocalize.js b/src/hooks/useLocalize.js new file mode 100644 index 000000000000..5df65296886b --- /dev/null +++ b/src/hooks/useLocalize.js @@ -0,0 +1,6 @@ +import {useContext} from 'react'; +import {LocaleContext} from '../components/withLocalize'; + +export default function useLocalize() { + useContext(LocaleContext); +} diff --git a/src/hooks/usePermissions.js b/src/hooks/usePermissions.js index e69de29bb2d1..bd6f085bc924 100644 --- a/src/hooks/usePermissions.js +++ b/src/hooks/usePermissions.js @@ -0,0 +1,29 @@ +import _ from 'underscore'; +import {useMemo} from 'react'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../ONYXKEYS'; +import Permissions from '../libs/Permissions'; + +let betas = []; +// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs +Onyx.connect({ + key: ONYXKEYS.BETAS, + callback: (val) => (betas = val), +}); + +export default function usePermissions() { + const result = useMemo( + () => + _.reduce( + Permissions, + (memo, checkerFunction, beta) => { + // eslint-disable-next-line no-param-reassign + memo[beta] = checkerFunction(betas); + return memo; + }, + {}, + ), + betas, + ); + return result; +} From debfaddd4267dde5eba1f0567f9cb10e69cda7e4 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:15:56 -0700 Subject: [PATCH 03/16] Migrate SignInPage to functional component --- src/pages/signin/SignInPage.js | 248 +++++++++++++++++---------------- 1 file changed, 127 insertions(+), 121 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index ac1759661ff1..8a9e236072bf 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, {useEffect, useMemo} from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; @@ -6,23 +6,21 @@ import Str from 'expensify-common/lib/str'; import {SafeAreaView} from 'react-native-safe-area-context'; import ONYXKEYS from '../../ONYXKEYS'; import styles from '../../styles/styles'; -import compose from '../../libs/compose'; import SignInPageLayout from './SignInPageLayout'; import LoginForm from './LoginForm'; import PasswordForm from './PasswordForm'; import ValidateCodeForm from './ValidateCodeForm'; import ResendValidationForm from './ResendValidationForm'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import Performance from '../../libs/Performance'; import * as App from '../../libs/actions/App'; -import Permissions from '../../libs/Permissions'; import UnlinkLoginForm from './UnlinkLoginForm'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import * as Localize from '../../libs/Localize'; +import useLocalize from '../../hooks/useLocalize'; +import usePermissions from '../../hooks/usePermissions'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; +import Log from '../../libs/Log'; const propTypes = { - /* Onyx Props */ - /** The details about the account that the user is signing in with */ account: PropTypes.shape({ /** Error to display when there is an account error returned */ @@ -33,10 +31,10 @@ const propTypes = { /** The primaryLogin associated with the account */ primaryLogin: PropTypes.string, - }), - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), + /** Has the user pressed the forgot password button? */ + forgotPassword: PropTypes.bool, + }), /** The credentials of the person signing in */ credentials: PropTypes.shape({ @@ -44,138 +42,146 @@ const propTypes = { password: PropTypes.string, twoFactorAuthCode: PropTypes.string, }), - - ...withLocalizePropTypes, - - ...windowDimensionsPropTypes, }; const defaultProps = { account: {}, - betas: [], credentials: {}, }; -class SignInPage extends Component { - componentDidMount() { - Performance.measureTTI(); +const SignInPage = (props) => { + const {translate, formatPhoneNumber} = useLocalize(); + const {canUsePasswordlessLogins} = usePermissions(); + const {isSmallScreenWidth} = useWindowDimensions(); + useEffect(() => Performance.measureTTI(), []); + useEffect(() => { App.setLocale(Localize.getDevicePreferredLocale()); - } - - render() { - // Show the login form if - // - A login has not been entered yet - // - AND a validateCode has not been cached with sign in link - const showLoginForm = !this.props.credentials.login && !this.props.credentials.validateCode; - - // Show the unlink form if - // - A login has been entered - // - AND the login is not the primary login - // - AND the login is not validated - const showUnlinkLoginForm = - this.props.credentials.login && this.props.account.primaryLogin && this.props.account.primaryLogin !== this.props.credentials.login && !this.props.account.validated; - - // Show the old password form if - // - A login has been entered - // - AND an account exists and is validated for this login - // - AND a password hasn't been entered yet - // - AND haven't forgotten password - // - AND the login isn't an unvalidated secondary login - // - AND the user is NOT on the passwordless beta - const showPasswordForm = - Boolean(this.props.credentials.login) && - this.props.account.validated && - !this.props.credentials.password && - !this.props.account.forgotPassword && - !showUnlinkLoginForm && - !Permissions.canUsePasswordlessLogins(this.props.betas); - - // Show the new magic code / validate code form if - // - A login has been entered or a validateCode has been cached from sign in link - // - AND the login isn't an unvalidated secondary login - // - AND the user is on the 'passwordless' beta - const showValidateCodeForm = (this.props.credentials.login || this.props.credentials.validateCode) && !showUnlinkLoginForm && Permissions.canUsePasswordlessLogins(this.props.betas); - - // Show the resend validation link form if - // - A login has been entered - // - AND is not validated or password is forgotten - // - AND the login isn't an unvalidated secondary login - // - AND user is not on 'passwordless' beta - const showResendValidationForm = - Boolean(this.props.credentials.login) && - (!this.props.account.validated || this.props.account.forgotPassword) && - !showUnlinkLoginForm && - !Permissions.canUsePasswordlessLogins(this.props.betas); - + }, []); + + /* + * Show the login form if: + * - A login has not been entered yet + * - AND a validateCode has not been cached with the sign in link + */ + const shouldShowLoginForm = !props.credentials.login && !props.credentials.validateCode; + + /* + * Show the unlink form if: + * - A login has been entered + * - AND the login is not the primary login for the account + * - AND the login is not validated + */ + const isUnvalidatedSecondaryLogin = props.account.primaryLogin && props.account.primaryLogin !== props.credentials.login && !props.account.validated; + const shouldShowUnlinkLoginForm = Boolean(props.credentials.login) && isUnvalidatedSecondaryLogin; + + /* Show the old password form if + * - A login has been entered + * - AND an account exists and is validated for this login + * - AND a password hasn't been entered yet + * - AND haven't forgotten password + * - AND the login isn't an unvalidated secondary login + * - AND the user is NOT on the passwordless beta + */ + const shouldShowPasswordForm = + Boolean(props.credentials.login) && + props.account.validated && + !props.credentials.password && + !props.account.forgotPassword && + !shouldShowUnlinkLoginForm && + !canUsePasswordlessLogins; + + /* Show the new magic code / validate code form if + * - A login has been entered or a validateCode has been cached from sign in link + * - AND the login isn't an unvalidated secondary login + * - AND the user is on the 'passwordless' beta + */ + const shouldShowValidateCodeForm = (props.credentials.login || props.credentials.validateCode) && !shouldShowUnlinkLoginForm && canUsePasswordlessLogins; + + /* Show the resend validation link form if + * - A login has been entered + * - AND is not validated or password is forgotten + * - AND the login isn't an unvalidated secondary login + * - AND user is not on 'passwordless' beta + */ + const shouldShowResendValidationForm = + Boolean(props.credentials.login) && (!props.account.validated || props.account.forgotPassword) && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; + + const {welcomeHeader, welcomeText} = useMemo(() => { let welcomeHeader = ''; let welcomeText = ''; - if (showValidateCodeForm) { - if (this.props.account.requiresTwoFactorAuth) { + if (shouldShowValidateCodeForm) { + if (props.account.requiresTwoFactorAuth) { // We will only know this after a user signs in successfully, without their 2FA code - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcomeBack'); - welcomeText = this.props.translate('validateCodeForm.enterAuthenticatorCode'); + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = translate('validateCodeForm.enterAuthenticatorCode'); } else { - const userLogin = Str.removeSMSDomain(lodashGet(this.props, 'credentials.login', '')); + const userLogin = Str.removeSMSDomain(lodashGet(props, 'credentials.login', '')); // replacing spaces with "hard spaces" to prevent breaking the number - const userLoginToDisplay = Str.isSMSLogin(userLogin) ? this.props.formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; - if (this.props.account.validated) { - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcomeBack'); - welcomeText = this.props.isSmallScreenWidth - ? `${this.props.translate('welcomeText.welcomeBack')} ${this.props.translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` - : this.props.translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); + const userLoginToDisplay = Str.isSMSLogin(userLogin) ? formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; + if (props.account.validated) { + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = isSmallScreenWidth + ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` + : translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); } else { - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcome'); - welcomeText = this.props.isSmallScreenWidth - ? `${this.props.translate('welcomeText.welcome')} ${this.props.translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` - : this.props.translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); + welcomeText = isSmallScreenWidth + ? `${translate('welcomeText.welcome')} ${translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` + : translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); } } - } else if (showPasswordForm) { - welcomeHeader = this.props.isSmallScreenWidth ? '' : this.props.translate('welcomeText.welcomeBack'); - welcomeText = this.props.isSmallScreenWidth - ? `${this.props.translate('welcomeText.welcomeBack')} ${this.props.translate('welcomeText.enterPassword')}` - : this.props.translate('welcomeText.enterPassword'); - } else if (showUnlinkLoginForm) { - welcomeHeader = this.props.isSmallScreenWidth ? this.props.translate('login.hero.header') : this.props.translate('welcomeText.welcomeBack'); - } else if (!showResendValidationForm) { - welcomeHeader = this.props.isSmallScreenWidth ? this.props.translate('login.hero.header') : this.props.translate('welcomeText.getStarted'); - welcomeText = this.props.isSmallScreenWidth ? this.props.translate('welcomeText.getStarted') : ''; + } else if (shouldShowPasswordForm) { + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.enterPassword')}` : translate('welcomeText.enterPassword'); + } else if (shouldShowUnlinkLoginForm) { + welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.welcomeBack'); + } else if (!shouldShowResendValidationForm) { + welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.getStarted'); + welcomeText = isSmallScreenWidth ? translate('welcomeText.getStarted') : ''; + } else { + Log.warn('SignInPage in unexpected state!'); } - - return ( - - - {/* LoginForm and PasswordForm must use the isVisible prop. This keeps them mounted, but visually hidden + return {welcomeHeader, welcomeText}; + }, [ + shouldShowValidateCodeForm, + shouldShowPasswordForm, + shouldShowUnlinkLoginForm, + shouldShowResendValidationForm, + props.account.requiresTwoFactorAuth, + props.account.validated, + translate, + formatPhoneNumber, + ]); + + return ( + + + {/* LoginForm and PasswordForm must use the isVisible prop. This keeps them mounted, but visually hidden so that password managers can access the values. Conditionally rendering these components will break this feature. */} - - {showValidateCodeForm ? : } - {showResendValidationForm && } - {showUnlinkLoginForm && } - - - ); - } -} + + {shouldShowValidateCodeForm ? : } + {shouldShowResendValidationForm && } + {shouldShowUnlinkLoginForm && } + + + ); +}; SignInPage.propTypes = propTypes; SignInPage.defaultProps = defaultProps; +SignInPage.displayName = 'SignInPage'; -export default compose( - withLocalize, - withWindowDimensions, - withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - betas: {key: ONYXKEYS.BETAS}, - credentials: {key: ONYXKEYS.CREDENTIALS}, - }), -)(SignInPage); +export default withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + credentials: {key: ONYXKEYS.CREDENTIALS}, +})(SignInPage); From d6c6f734905a3db85da335933a4881df42d4a9f3 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:47:52 -0700 Subject: [PATCH 04/16] Destructure props --- src/pages/signin/SignInPage.js | 63 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 8a9e236072bf..480df7cfdd7e 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -34,6 +34,9 @@ const propTypes = { /** Has the user pressed the forgot password button? */ forgotPassword: PropTypes.bool, + + /** Does this account require 2FA? */ + requiresTwoFactorAuth: PropTypes.bool, }), /** The credentials of the person signing in */ @@ -41,6 +44,7 @@ const propTypes = { login: PropTypes.string, password: PropTypes.string, twoFactorAuthCode: PropTypes.string, + validateCode: PropTypes.string, }), }; @@ -49,7 +53,7 @@ const defaultProps = { credentials: {}, }; -const SignInPage = (props) => { +const SignInPage = ({account, credentials}) => { const {translate, formatPhoneNumber} = useLocalize(); const {canUsePasswordlessLogins} = usePermissions(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -64,7 +68,7 @@ const SignInPage = (props) => { * - A login has not been entered yet * - AND a validateCode has not been cached with the sign in link */ - const shouldShowLoginForm = !props.credentials.login && !props.credentials.validateCode; + const shouldShowLoginForm = !credentials.login && !credentials.validateCode; /* * Show the unlink form if: @@ -72,8 +76,8 @@ const SignInPage = (props) => { * - AND the login is not the primary login for the account * - AND the login is not validated */ - const isUnvalidatedSecondaryLogin = props.account.primaryLogin && props.account.primaryLogin !== props.credentials.login && !props.account.validated; - const shouldShowUnlinkLoginForm = Boolean(props.credentials.login) && isUnvalidatedSecondaryLogin; + const isUnvalidatedSecondaryLogin = account.primaryLogin && account.primaryLogin !== credentials.login && !account.validated; + const shouldShowUnlinkLoginForm = Boolean(credentials.login) && isUnvalidatedSecondaryLogin; /* Show the old password form if * - A login has been entered @@ -84,19 +88,14 @@ const SignInPage = (props) => { * - AND the user is NOT on the passwordless beta */ const shouldShowPasswordForm = - Boolean(props.credentials.login) && - props.account.validated && - !props.credentials.password && - !props.account.forgotPassword && - !shouldShowUnlinkLoginForm && - !canUsePasswordlessLogins; + Boolean(credentials.login) && account.validated && !credentials.password && !account.forgotPassword && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; /* Show the new magic code / validate code form if * - A login has been entered or a validateCode has been cached from sign in link * - AND the login isn't an unvalidated secondary login * - AND the user is on the 'passwordless' beta */ - const shouldShowValidateCodeForm = (props.credentials.login || props.credentials.validateCode) && !shouldShowUnlinkLoginForm && canUsePasswordlessLogins; + const shouldShowValidateCodeForm = (credentials.login || credentials.validateCode) && !shouldShowUnlinkLoginForm && canUsePasswordlessLogins; /* Show the resend validation link form if * - A login has been entered @@ -104,55 +103,55 @@ const SignInPage = (props) => { * - AND the login isn't an unvalidated secondary login * - AND user is not on 'passwordless' beta */ - const shouldShowResendValidationForm = - Boolean(props.credentials.login) && (!props.account.validated || props.account.forgotPassword) && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; + const shouldShowResendValidationForm = Boolean(credentials.login) && (!account.validated || account.forgotPassword) && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; const {welcomeHeader, welcomeText} = useMemo(() => { - let welcomeHeader = ''; - let welcomeText = ''; + let header = ''; + let text = ''; if (shouldShowValidateCodeForm) { - if (props.account.requiresTwoFactorAuth) { + if (account.requiresTwoFactorAuth) { // We will only know this after a user signs in successfully, without their 2FA code - welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - welcomeText = translate('validateCodeForm.enterAuthenticatorCode'); + header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + text = translate('validateCodeForm.enterAuthenticatorCode'); } else { const userLogin = Str.removeSMSDomain(lodashGet(props, 'credentials.login', '')); // replacing spaces with "hard spaces" to prevent breaking the number const userLoginToDisplay = Str.isSMSLogin(userLogin) ? formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; - if (props.account.validated) { - welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - welcomeText = isSmallScreenWidth + if (account.validated) { + header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + text = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` : translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); } else { - welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); - welcomeText = isSmallScreenWidth + header = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); + text = isSmallScreenWidth ? `${translate('welcomeText.welcome')} ${translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` : translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); } } } else if (shouldShowPasswordForm) { - welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - welcomeText = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.enterPassword')}` : translate('welcomeText.enterPassword'); + header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + text = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.enterPassword')}` : translate('welcomeText.enterPassword'); } else if (shouldShowUnlinkLoginForm) { - welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.welcomeBack'); + header = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.welcomeBack'); } else if (!shouldShowResendValidationForm) { - welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.getStarted'); - welcomeText = isSmallScreenWidth ? translate('welcomeText.getStarted') : ''; + header = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.getStarted'); + text = isSmallScreenWidth ? translate('welcomeText.getStarted') : ''; } else { Log.warn('SignInPage in unexpected state!'); } - return {welcomeHeader, welcomeText}; + return {welcomeHeader: header, welcomeText: text}; }, [ shouldShowValidateCodeForm, shouldShowPasswordForm, shouldShowUnlinkLoginForm, shouldShowResendValidationForm, - props.account.requiresTwoFactorAuth, - props.account.validated, + account.requiresTwoFactorAuth, + account.validated, translate, formatPhoneNumber, + isSmallScreenWidth, ]); return ( @@ -167,7 +166,7 @@ const SignInPage = (props) => { so that password managers can access the values. Conditionally rendering these components will break this feature. */} {shouldShowValidateCodeForm ? : } {shouldShowResendValidationForm && } From c2288d6ee3aaee90e0872e2096fb2a10ad686214 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:49:06 -0700 Subject: [PATCH 05/16] Remove unnecessary lodashGet --- src/pages/signin/SignInPage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 480df7cfdd7e..b933e91d3f93 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -1,7 +1,6 @@ import React, {useEffect, useMemo} from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import {SafeAreaView} from 'react-native-safe-area-context'; import ONYXKEYS from '../../ONYXKEYS'; @@ -114,7 +113,7 @@ const SignInPage = ({account, credentials}) => { header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); text = translate('validateCodeForm.enterAuthenticatorCode'); } else { - const userLogin = Str.removeSMSDomain(lodashGet(props, 'credentials.login', '')); + const userLogin = Str.removeSMSDomain(credentials.login || ''); // replacing spaces with "hard spaces" to prevent breaking the number const userLoginToDisplay = Str.isSMSLogin(userLogin) ? formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; From eacbc0d782fb73869aeec5af6ba9f080030cca8c Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:51:12 -0700 Subject: [PATCH 06/16] Fix useLocalize --- src/hooks/useLocalize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useLocalize.js b/src/hooks/useLocalize.js index 5df65296886b..9ad5048729bd 100644 --- a/src/hooks/useLocalize.js +++ b/src/hooks/useLocalize.js @@ -2,5 +2,5 @@ import {useContext} from 'react'; import {LocaleContext} from '../components/withLocalize'; export default function useLocalize() { - useContext(LocaleContext); + return useContext(LocaleContext); } From 5d9a9141c911f127b13a5fb396d96f2c4480317b Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 14:58:24 -0700 Subject: [PATCH 07/16] Fix missing dependency --- src/pages/signin/SignInPage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index b933e91d3f93..c05f16bf0722 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -148,9 +148,10 @@ const SignInPage = ({account, credentials}) => { shouldShowResendValidationForm, account.requiresTwoFactorAuth, account.validated, + credentials.login, + isSmallScreenWidth, translate, formatPhoneNumber, - isSmallScreenWidth, ]); return ( From 9fb84a2ba3d6caeacb7e0cdaff520f9d8719f882 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 23 May 2023 19:49:57 -0700 Subject: [PATCH 08/16] Simplify usePermissions hook to use context --- src/components/OnyxProvider.js | 4 ++-- src/hooks/usePermissions.js | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/components/OnyxProvider.js b/src/components/OnyxProvider.js index 6cee7e5b7a62..76cda71da2b2 100644 --- a/src/components/OnyxProvider.js +++ b/src/components/OnyxProvider.js @@ -11,7 +11,7 @@ const [withPersonalDetails, PersonalDetailsProvider] = createOnyxContext(ONYXKEY const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE); const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS); const [withBlockedFromConcierge, BlockedFromConciergeProvider] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); -const [withBetas, BetasProvider] = createOnyxContext(ONYXKEYS.BETAS); +const [withBetas, BetasProvider, BetasContext] = createOnyxContext(ONYXKEYS.BETAS); const propTypes = { /** Rendered child component */ @@ -29,4 +29,4 @@ OnyxProvider.propTypes = propTypes; export default OnyxProvider; -export {withNetwork, withPersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, withBetas, NetworkContext}; +export {withNetwork, withPersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, withBetas, NetworkContext, BetasContext}; diff --git a/src/hooks/usePermissions.js b/src/hooks/usePermissions.js index bd6f085bc924..4ab581231489 100644 --- a/src/hooks/usePermissions.js +++ b/src/hooks/usePermissions.js @@ -1,18 +1,11 @@ import _ from 'underscore'; -import {useMemo} from 'react'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../ONYXKEYS'; +import {useContext, useMemo} from 'react'; import Permissions from '../libs/Permissions'; - -let betas = []; -// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs -Onyx.connect({ - key: ONYXKEYS.BETAS, - callback: (val) => (betas = val), -}); +import {BetasContext} from '../components/OnyxProvider'; export default function usePermissions() { - const result = useMemo( + const betas = useContext(BetasContext); + return useMemo( () => _.reduce( Permissions, @@ -23,7 +16,6 @@ export default function usePermissions() { }, {}, ), - betas, + [betas], ); - return result; } From bcae04a1618ad569ff23da8bd48230d8c97c995a Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 24 May 2023 16:22:39 -0700 Subject: [PATCH 09/16] Function keyword instead of arrow function --- src/pages/signin/SignInPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index c05f16bf0722..bfecf92a79b2 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -52,7 +52,7 @@ const defaultProps = { credentials: {}, }; -const SignInPage = ({account, credentials}) => { +function SignInPage({account, credentials}) { const {translate, formatPhoneNumber} = useLocalize(); const {canUsePasswordlessLogins} = usePermissions(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -174,7 +174,7 @@ const SignInPage = ({account, credentials}) => { ); -}; +} SignInPage.propTypes = propTypes; SignInPage.defaultProps = defaultProps; From 2cb906c04de4163a3fbaa37b8db4abafa9e9f8bc Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 24 May 2023 16:56:25 -0700 Subject: [PATCH 10/16] Extract conditional rendering logic to separate function --- src/pages/signin/SignInPage.js | 92 ++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index bfecf92a79b2..ef3b39618ef7 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -52,6 +52,36 @@ const defaultProps = { credentials: {}, }; +/** + * @param {Boolean} hasLogin + * @param {Boolean} hasPassword + * @param {Boolean} hasValidateCode + * @param {Boolean} isPrimaryLogin + * @param {Boolean} isAccountValidated + * @param {Boolean} didForgetPassword + * @param {Boolean} canUsePasswordlessLogins + * @returns {Object} + */ +function getRenderOptions({hasLogin, hasPassword, hasValidateCode, isPrimaryLogin, isAccountValidated, didForgetPassword, canUsePasswordlessLogins}) { + const isUnvalidatedSecondaryLogin = !isPrimaryLogin && !isAccountValidated; + const shouldShowLoginForm = !hasLogin && !hasValidateCode; + const shouldShowUnlinkLoginForm = hasLogin && isUnvalidatedSecondaryLogin; + const shouldShowPasswordForm = hasLogin && isAccountValidated && !hasPassword && !didForgetPassword && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; + const shouldShowValidateCodeForm = (hasLogin || hasValidateCode) && !shouldShowUnlinkLoginForm && canUsePasswordlessLogins; + const shouldShowResendValidationForm = hasLogin && (!isAccountValidated || didForgetPassword) && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; + const shouldShowWelcomeHeader = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm || shouldShowUnlinkLoginForm; + const shouldShowWelcomeText = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm; + return { + shouldShowLoginForm, + shouldShowUnlinkLoginForm, + shouldShowPasswordForm, + shouldShowValidateCodeForm, + shouldShowResendValidationForm, + shouldShowWelcomeHeader, + shouldShowWelcomeText, + }; +} + function SignInPage({account, credentials}) { const {translate, formatPhoneNumber} = useLocalize(); const {canUsePasswordlessLogins} = usePermissions(); @@ -62,47 +92,23 @@ function SignInPage({account, credentials}) { App.setLocale(Localize.getDevicePreferredLocale()); }, []); - /* - * Show the login form if: - * - A login has not been entered yet - * - AND a validateCode has not been cached with the sign in link - */ - const shouldShowLoginForm = !credentials.login && !credentials.validateCode; - - /* - * Show the unlink form if: - * - A login has been entered - * - AND the login is not the primary login for the account - * - AND the login is not validated - */ - const isUnvalidatedSecondaryLogin = account.primaryLogin && account.primaryLogin !== credentials.login && !account.validated; - const shouldShowUnlinkLoginForm = Boolean(credentials.login) && isUnvalidatedSecondaryLogin; - - /* Show the old password form if - * - A login has been entered - * - AND an account exists and is validated for this login - * - AND a password hasn't been entered yet - * - AND haven't forgotten password - * - AND the login isn't an unvalidated secondary login - * - AND the user is NOT on the passwordless beta - */ - const shouldShowPasswordForm = - Boolean(credentials.login) && account.validated && !credentials.password && !account.forgotPassword && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; - - /* Show the new magic code / validate code form if - * - A login has been entered or a validateCode has been cached from sign in link - * - AND the login isn't an unvalidated secondary login - * - AND the user is on the 'passwordless' beta - */ - const shouldShowValidateCodeForm = (credentials.login || credentials.validateCode) && !shouldShowUnlinkLoginForm && canUsePasswordlessLogins; - - /* Show the resend validation link form if - * - A login has been entered - * - AND is not validated or password is forgotten - * - AND the login isn't an unvalidated secondary login - * - AND user is not on 'passwordless' beta - */ - const shouldShowResendValidationForm = Boolean(credentials.login) && (!account.validated || account.forgotPassword) && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; + const { + shouldShowLoginForm, + shouldShowUnlinkLoginForm, + shouldShowPasswordForm, + shouldShowValidateCodeForm, + shouldShowResendValidationForm, + shouldShowWelcomeHeader, + shouldShowWelcomeText, + } = getRenderOptions({ + hasLogin: Boolean(credentials.login), + hasPassword: Boolean(credentials.password), + hasValidateCode: Boolean(credentials.validateCode), + isPrimaryLogin: account.primaryLogin && account.primaryLogin !== credentials.login, + isAccountValidated: account.validated, + didForgetPassword: account.forgotPassword, + canUsePasswordlessLogins, + }); const {welcomeHeader, welcomeText} = useMemo(() => { let header = ''; @@ -159,8 +165,8 @@ function SignInPage({account, credentials}) { {/* LoginForm and PasswordForm must use the isVisible prop. This keeps them mounted, but visually hidden so that password managers can access the values. Conditionally rendering these components will break this feature. */} From 03f53a2796d914e28343cca6ca055f62810f53bc Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 24 May 2023 17:00:50 -0700 Subject: [PATCH 11/16] Clearer variable naming --- src/pages/signin/SignInPage.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index ef3b39618ef7..86e0f546facd 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -63,17 +63,16 @@ const defaultProps = { * @returns {Object} */ function getRenderOptions({hasLogin, hasPassword, hasValidateCode, isPrimaryLogin, isAccountValidated, didForgetPassword, canUsePasswordlessLogins}) { - const isUnvalidatedSecondaryLogin = !isPrimaryLogin && !isAccountValidated; const shouldShowLoginForm = !hasLogin && !hasValidateCode; - const shouldShowUnlinkLoginForm = hasLogin && isUnvalidatedSecondaryLogin; - const shouldShowPasswordForm = hasLogin && isAccountValidated && !hasPassword && !didForgetPassword && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; - const shouldShowValidateCodeForm = (hasLogin || hasValidateCode) && !shouldShowUnlinkLoginForm && canUsePasswordlessLogins; - const shouldShowResendValidationForm = hasLogin && (!isAccountValidated || didForgetPassword) && !shouldShowUnlinkLoginForm && !canUsePasswordlessLogins; - const shouldShowWelcomeHeader = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm || shouldShowUnlinkLoginForm; + const isUnvalidatedSecondaryLogin = hasLogin && !isPrimaryLogin && !isAccountValidated; + const shouldShowPasswordForm = hasLogin && isAccountValidated && !hasPassword && !didForgetPassword && !isUnvalidatedSecondaryLogin && !canUsePasswordlessLogins; + const shouldShowValidateCodeForm = (hasLogin || hasValidateCode) && !isUnvalidatedSecondaryLogin && canUsePasswordlessLogins; + const shouldShowResendValidationForm = hasLogin && (!isAccountValidated || didForgetPassword) && !isUnvalidatedSecondaryLogin && !canUsePasswordlessLogins; + const shouldShowWelcomeHeader = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm || isUnvalidatedSecondaryLogin; const shouldShowWelcomeText = shouldShowLoginForm || shouldShowPasswordForm || shouldShowValidateCodeForm; return { shouldShowLoginForm, - shouldShowUnlinkLoginForm, + shouldShowUnlinkLoginForm: isUnvalidatedSecondaryLogin, shouldShowPasswordForm, shouldShowValidateCodeForm, shouldShowResendValidationForm, @@ -105,8 +104,8 @@ function SignInPage({account, credentials}) { hasPassword: Boolean(credentials.password), hasValidateCode: Boolean(credentials.validateCode), isPrimaryLogin: account.primaryLogin && account.primaryLogin !== credentials.login, - isAccountValidated: account.validated, - didForgetPassword: account.forgotPassword, + isAccountValidated: Boolean(account.validated), + didForgetPassword: Boolean(account.forgotPassword), canUsePasswordlessLogins, }); From 6b9a9c717b0a49d4de3cc29c6cfd9d7ed6fe49e4 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 1 Jun 2023 11:06:37 -0700 Subject: [PATCH 12/16] Fix bad merge --- src/pages/signin/SignInPage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index be4c828439ee..e4238eab9358 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -87,6 +87,7 @@ function SignInPage({account, credentials}) { const {translate, formatPhoneNumber} = useLocalize(); const {canUsePasswordlessLogins} = usePermissions(); const {isSmallScreenWidth} = useWindowDimensions(); + const safeAreaInsets = useSafeAreaInsets(); useEffect(() => Performance.measureTTI(), []); useEffect(() => { @@ -162,7 +163,7 @@ function SignInPage({account, credentials}) { ]); return ( - + Date: Thu, 1 Jun 2023 14:57:53 -0700 Subject: [PATCH 13/16] Fix crash in UnlinkLoginForm --- src/pages/signin/UnlinkLoginForm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/signin/UnlinkLoginForm.js b/src/pages/signin/UnlinkLoginForm.js index 6066f5ef32fa..67096aa2d9c4 100644 --- a/src/pages/signin/UnlinkLoginForm.js +++ b/src/pages/signin/UnlinkLoginForm.js @@ -50,8 +50,8 @@ const defaultProps = { }; const UnlinkLoginForm = (props) => { - const primaryLogin = Str.isSMSLogin(props.account.primaryLogin) ? Str.removeSMSDomain(props.account.primaryLogin) : props.account.primaryLogin; - const secondaryLogin = Str.isSMSLogin(props.credentials.login) ? Str.removeSMSDomain(props.credentials.login) : props.credentials.login; + const primaryLogin = Str.isSMSLogin(props.account.primaryLogin || '') ? Str.removeSMSDomain(props.account.primaryLogin || '') : props.account.primaryLogin; + const secondaryLogin = Str.isSMSLogin(props.credentials.login || '') ? Str.removeSMSDomain(props.credentials.login || '') : props.credentials.login; return ( <> From 6ca6ff06259708ab7fa464cd722a206e37cccdef Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 6 Jun 2023 17:07:53 -0700 Subject: [PATCH 14/16] Fix isPrimaryLogin --- src/pages/signin/SignInPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index e4238eab9358..1ed57741e8bb 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -106,7 +106,7 @@ function SignInPage({account, credentials}) { hasLogin: Boolean(credentials.login), hasPassword: Boolean(credentials.password), hasValidateCode: Boolean(credentials.validateCode), - isPrimaryLogin: account.primaryLogin && account.primaryLogin !== credentials.login, + isPrimaryLogin: account.primaryLogin && account.primaryLogin === credentials.login, isAccountValidated: Boolean(account.validated), didForgetPassword: Boolean(account.forgotPassword), canUsePasswordlessLogins, From 2972805e52557d38cdcdac0f5383d32286dd9627 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 8 Jun 2023 10:08:36 -0700 Subject: [PATCH 15/16] Remove useMemo for welcomeHeader and welcomeText --- src/pages/signin/SignInPage.js | 78 ++++++++++++++-------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 1ed57741e8bb..2d22a46aa278 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -112,55 +112,41 @@ function SignInPage({account, credentials}) { canUsePasswordlessLogins, }); - const {welcomeHeader, welcomeText} = useMemo(() => { - let header = ''; - let text = ''; - if (shouldShowValidateCodeForm) { - if (account.requiresTwoFactorAuth) { - // We will only know this after a user signs in successfully, without their 2FA code - header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - text = translate('validateCodeForm.enterAuthenticatorCode'); + let welcomeHeader; + let welcomeText; + if (shouldShowValidateCodeForm) { + if (account.requiresTwoFactorAuth) { + // We will only know this after a user signs in successfully, without their 2FA code + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = translate('validateCodeForm.enterAuthenticatorCode'); + } else { + const userLogin = Str.removeSMSDomain(credentials.login || ''); + + // replacing spaces with "hard spaces" to prevent breaking the number + const userLoginToDisplay = Str.isSMSLogin(userLogin) ? formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; + if (account.validated) { + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = isSmallScreenWidth + ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` + : translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); } else { - const userLogin = Str.removeSMSDomain(credentials.login || ''); - - // replacing spaces with "hard spaces" to prevent breaking the number - const userLoginToDisplay = Str.isSMSLogin(userLogin) ? formatPhoneNumber(userLogin).replace(/ /g, '\u00A0') : userLogin; - if (account.validated) { - header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - text = isSmallScreenWidth - ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay})}` - : translate('welcomeText.welcomeEnterMagicCode', {login: userLoginToDisplay}); - } else { - header = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); - text = isSmallScreenWidth - ? `${translate('welcomeText.welcome')} ${translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` - : translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); - } + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); + welcomeText = isSmallScreenWidth + ? `${translate('welcomeText.welcome')} ${translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` + : translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); } - } else if (shouldShowPasswordForm) { - header = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - text = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.enterPassword')}` : translate('welcomeText.enterPassword'); - } else if (shouldShowUnlinkLoginForm) { - header = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.welcomeBack'); - } else if (!shouldShowResendValidationForm) { - header = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.getStarted'); - text = isSmallScreenWidth ? translate('welcomeText.getStarted') : ''; - } else { - Log.warn('SignInPage in unexpected state!'); } - return {welcomeHeader: header, welcomeText: text}; - }, [ - shouldShowValidateCodeForm, - shouldShowPasswordForm, - shouldShowUnlinkLoginForm, - shouldShowResendValidationForm, - account.requiresTwoFactorAuth, - account.validated, - credentials.login, - isSmallScreenWidth, - translate, - formatPhoneNumber, - ]); + } else if (shouldShowPasswordForm) { + welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); + welcomeText = isSmallScreenWidth ? `${translate('welcomeText.welcomeBack')} ${translate('welcomeText.enterPassword')}` : translate('welcomeText.enterPassword'); + } else if (shouldShowUnlinkLoginForm) { + welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.welcomeBack'); + } else if (!shouldShowResendValidationForm) { + welcomeHeader = isSmallScreenWidth ? translate('login.hero.header') : translate('welcomeText.getStarted'); + welcomeText = isSmallScreenWidth ? translate('welcomeText.getStarted') : ''; + } else { + Log.warn('SignInPage in unexpected state!'); + } return ( From 3c5873708441d2d280d54dcb66648cbad11e5c93 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 8 Jun 2023 10:14:09 -0700 Subject: [PATCH 16/16] Remove unused import --- src/pages/signin/SignInPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 2d22a46aa278..dcfc7b67a56a 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx';