From f29040011d082b5538f9d0cfdd9f8a1d9ef2eb93 Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:15:26 +0200 Subject: [PATCH] Revert "Migrate SignInPage to functional component" --- src/components/OnyxProvider.js | 4 +- src/components/withLocalize.js | 2 +- src/hooks/useLocalize.js | 6 - src/hooks/usePermissions.js | 21 --- src/pages/signin/SignInPage.js | 259 ++++++++++++++-------------- src/pages/signin/UnlinkLoginForm.js | 4 +- 6 files changed, 138 insertions(+), 158 deletions(-) delete mode 100644 src/hooks/useLocalize.js delete mode 100644 src/hooks/usePermissions.js diff --git a/src/components/OnyxProvider.js b/src/components/OnyxProvider.js index 76cda71da2b2..6cee7e5b7a62 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, BetasContext] = createOnyxContext(ONYXKEYS.BETAS); +const [withBetas, BetasProvider] = 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, BetasContext}; +export {withNetwork, withPersonalDetails, withReportActionsDrafts, withCurrentDate, withBlockedFromConcierge, withBetas, NetworkContext}; diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index def7110c1b40..4cbdda876231 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, LocaleContext}; +export {withLocalizePropTypes, Provider as LocaleContextProvider}; diff --git a/src/hooks/useLocalize.js b/src/hooks/useLocalize.js deleted file mode 100644 index 9ad5048729bd..000000000000 --- a/src/hooks/useLocalize.js +++ /dev/null @@ -1,6 +0,0 @@ -import {useContext} from 'react'; -import {LocaleContext} from '../components/withLocalize'; - -export default function useLocalize() { - return useContext(LocaleContext); -} diff --git a/src/hooks/usePermissions.js b/src/hooks/usePermissions.js deleted file mode 100644 index 4ab581231489..000000000000 --- a/src/hooks/usePermissions.js +++ /dev/null @@ -1,21 +0,0 @@ -import _ from 'underscore'; -import {useContext, useMemo} from 'react'; -import Permissions from '../libs/Permissions'; -import {BetasContext} from '../components/OnyxProvider'; - -export default function usePermissions() { - const betas = useContext(BetasContext); - return useMemo( - () => - _.reduce( - Permissions, - (memo, checkerFunction, beta) => { - // eslint-disable-next-line no-param-reassign - memo[beta] = checkerFunction(betas); - return memo; - }, - {}, - ), - [betas], - ); -} diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index dcfc7b67a56a..a875c25359b0 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -1,27 +1,30 @@ -import React, {useEffect} from 'react'; -import {View} from 'react-native'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; +import {View} from 'react-native'; +import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; -import {useSafeAreaInsets} from 'react-native-safe-area-context'; +import {withSafeAreaInsets} 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'; import * as StyleUtils from '../../styles/StyleUtils'; 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 */ @@ -32,149 +35,153 @@ const propTypes = { /** The primaryLogin associated with the account */ primaryLogin: PropTypes.string, - - /** Has the user pressed the forgot password button? */ - forgotPassword: PropTypes.bool, - - /** Does this account require 2FA? */ - requiresTwoFactorAuth: PropTypes.bool, }), + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), + /** The credentials of the person signing in */ credentials: PropTypes.shape({ login: PropTypes.string, password: PropTypes.string, twoFactorAuthCode: PropTypes.string, - validateCode: PropTypes.string, }), + + ...withLocalizePropTypes, + + ...windowDimensionsPropTypes, }; const defaultProps = { account: {}, + betas: [], 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 shouldShowLoginForm = !hasLogin && !hasValidateCode; - 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: isUnvalidatedSecondaryLogin, - shouldShowPasswordForm, - shouldShowValidateCodeForm, - shouldShowResendValidationForm, - shouldShowWelcomeHeader, - shouldShowWelcomeText, - }; -} - -function SignInPage({account, credentials}) { - const {translate, formatPhoneNumber} = useLocalize(); - const {canUsePasswordlessLogins} = usePermissions(); - const {isSmallScreenWidth} = useWindowDimensions(); - const safeAreaInsets = useSafeAreaInsets(); +class SignInPage extends Component { + componentDidMount() { + Performance.measureTTI(); - useEffect(() => Performance.measureTTI(), []); - useEffect(() => { App.setLocale(Localize.getDevicePreferredLocale()); - }, []); - - 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: Boolean(account.validated), - didForgetPassword: Boolean(account.forgotPassword), - canUsePasswordlessLogins, - }); - - 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}); + } + + 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 = + Boolean(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 = + Boolean(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); + + let welcomeHeader = ''; + let welcomeText = ''; + if (showValidateCodeForm) { + if (this.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'); } else { - welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcome'); - welcomeText = isSmallScreenWidth - ? `${translate('welcomeText.welcome')} ${translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay})}` - : translate('welcomeText.newFaceEnterMagicCode', {login: userLoginToDisplay}); + const userLogin = Str.removeSMSDomain(lodashGet(this.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}); + } 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}); + } } + } 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 ( + // There is an issue SafeAreaView on Android where wrong insets flicker on app start. + // Can be removed once https://github.com/th3rdwave/react-native-safe-area-context/issues/364 is resolved. + + + {/* 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. */} - - {shouldShowValidateCodeForm ? : } - {shouldShowResendValidationForm && } - {shouldShowUnlinkLoginForm && } - - - ); + + {showValidateCodeForm ? : } + {showResendValidationForm && } + {showUnlinkLoginForm && } + + + ); + } } SignInPage.propTypes = propTypes; SignInPage.defaultProps = defaultProps; -SignInPage.displayName = 'SignInPage'; -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - credentials: {key: ONYXKEYS.CREDENTIALS}, -})(SignInPage); +export default compose( + withSafeAreaInsets, + withLocalize, + withWindowDimensions, + withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + betas: {key: ONYXKEYS.BETAS}, + credentials: {key: ONYXKEYS.CREDENTIALS}, + }), +)(SignInPage); diff --git a/src/pages/signin/UnlinkLoginForm.js b/src/pages/signin/UnlinkLoginForm.js index adf44aeb1d8b..af108e5bbecc 100644 --- a/src/pages/signin/UnlinkLoginForm.js +++ b/src/pages/signin/UnlinkLoginForm.js @@ -51,8 +51,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 ( <>