Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate login form whever the input is changed #30563

Merged
merged 17 commits into from
Nov 15, 2023
107 changes: 74 additions & 33 deletions src/pages/signin/LoginForm/BaseLoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Log from '@libs/Log';
import * as LoginUtils from '@libs/LoginUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import styles from '@styles/styles';
import * as CloseAccount from '@userActions/CloseAccount';
import * as MemoryOnlyKeys from '@userActions/MemoryOnlyKeys/MemoryOnlyKeys';
Expand Down Expand Up @@ -98,18 +99,52 @@ function LoginForm(props) {
const [login, setLogin] = useState(() => Str.removeSMSDomain(props.credentials.login || ''));
const [formError, setFormError] = useState(false);
const prevIsVisible = usePrevious(props.isVisible);
const firstBlurred = useRef(false);

const {translate} = props;

/**
* Handle text input and clear formError upon text change
* Validate the input value and set the error for formError
*
* @param {String} value
*/
const validate = useCallback(
(value) => {
const loginTrim = value.trim();
if (!loginTrim) {
setFormError('common.pleaseEnterEmailOrPhoneNumber');
return false;
}

const phoneLogin = LoginUtils.appendCountryCode(LoginUtils.getPhoneNumberWithoutSpecialChars(loginTrim));
const parsedPhoneNumber = parsePhoneNumber(phoneLogin);

if (!Str.isValidEmail(loginTrim) && !parsedPhoneNumber.possible) {
if (ValidationUtils.isNumericWithSpecialChars(loginTrim)) {
setFormError('common.error.phoneNumber');
} else {
setFormError('loginForm.error.invalidFormatEmailLogin');
}
return false;
}

setFormError(null);
return true;
},
[setFormError],
);

/**
* Handle text input and validate the text input if it is blurred
*
* @param {String} text
*/
const onTextInput = useCallback(
(text) => {
setLogin(text);
setFormError(null);
if (firstBlurred.current) {
validate(text);
}

if (props.account.errors || props.account.message) {
Session.clearAccountMessages();
Expand All @@ -120,7 +155,7 @@ function LoginForm(props) {
CloseAccount.setDefaultData();
}
},
[props.account, props.closeAccount, input, setFormError, setLogin],
[props.account, props.closeAccount, input, setLogin, validate],
);

function getSignInWithStyles() {
Expand All @@ -140,35 +175,30 @@ function LoginForm(props) {
CloseAccount.setDefaultData();
}

const loginTrim = login.trim();
if (!loginTrim) {
setFormError('common.pleaseEnterEmailOrPhoneNumber');
return;
// For native, the single input doesn't lost focus when we click outside.
// So we need to change firstBlurred here to make the validate function is called whenever the text input is changed after the first validation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// So we need to change firstBlurred here to make the validate function is called whenever the text input is changed after the first validation.
// So we need to change firstBlurred here to make sure the validate function is called whenever the text input is changed after the first validation.

if (!firstBlurred.current) {
firstBlurred.current = true;
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
}

const phoneLogin = LoginUtils.appendCountryCode(LoginUtils.getPhoneNumberWithoutSpecialChars(loginTrim));
const parsedPhoneNumber = parsePhoneNumber(phoneLogin);

if (!Str.isValidEmail(loginTrim) && !parsedPhoneNumber.possible) {
if (ValidationUtils.isNumericWithSpecialChars(loginTrim)) {
setFormError('common.error.phoneNumber');
} else {
setFormError('loginForm.error.invalidFormatEmailLogin');
}
if (!validate(login)) {
return;
}

const loginTrim = login.trim();

// If the user has entered a guide email, then we are going to enable an experimental Onyx mode to help with performance
if (PolicyUtils.isExpensifyGuideTeam(loginTrim)) {
Log.info('Detected guide email in login field, setting memory only keys.');
MemoryOnlyKeys.enable();
}

setFormError(null);
const phoneLogin = LoginUtils.appendCountryCode(LoginUtils.getPhoneNumberWithoutSpecialChars(loginTrim));
const parsedPhoneNumber = parsePhoneNumber(phoneLogin);

// Check if this login has an account associated with it or not
Session.beginSignIn(parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : loginTrim);
}, [login, props.account, props.closeAccount, props.network, setFormError]);
}, [login, props.account, props.closeAccount, props.network, validate]);

useEffect(() => {
// Just call clearAccountMessages on the login page (home route), because when the user is in the transition route and not yet authenticated,
Expand Down Expand Up @@ -227,6 +257,13 @@ function LoginForm(props) {
textContentType="username"
id="username"
name="username"
onBlur={() => {
if (firstBlurred.current || !Visibility.isVisible() || !Visibility.hasFocus()) {
return;
}
firstBlurred.current = true;
validate(login);
}}
onChangeText={onTextInput}
onSubmitEditing={validateAndSubmitForm}
autoCapitalize="none"
Expand Down Expand Up @@ -265,22 +302,26 @@ function LoginForm(props) {
// for developers about possible regressions, we won't render buttons in development mode.
// For more information about these differences and how to test in development mode,
// see`Expensify/App/contributingGuides/APPLE_GOOGLE_SIGNIN.md`
CONFIG.ENVIRONMENT !== CONST.ENVIRONMENT.DEV && (
<View style={[getSignInWithStyles()]}>
<Text
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants"
style={[styles.textLabelSupporting, styles.textAlignCenter, styles.mb3, styles.mt2]}
>
{props.translate('common.signInWith')}
</Text>

<View style={props.isSmallScreenWidth ? styles.loginButtonRowSmallScreen : styles.loginButtonRow}>
<AppleSignIn />
<GoogleSignIn />
CONFIG.ENVIRONMENT !== CONST.ENVIRONMENT.DEV ||
(true && (
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
<View style={[getSignInWithStyles()]}>
<Text
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants"
style={[styles.textLabelSupporting, styles.textAlignCenter, styles.mb3, styles.mt2]}
>
{props.translate('common.signInWith')}
</Text>

<View
onMouseDown={(e) => e.preventDefault()}
style={props.isSmallScreenWidth ? styles.loginButtonRowSmallScreen : styles.loginButtonRow}
>
<AppleSignIn />
<GoogleSignIn />
</View>
</View>
</View>
)
))
}
</View>
)
Expand Down
Loading