Skip to content

Commit

Permalink
Merge pull request #20515 from esh-g/PasswordForm-FC-migration
Browse files Browse the repository at this point in the history
PasswordForm Function Migration
  • Loading branch information
yuwenmemon authored Jun 15, 2023
2 parents 826dbee + ef23e0e commit 9727956
Showing 1 changed file with 149 additions and 141 deletions.
290 changes: 149 additions & 141 deletions src/pages/signin/PasswordForm.js
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {useState, useEffect, useCallback, useRef} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -52,202 +52,210 @@ const defaultProps = {
preferredLocale: CONST.LOCALES.DEFAULT,
};

class PasswordForm extends React.Component {
constructor(props) {
super(props);
this.validateAndSubmitForm = this.validateAndSubmitForm.bind(this);
this.resetPassword = this.resetPassword.bind(this);
this.clearSignInData = this.clearSignInData.bind(this);

this.state = {
formError: {},
password: '',
twoFactorAuthCode: '',
};
}

componentDidMount() {
if (!canFocusInputOnScreenFocus() || !this.inputPassword || !this.props.isVisible) {
return;
}
this.inputPassword.focus();
}
function PasswordForm(props) {
const [formError, setFormError] = useState({});
const [password, setPassword] = useState('');
const [twoFactorAuthCode, setTwoFactorAuthCode] = useState('');

componentDidUpdate(prevProps, prevState) {
if (!prevProps.isVisible && this.props.isVisible) {
this.inputPassword.focus();
}
if (prevProps.isVisible && !this.props.isVisible && this.state.password) {
this.clearPassword();
}
if (!prevProps.account.requiresTwoFactorAuth && this.props.account.requiresTwoFactorAuth) {
this.input2FA.focus();
}
if (prevState.twoFactorAuthCode !== this.state.twoFactorAuthCode && this.state.twoFactorAuthCode.length === CONST.TFA_CODE_LENGTH) {
this.validateAndSubmitForm();
}
}
const inputPasswordRef = useRef(null);
const input2FA = useRef(null);

/**
* Handle text input and clear formError upon text change
*
* @param {String} text
* @param {String} key
*/
onTextInput(text, key) {
this.setState({
[key]: text,
formError: {[key]: ''},
});
const onTextInput = (text, key) => {
if (key === 'password') {
setPassword(text);
}
if (key === 'twoFactorAuthCode') {
setTwoFactorAuthCode(text);
}
setFormError({[key]: ''});

if (this.props.account.errors) {
if (props.account.errors) {
Session.clearAccountMessages();
}
}
};

/**
* Clear Password from the state
*/
clearPassword() {
this.setState({password: ''}, this.inputPassword.clear);
}
const clearPassword = () => {
setPassword('');
inputPasswordRef.current.clear();
};

/**
* Trigger the reset password flow and ensure the 2FA input field is reset to avoid it being permanently hidden
*/
resetPassword() {
if (this.input2FA) {
this.setState({twoFactorAuthCode: ''}, this.input2FA.clear);
const resetPassword = () => {
if (input2FA.current) {
setTwoFactorAuthCode('');
input2FA.current.clear();
}
this.setState({formError: {}});
setFormError({});
Session.resetPassword();
}
};

/**
* Clears local and Onyx sign in states
*/
clearSignInData() {
this.setState({twoFactorAuthCode: '', formError: {}});
const clearSignInData = () => {
setTwoFactorAuthCode('');
setFormError({});
Session.clearSignInData();
}
};

/**
* Check that all the form fields are valid, then trigger the submit callback
*/
validateAndSubmitForm() {
const password = this.state.password.trim();
const twoFactorCode = this.state.twoFactorAuthCode.trim();
const requiresTwoFactorAuth = this.props.account.requiresTwoFactorAuth;
const validateAndSubmitForm = useCallback(() => {
const passwordTrimmed = password.trim();
const twoFactorCodeTrimmed = twoFactorAuthCode.trim();
const requiresTwoFactorAuth = props.account.requiresTwoFactorAuth;

if (!passwordTrimmed) {
setFormError({password: 'passwordForm.pleaseFillPassword'});
return;
}

if (!ValidationUtils.isValidPassword(passwordTrimmed)) {
setFormError({password: 'passwordForm.error.incorrectPassword'});
return;
}

if (!password) {
this.setState({formError: {password: 'passwordForm.pleaseFillPassword'}});
if (requiresTwoFactorAuth && !twoFactorCodeTrimmed) {
setFormError({twoFactorAuthCode: 'passwordForm.pleaseFillTwoFactorAuth'});
return;
}

if (!ValidationUtils.isValidPassword(password)) {
this.setState({formError: {password: 'passwordForm.error.incorrectPassword'}});
if (requiresTwoFactorAuth && !ValidationUtils.isValidTwoFactorCode(twoFactorCodeTrimmed)) {
setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'});
return;
}

if (requiresTwoFactorAuth && !twoFactorCode) {
this.setState({formError: {twoFactorAuthCode: 'passwordForm.pleaseFillTwoFactorAuth'}});
setFormError({});

Session.signIn(passwordTrimmed, '', twoFactorCodeTrimmed, props.preferredLocale);
}, [password, twoFactorAuthCode, props.account.requiresTwoFactorAuth, props.preferredLocale]);

useEffect(() => {
if (!canFocusInputOnScreenFocus() || !inputPasswordRef.current || !props.isVisible) {
return;
}
inputPasswordRef.current.focus();
// eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want this effect to run again
}, []);

useEffect(() => {
if (props.isVisible) {
inputPasswordRef.current.focus();
}
if (!props.isVisible) {
clearPassword();
}
}, [props.isVisible]);

useEffect(() => {
if (!props.account.requiresTwoFactorAuth) {
return;
}
input2FA.current.focus();
}, [props.account.requiresTwoFactorAuth]);

if (requiresTwoFactorAuth && !ValidationUtils.isValidTwoFactorCode(twoFactorCode)) {
this.setState({formError: {twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}});
useEffect(() => {
if (twoFactorAuthCode.length !== CONST.TFA_CODE_LENGTH) {
return;
}
validateAndSubmitForm();
}, [twoFactorAuthCode, validateAndSubmitForm]);

this.setState({
formError: {},
});
const isTwoFactorAuthRequired = Boolean(props.account.requiresTwoFactorAuth);
const hasServerError = Boolean(props.account) && !_.isEmpty(props.account.errors);

Session.signIn(password, '', twoFactorCode, this.props.preferredLocale);
}
// When the 2FA required flag is set, user has already passed/completed the password field
const passwordFieldHasError = !isTwoFactorAuthRequired && hasServerError;
const twoFactorFieldHasError = isTwoFactorAuthRequired && hasServerError;

render() {
const isTwoFactorAuthRequired = Boolean(this.props.account.requiresTwoFactorAuth);
const hasServerError = Boolean(this.props.account) && !_.isEmpty(this.props.account.errors);
return (
<>
<View style={[styles.mv3]}>
<TextInput
ref={inputPasswordRef}
label={props.translate('common.password')}
secureTextEntry
autoCompleteType={ComponentUtils.PASSWORD_AUTOCOMPLETE_TYPE}
textContentType="password"
nativeID="password"
name="password"
value={password}
onChangeText={(text) => onTextInput(text, 'password')}
onSubmitEditing={validateAndSubmitForm}
blurOnSubmit={false}
errorText={formError.password ? props.translate(formError.password) : ''}
hasError={passwordFieldHasError}
/>
<View style={[styles.changeExpensifyLoginLinkContainer]}>
<PressableWithFeedback
style={[styles.mt2]}
onPress={resetPassword}
accessibilityRole="link"
accessibilityLabel={props.translate('passwordForm.forgot')}
hoverDimmingValue={1}
>
<Text style={[styles.link]}>{props.translate('passwordForm.forgot')}</Text>
</PressableWithFeedback>
</View>
</View>

// When the 2FA required flag is set, user has already passed/completed the password field
const passwordFieldHasError = !isTwoFactorAuthRequired && hasServerError;
const twoFactorFieldHasError = isTwoFactorAuthRequired && hasServerError;
return (
<>
{isTwoFactorAuthRequired && (
<View style={[styles.mv3]}>
<TextInput
ref={(el) => (this.inputPassword = el)}
label={this.props.translate('common.password')}
secureTextEntry
autoCompleteType={ComponentUtils.PASSWORD_AUTOCOMPLETE_TYPE}
textContentType="password"
nativeID="password"
name="password"
value={this.state.password}
onChangeText={(text) => this.onTextInput(text, 'password')}
onSubmitEditing={this.validateAndSubmitForm}
ref={input2FA}
label={props.translate('common.twoFactorCode')}
value={twoFactorAuthCode}
placeholder={props.translate('passwordForm.requiredWhen2FAEnabled')}
placeholderTextColor={themeColors.placeholderText}
onChangeText={(text) => onTextInput(text, 'twoFactorAuthCode')}
onSubmitEditing={validateAndSubmitForm}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
blurOnSubmit={false}
errorText={this.state.formError.password ? this.props.translate(this.state.formError.password) : ''}
hasError={passwordFieldHasError}
maxLength={CONST.TFA_CODE_LENGTH}
errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''}
hasError={twoFactorFieldHasError}
/>
<View style={[styles.changeExpensifyLoginLinkContainer]}>
<PressableWithFeedback
style={[styles.mt2]}
onPress={this.resetPassword}
accessibilityRole="link"
accessibilityLabel={this.props.translate('passwordForm.forgot')}
hoverDimmingValue={1}
>
<Text style={[styles.link]}>{this.props.translate('passwordForm.forgot')}</Text>
</PressableWithFeedback>
</View>
</View>
)}

{isTwoFactorAuthRequired && (
<View style={[styles.mv3]}>
<TextInput
ref={(el) => (this.input2FA = el)}
label={this.props.translate('common.twoFactorCode')}
value={this.state.twoFactorAuthCode}
placeholder={this.props.translate('passwordForm.requiredWhen2FAEnabled')}
placeholderTextColor={themeColors.placeholderText}
onChangeText={(text) => this.onTextInput(text, 'twoFactorAuthCode')}
onSubmitEditing={this.validateAndSubmitForm}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
blurOnSubmit={false}
maxLength={CONST.TFA_CODE_LENGTH}
errorText={this.state.formError.twoFactorAuthCode ? this.props.translate(this.state.formError.twoFactorAuthCode) : ''}
hasError={twoFactorFieldHasError}
/>
</View>
)}

{hasServerError && <FormHelpMessage message={ErrorUtils.getLatestErrorMessage(this.props.account)} />}
<View>
<Button
isDisabled={this.props.network.isOffline}
success
style={[styles.mv3]}
text={this.props.translate('common.signIn')}
isLoading={
this.props.account.isLoading &&
this.props.account.loadingForm === (this.props.account.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM)
}
onPress={this.validateAndSubmitForm}
/>
<ChangeExpensifyLoginLink onPress={this.clearSignInData} />
</View>
<View style={[styles.mt5, styles.signInPageWelcomeTextContainer]}>
<Terms />
</View>
</>
);
}
{hasServerError && <FormHelpMessage message={ErrorUtils.getLatestErrorMessage(props.account)} />}

<View>
<Button
isDisabled={props.network.isOffline}
success
style={[styles.mv3]}
text={props.translate('common.signIn')}
isLoading={
props.account.isLoading && props.account.loadingForm === (props.account.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM)
}
onPress={validateAndSubmitForm}
/>
<ChangeExpensifyLoginLink onPress={clearSignInData} />
</View>

<View style={[styles.mt5, styles.signInPageWelcomeTextContainer]}>
<Terms />
</View>
</>
);
}

PasswordForm.propTypes = propTypes;
PasswordForm.defaultProps = defaultProps;
PasswordForm.displayName = 'PasswordForm';

export default compose(
withLocalize,
Expand Down

0 comments on commit 9727956

Please sign in to comment.