diff --git a/README.md b/README.md
index 8881a1ab3a71..55d9b8f1478e 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11
1. If you are having issues with **_Getting Started_**, please reference [React Native's Documentation](https://reactnative.dev/docs/environment-setup)
2. If you are running into CORS errors like (in the browser dev console)
```sh
- Access to fetch at 'https://www.expensify.com/api?command=BeginSignIn' from origin 'http://localhost:8080' has been blocked by CORS policy
+ Access to fetch at 'https://www.expensify.com/api?command=GetAccountStatus' from origin 'http://localhost:8080' has been blocked by CORS policy
```
You probably have a misconfigured `.env` file - remove it (`rm .env`) and try again
@@ -164,7 +164,7 @@ That action will then call `Onyx.merge()` to [set default data and a loading sta
```js
function signIn(password, twoFactorAuthCode) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true});
Authentication.Authenticate({
...defaultParams,
password,
@@ -177,7 +177,7 @@ function signIn(password, twoFactorAuthCode) {
Onyx.merge(ONYXKEYS.ACCOUNT, {error: error.message});
})
.finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
}
```
@@ -188,7 +188,7 @@ Keeping our `Onyx.merge()` out of the view layer and in actions helps organize t
// Bad
validateAndSubmitForm() {
// validate...
- this.setState({isLoading: true});
+ this.setState({loading: true});
signIn()
.then((response) => {
if (result.jsonCode === 200) {
@@ -198,7 +198,7 @@ validateAndSubmitForm() {
this.setState({error: response.message});
})
.finally(() => {
- this.setState({isLoading: false});
+ this.setState({loading: false});
});
}
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index a6fe6476435f..17302ba46467 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -186,6 +186,9 @@ export default {
// The policyID of the last workspace whose settings were accessed by the user
LAST_ACCESSED_WORKSPACE_POLICY_ID: 'lastAccessedWorkspacePolicyID',
+ // Validating Email?
+ USER_SIGN_UP: 'userSignUp',
+
// List of Form ids
FORMS: {
ADD_DEBIT_CARD_FORM: 'addDebitCardForm',
diff --git a/src/languages/en.js b/src/languages/en.js
index cbafe0744149..b9360c8fdb3e 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -497,9 +497,11 @@ export default {
},
resendValidationForm: {
linkHasBeenResent: 'Link has been re-sent',
- weSentYouMagicSignInLink: ({login, loginType}) => `I've sent a magic sign-in link to ${login}. Please check your ${loginType} to sign in.`,
+ weSentYouMagicSignInLink: ({login}) => `We've sent a magic sign in link to ${login}. Check your Inbox and your Spam folder and wait 5-10 minutes before trying again.`,
resendLink: 'Resend link',
validationCodeFailedMessage: 'It looks like there was an error with your validation link or it has expired.',
+ unvalidatedAccount: 'This account exists but isn\'t validated, please check your inbox for your magic link.',
+ newAccount: ({login, loginType}) => `Welcome ${login}, it's always great to see a new face around here! Please check your ${loginType} for a magic link to validate your account.`,
},
detailsPage: {
localTime: 'Local time',
@@ -521,7 +523,7 @@ export default {
passwordFormTitle: 'Welcome back to the New Expensify! Please set your password.',
passwordNotSet: 'We were unable to set your new password. We have sent you a new password link to try again.',
setPasswordLinkInvalid: 'This set password link is invalid or has expired. A new one is waiting for you in your email inbox!',
- validatingAccount: 'Verifying account',
+ verifyingAccount: 'Verifying account',
},
stepCounter: ({step, total}) => `Step ${step} of ${total}`,
bankAccount: {
diff --git a/src/languages/es.js b/src/languages/es.js
index fdcb6778c643..86d9cd026e42 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -497,9 +497,11 @@ export default {
},
resendValidationForm: {
linkHasBeenResent: 'El enlace se ha reenviado',
- weSentYouMagicSignInLink: ({login, loginType}) => `Te he enviado un hiperenlace mágico para iniciar sesión a ${login}. Por favor revisa tu ${loginType}`,
+ weSentYouMagicSignInLink: ({login}) => `Hemos enviado un enlace mágico de inicio de sesión a ${login}. Verifica tu bandeja de entrada y tu carpeta de correo no deseado y espera de 5 a 10 minutos antes de intentarlo de nuevo.`,
resendLink: 'Reenviar enlace',
validationCodeFailedMessage: 'Parece que hubo un error con el enlace de validación o ha caducado.',
+ unvalidatedAccount: 'Esta cuenta existe pero no está validada, por favor busca el enlace mágico en tu bandeja de entrada',
+ newAccount: ({login, loginType}) => `¡Bienvenido ${login}, es genial ver una cara nueva por aquí! En tu ${loginType} encontrarás un enlace para validar tu cuenta, por favor, revísalo`,
},
detailsPage: {
localTime: 'Hora local',
@@ -521,7 +523,7 @@ export default {
passwordFormTitle: '¡Bienvenido de vuelta al Nuevo Expensify! Por favor, elige una contraseña.',
passwordNotSet: 'No pudimos cambiar tu clave. Te hemos enviado un nuevo enlace para que intentes cambiar la clave nuevamente.',
setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.',
- validatingAccount: 'Verificando cuenta',
+ verifyingAccount: 'Verificando cuenta',
},
stepCounter: ({step, total}) => `Paso ${step} de ${total}`,
bankAccount: {
diff --git a/src/libs/Network/MainQueue.js b/src/libs/Network/MainQueue.js
index bb0804565893..2143cee9d851 100644
--- a/src/libs/Network/MainQueue.js
+++ b/src/libs/Network/MainQueue.js
@@ -19,7 +19,7 @@ let networkRequestQueue = [];
* @return {Boolean}
*/
function canMakeRequest(request) {
- // Some requests are always made even when we are in the process of authenticating (typically because they require no authToken e.g. Log, BeginSignIn)
+ // Some requests are always made even when we are in the process of authenticating (typically because they require no authToken e.g. Log, GetAccountStatus)
// However, if we are in the process of authenticating we always want to queue requests until we are no longer authenticating.
return request.data.forceNetworkRequest === true || (!NetworkStore.isAuthenticating() && !SequentialQueue.isRunning());
}
diff --git a/src/libs/Network/enhanceParameters.js b/src/libs/Network/enhanceParameters.js
index b7f82f47dda5..093c0d96c087 100644
--- a/src/libs/Network/enhanceParameters.js
+++ b/src/libs/Network/enhanceParameters.js
@@ -15,10 +15,11 @@ function isAuthTokenRequired(command) {
'Log',
'Graphite_Timer',
'Authenticate',
- 'BeginSignIn',
+ 'GetAccountStatus',
'SetPassword',
'User_SignUp',
'ResendValidateCode',
+ 'User_ReopenAccount',
'ValidateEmail',
], command);
}
diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js
index d88d35bfe304..5059a434ad43 100644
--- a/src/libs/actions/Session/index.js
+++ b/src/libs/actions/Session/index.js
@@ -18,12 +18,12 @@ import Timers from '../../Timers';
import * as Pusher from '../../Pusher/pusher';
import NetworkConnection from '../../NetworkConnection';
import * as User from '../User';
+import * as ValidationUtils from '../../ValidationUtils';
import * as Authentication from '../../Authentication';
import * as ErrorUtils from '../../ErrorUtils';
import * as Welcome from '../Welcome';
import * as API from '../../API';
import * as NetworkStore from '../../Network/NetworkStore';
-import DateUtils from '../../DateUtils';
let credentials = {};
Onyx.connect({
@@ -48,6 +48,28 @@ function setSuccessfulSignInData(data) {
});
}
+/**
+ * Create an account for the user logging in.
+ * This will send them a notification with a link to click on to validate the account and set a password
+ *
+ * @param {String} login
+ */
+function createAccount(login) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {error: ''});
+
+ DeprecatedAPI.User_SignUp({
+ email: login,
+ }).then((response) => {
+ // A 405 means that the account needs to be validated. We should let the user proceed to the ResendValidationForm view.
+ if (response.jsonCode === 200 || response.jsonCode === 405) {
+ return;
+ }
+
+ Onyx.merge(ONYXKEYS.CREDENTIALS, {login: null});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {error: response.message || `Unknown API Error: ${response.jsonCode}`});
+ });
+}
+
/**
* Clears the Onyx store and redirects user to the sign in page
*/
@@ -84,16 +106,29 @@ function signOutAndRedirectToSignIn() {
Log.info('Redirecting to Sign In because signOut() was called');
}
+/**
+ * Reopen the account and send the user a link to set password
+ *
+ * @param {String} [login]
+ */
+function reopenAccount(login = credentials.login) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true});
+ DeprecatedAPI.User_ReopenAccount({email: login})
+ .finally(() => {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
+ });
+}
+
/**
* Resend the validation link to the user that is validating their account
*
* @param {String} [login]
*/
function resendValidationLink(login = credentials.login) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: true});
DeprecatedAPI.ResendValidateCode({email: login})
.finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
}
@@ -102,42 +137,45 @@ function resendValidationLink(login = credentials.login) {
*
* @param {String} login
*/
-function beginSignIn(login) {
- const optimisticData = [
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: ONYXKEYS.ACCOUNT,
- value: {
- ...CONST.DEFAULT_ACCOUNT_DATA,
- isLoading: true,
- },
- },
- ];
-
- const successData = [
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: ONYXKEYS.ACCOUNT,
- value: {
- isLoading: false,
- },
- },
- ];
-
- const failureData = [
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: ONYXKEYS.ACCOUNT,
- value: {
- isLoading: false,
- errors: {
- [DateUtils.getMicroseconds()]: 'Cannot get account details, please try again',
- },
- },
- },
- ];
+function fetchAccountDetails(login) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
- API.read('BeginSignIn', {email: login}, {optimisticData, successData, failureData});
+ DeprecatedAPI.GetAccountStatus({email: login, forceNetworkRequest: true})
+ .then((response) => {
+ if (response.jsonCode === 200) {
+ Onyx.merge(ONYXKEYS.CREDENTIALS, {
+ login: response.normalizedLogin,
+ });
+ Onyx.merge(ONYXKEYS.ACCOUNT, {
+ accountExists: response.accountExists,
+ validated: response.validated,
+ closed: response.isClosed,
+ forgotPassword: false,
+ validateCodeExpired: false,
+ });
+
+ if (!response.accountExists) {
+ createAccount(login);
+ } else if (response.isClosed) {
+ reopenAccount(login);
+ } else if (!response.validated) {
+ resendValidationLink(login);
+ }
+ } else if (response.jsonCode === 402) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {
+ error: ValidationUtils.isNumericWithSpecialChars(login)
+ ? Localize.translateLocal('common.error.phoneNumber')
+ : Localize.translateLocal('loginForm.error.invalidFormatEmailLogin'),
+ });
+ } else if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {error: Localize.translateLocal('session.offlineMessageRetry')});
+ } else {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {error: response.message});
+ }
+ })
+ .finally(() => {
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
+ });
}
/**
@@ -197,7 +235,7 @@ function createTemporaryLogin(authToken, email) {
return createLoginResponse;
})
.finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
}
@@ -210,7 +248,7 @@ function createTemporaryLogin(authToken, email) {
* @param {String} [twoFactorAuthCode]
*/
function signIn(password, twoFactorAuthCode) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
Authentication.Authenticate({
useExpensifyLogin: true,
@@ -225,10 +263,10 @@ function signIn(password, twoFactorAuthCode) {
if (response.jsonCode !== 200) {
const errorMessage = ErrorUtils.getAuthenticateErrorMessage(response);
if (errorMessage === 'passwordForm.error.twoFactorAuthenticationEnabled') {
- Onyx.merge(ONYXKEYS.ACCOUNT, {requiresTwoFactorAuth: true, isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {requiresTwoFactorAuth: true, loading: false});
return;
}
- Onyx.merge(ONYXKEYS.ACCOUNT, {error: Localize.translateLocal(errorMessage), isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {error: Localize.translateLocal(errorMessage), loading: false});
return;
}
@@ -245,7 +283,7 @@ function signIn(password, twoFactorAuthCode) {
* @param {String} exitTo
*/
function signInWithShortLivedToken(email, shortLivedToken) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
createTemporaryLogin(shortLivedToken, email)
.then((response) => {
@@ -256,7 +294,7 @@ function signInWithShortLivedToken(email, shortLivedToken) {
User.getUserDetails();
Onyx.merge(ONYXKEYS.ACCOUNT, {success: true});
}).finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
}
@@ -284,6 +322,7 @@ function resetPassword() {
key: ONYXKEYS.ACCOUNT,
value: {
isLoading: false,
+ validateCodeExpired: false,
},
},
],
@@ -293,6 +332,7 @@ function resetPassword() {
key: ONYXKEYS.ACCOUNT,
value: {
isLoading: false,
+ validateCodeExpired: false,
},
},
],
@@ -309,7 +349,7 @@ function resetPassword() {
* @param {Number} accountID
*/
function setPassword(password, validateCode, accountID) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true, validateCodeExpired: false});
DeprecatedAPI.SetPassword({
password,
validateCode,
@@ -325,7 +365,7 @@ function setPassword(password, validateCode, accountID) {
Onyx.merge(ONYXKEYS.ACCOUNT, {error: response.message});
})
.finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
}
@@ -379,6 +419,7 @@ function changePasswordAndSignIn(authToken, password) {
password,
})
.then((responsePassword) => {
+ Onyx.merge(ONYXKEYS.USER_SIGN_UP, {authToken: null});
if (responsePassword.jsonCode === 200) {
signIn(password);
return;
@@ -391,7 +432,7 @@ function changePasswordAndSignIn(authToken, password) {
}
if (responsePassword.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) {
// authToken has expired, and we have the account email, so we request a new magic link.
- Onyx.merge(ONYXKEYS.ACCOUNT, {error: null});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validateCodeExpired: true, error: null});
resetPassword();
Navigation.navigate(ROUTES.HOME);
return;
@@ -407,6 +448,7 @@ function changePasswordAndSignIn(authToken, password) {
* @param {String} authToken
*/
function validateEmail(accountID, validateCode) {
+ Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: true});
Onyx.merge(ONYXKEYS.SESSION, {error: ''});
DeprecatedAPI.ValidateEmail({
accountID,
@@ -414,16 +456,19 @@ function validateEmail(accountID, validateCode) {
})
.then((responseValidate) => {
if (responseValidate.jsonCode === 200) {
- Onyx.merge(ONYXKEYS.CREDENTIALS, {login: responseValidate.email, authToken: responseValidate.authToken});
+ Onyx.merge(ONYXKEYS.USER_SIGN_UP, {authToken: responseValidate.authToken});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validated: true});
+ Onyx.merge(ONYXKEYS.CREDENTIALS, {login: responseValidate.email});
return;
}
if (responseValidate.jsonCode === 666) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {validated: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {accountExists: true, validated: true});
}
if (responseValidate.jsonCode === 401) {
Onyx.merge(ONYXKEYS.SESSION, {error: 'setPasswordPage.setPasswordLinkInvalid'});
}
- });
+ })
+ .finally(Onyx.merge(ONYXKEYS.USER_SIGN_UP, {isValidating: false}));
}
// It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to
@@ -492,12 +537,13 @@ function setShouldShowComposeInput(shouldShowComposeInput) {
}
export {
- beginSignIn,
+ fetchAccountDetails,
setPassword,
signIn,
signInWithShortLivedToken,
signOut,
signOutAndRedirectToSignIn,
+ reopenAccount,
resendValidationLink,
resetPassword,
clearSignInData,
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index a7b71d9fd3b5..d41fcf3ef974 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -50,21 +50,21 @@ function updatePassword(oldPassword, password) {
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
- value: {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true},
+ value: {...CONST.DEFAULT_ACCOUNT_DATA, loading: true},
},
],
successData: [
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
- value: {isLoading: false},
+ value: {loading: false},
},
],
failureData: [
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
- value: {isLoading: false},
+ value: {loading: false},
},
],
});
@@ -177,7 +177,7 @@ function updateNewsletterSubscription(isSubscribed) {
* @returns {Promise}
*/
function setSecondaryLoginAndNavigate(login, password) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
return DeprecatedAPI.User_SecondaryLogin_Send({
email: login,
@@ -202,7 +202,7 @@ function setSecondaryLoginAndNavigate(login, password) {
Onyx.merge(ONYXKEYS.USER, {error});
}).finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
});
}
@@ -215,7 +215,7 @@ function setSecondaryLoginAndNavigate(login, password) {
function validateLogin(accountID, validateCode) {
const isLoggedIn = !_.isEmpty(sessionAuthToken);
const redirectRoute = isLoggedIn ? ROUTES.getReportRoute(currentlyViewedReportID) : ROUTES.HOME;
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, loading: true});
DeprecatedAPI.ValidateEmail({
accountID,
@@ -236,7 +236,7 @@ function validateLogin(accountID, validateCode) {
Onyx.merge(ONYXKEYS.ACCOUNT, {error});
}
}).finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
+ Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false});
Navigation.navigate(redirectRoute);
});
}
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
index 4ec69b3c60a6..ebbff2141e26 100644
--- a/src/libs/deprecatedAPI.js
+++ b/src/libs/deprecatedAPI.js
@@ -134,6 +134,18 @@ function Get(parameters, shouldUseSecure = false) {
return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, shouldUseSecure);
}
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @param {Boolean} parameters.forceNetworkRequest
+ * @returns {Promise}
+ */
+function GetAccountStatus(parameters) {
+ const commandName = 'GetAccountStatus';
+ requireParameters(['email'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
/**
* @param {Object} parameters
* @param {String} parameters.debtorEmail
@@ -329,6 +341,17 @@ function SetPassword(parameters) {
return Network.post(commandName, parameters);
}
+/**
+ * @param {Object} parameters
+ * @param {String} parameters.email
+ * @returns {Promise}
+ */
+function User_ReopenAccount(parameters) {
+ const commandName = 'User_ReopenAccount';
+ requireParameters(['email'], parameters, commandName);
+ return Network.post(commandName, parameters);
+}
+
/**
* @param {Object} parameters
* @param {String} parameters.email
@@ -694,6 +717,7 @@ export {
DeleteLogin,
DeleteBankAccount,
Get,
+ GetAccountStatus,
GetStatementPDF,
GetIOUReport,
GetFullPolicy,
@@ -716,6 +740,7 @@ export {
UpdatePolicy,
User_SignUp,
User_IsUsingExpensifyCard,
+ User_ReopenAccount,
User_SecondaryLogin_Send,
User_UploadAvatar,
User_FixAccount,
diff --git a/src/pages/SetPasswordPage.js b/src/pages/SetPasswordPage.js
index d3b14a6d3a7c..ec5c3fb3599d 100755
--- a/src/pages/SetPasswordPage.js
+++ b/src/pages/SetPasswordPage.js
@@ -29,7 +29,7 @@ const propTypes = {
error: PropTypes.string,
/** Whether a sign on form is loading (being submitted) */
- isLoading: PropTypes.bool,
+ loading: PropTypes.bool,
}),
/** The credentials of the logged in person */
@@ -47,6 +47,15 @@ const propTypes = {
error: PropTypes.string,
}),
+ /** User signup object */
+ userSignUp: PropTypes.shape({
+ /** Is Validating Email */
+ isValidating: PropTypes.bool,
+
+ /** Auth token used to change password */
+ authToken: PropTypes.string,
+ }),
+
/** The accountID and validateCode are passed via the URL */
route: validateLinkPropTypes,
@@ -61,6 +70,10 @@ const defaultProps = {
error: '',
authToken: '',
},
+ userSignUp: {
+ isValidating: false,
+ authToken: '',
+ },
};
class SetPasswordPage extends Component {
@@ -78,7 +91,7 @@ class SetPasswordPage extends Component {
componentDidMount() {
const accountID = lodashGet(this.props.route.params, 'accountID', '');
const validateCode = lodashGet(this.props.route.params, 'validateCode', '');
- if (this.props.credentials.authToken) {
+ if (this.props.userSignUp.authToken) {
return;
}
Session.validateEmail(accountID, validateCode);
@@ -91,15 +104,15 @@ class SetPasswordPage extends Component {
const accountID = lodashGet(this.props.route.params, 'accountID', '');
const validateCode = lodashGet(this.props.route.params, 'validateCode', '');
- if (this.props.credentials.authToken) {
- Session.changePasswordAndSignIn(this.props.credentials.authToken, this.state.password);
+ if (this.props.userSignUp.authToken) {
+ Session.changePasswordAndSignIn(this.props.userSignUp.authToken, this.state.password);
} else {
Session.setPassword(this.state.password, validateCode, accountID);
}
}
render() {
- const buttonText = !this.props.account.validated ? this.props.translate('setPasswordPage.validatingAccount') : this.props.translate('setPasswordPage.setPassword');
+ const buttonText = this.props.userSignUp.isValidating ? this.props.translate('setPasswordPage.verifyingAccount') : this.props.translate('setPasswordPage.setPassword');
const sessionError = this.props.session.error && this.props.translate(this.props.session.error);
const error = sessionError || this.props.account.error;
return (
@@ -120,7 +133,7 @@ class SetPasswordPage extends Component {
diff --git a/src/pages/signin/PasswordForm.js b/src/pages/signin/PasswordForm.js
index 7936e59f1c74..0801487c8803 100755
--- a/src/pages/signin/PasswordForm.js
+++ b/src/pages/signin/PasswordForm.js
@@ -26,11 +26,14 @@ const propTypes = {
/** The details about the account that the user is signing in with */
account: PropTypes.shape({
+ /** Whether or not the account already exists */
+ accountExists: PropTypes.bool,
+
/** Whether or not two factor authentication is required */
requiresTwoFactorAuth: PropTypes.bool,
/** Whether or not a sign on form is loading (being submitted) */
- isLoading: PropTypes.bool,
+ loading: PropTypes.bool,
}),
...withLocalizePropTypes,
@@ -180,7 +183,7 @@ class PasswordForm extends React.Component {
success
style={[styles.mv3]}
text={this.props.translate('common.signIn')}
- isLoading={this.props.account.isLoading}
+ isLoading={this.props.account.loading}
onPress={this.validateAndSubmitForm}
/>
diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js
index e643e94eed73..95956cb7b933 100755
--- a/src/pages/signin/ResendValidationForm.js
+++ b/src/pages/signin/ResendValidationForm.js
@@ -34,6 +34,12 @@ const propTypes = {
/** Whether or not the account is validated */
validated: PropTypes.bool,
+
+ /** Whether or not the account is closed */
+ closed: PropTypes.bool,
+
+ /** Whether or not the account already exists */
+ accountExists: PropTypes.bool,
}),
/** Information about the network */
@@ -73,7 +79,9 @@ class ResendValidationForm extends React.Component {
formSuccess: this.props.translate('resendValidationForm.linkHasBeenResent'),
});
- if (!this.props.account.validated) {
+ if (this.props.account.closed) {
+ Session.reopenAccount();
+ } else if (!this.props.account.validated) {
Session.resendValidationLink();
} else {
Session.resetPassword();
@@ -85,10 +93,27 @@ class ResendValidationForm extends React.Component {
}
render() {
+ const isNewAccount = !this.props.account.accountExists;
+ const isOldUnvalidatedAccount = this.props.account.accountExists && !this.props.account.validated;
const isSMSLogin = Str.isSMSLogin(this.props.credentials.login);
const login = isSMSLogin ? this.props.toLocalPhone(Str.removeSMSDomain(this.props.credentials.login)) : this.props.credentials.login;
const loginType = (isSMSLogin ? this.props.translate('common.phone') : this.props.translate('common.email')).toLowerCase();
-
+ let message = '';
+
+ if (isNewAccount) {
+ message = this.props.translate('resendValidationForm.newAccount', {
+ login,
+ loginType,
+ });
+ } else if (this.props.account.validateCodeExpired) {
+ message = this.props.translate('resendValidationForm.validationCodeFailedMessage');
+ } else if (isOldUnvalidatedAccount) {
+ message = this.props.translate('resendValidationForm.unvalidatedAccount');
+ } else {
+ message = this.props.translate('resendValidationForm.weSentYouMagicSignInLink', {
+ login,
+ });
+ }
return (
<>
@@ -104,7 +129,7 @@ class ResendValidationForm extends React.Component {
- {this.props.translate('resendValidationForm.weSentYouMagicSignInLink', {login, loginType})}
+ {message}
{!_.isEmpty(this.state.formSuccess) && (
diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js
index c19d8ba09fe9..c0d7945e9945 100644
--- a/src/pages/signin/SignInPage.js
+++ b/src/pages/signin/SignInPage.js
@@ -4,6 +4,7 @@ import {
} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
+import lodashGet from 'lodash/get';
import ONYXKEYS from '../../ONYXKEYS';
import styles from '../../styles/styles';
import updateUnread from '../../libs/UnreadIndicatorUpdater/updateUnread/index';
@@ -19,11 +20,17 @@ const propTypes = {
/** The details about the account that the user is signing in with */
account: PropTypes.shape({
+ /** Whether or not the account already exists */
+ accountExists: PropTypes.bool,
+
/** Error to display when there is an account error returned */
error: PropTypes.string,
- /** Whether the account is validated */
+ /** Whether or not the account is validated */
validated: PropTypes.bool,
+
+ /** Whether or not the account is validated */
+ forgotPassword: PropTypes.bool,
}),
/** The credentials of the person signing in */
@@ -53,21 +60,27 @@ class SignInPage extends Component {
// - A login has not been entered yet
const showLoginForm = !this.props.credentials.login;
+ const validateCodeExpired = lodashGet(this.props.account, 'validateCodeExpired', false);
+
+ const validAccount = this.props.account.accountExists
+ && this.props.account.validated
+ && !this.props.account.forgotPassword
+ && !validateCodeExpired;
+
// Show the password form if
// - A login has been entered
+ // - AND a GitHub username has been entered OR they already have access to New Expensify
// - AND an account exists and is validated for this login
// - AND a password hasn't been entered yet
- // - AND haven't forgotten password
const showPasswordForm = this.props.credentials.login
- && this.props.account.validated
- && !this.props.credentials.password
- && !this.props.account.forgotPassword;
+ && validAccount
+ && !this.props.credentials.password;
// Show the resend validation link form if
// - A login has been entered
- // - AND is not validated or password is forgotten
- const shouldShowResendValidationLinkForm = this.props.credentials.login
- && (!this.props.account.validated || this.props.account.forgotPassword);
+ // - AND a GitHub username has been entered OR they already have access to this app
+ // - AND an account did not exist or is not validated for that login
+ const shouldShowResendValidationLinkForm = this.props.credentials.login && !validAccount;
const welcomeText = shouldShowResendValidationLinkForm
? ''
diff --git a/tests/actions/ReimbursementAccountTest.js b/tests/actions/ReimbursementAccountTest.js
index cd0ae97aeda6..0c0b53c25f90 100644
--- a/tests/actions/ReimbursementAccountTest.js
+++ b/tests/actions/ReimbursementAccountTest.js
@@ -259,30 +259,25 @@ describe('actions/BankAccounts', () => {
expect(reimbursementAccount.error).toBe('');
expect(reimbursementAccount.achData.currentStep).toBe(CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT);
- HttpUtils.xhr.mockImplementation((command) => {
- // WHEN we mock a sucessful call to SetupWithdrawalAccount while on the ACHContractStep
- switch (command) {
- case 'BankAccount_SetupWithdrawal':
- return Promise.resolve({
- jsonCode: 200,
- achData: {
- bankAccountID: TEST_BANK_ACCOUNT_ID,
- },
- });
-
- // And mock the response of Get&returnValueList=bankAccountList
- case 'Get':
- return Promise.resolve({
- jsonCode: 200,
- bankAccountList: [{
- bankAccountID: TEST_BANK_ACCOUNT_ID,
- state: BankAccount.STATE.PENDING,
- }],
- });
- default:
- return Promise.resolve({jsonCode: 200});
- }
- });
+ // WHEN we mock a sucessful call to SetupWithdrawalAccount while on the ACHContractStep
+ HttpUtils.xhr.mockImplementationOnce(() => Promise.resolve({
+ jsonCode: 200,
+ achData: {
+ bankAccountID: TEST_BANK_ACCOUNT_ID,
+ },
+ }));
+
+ // And mock SetNameValuePair response
+ HttpUtils.xhr.mockImplementationOnce(() => Promise.resolve({jsonCode: 200}));
+
+ // And mock the response of Get&returnValueList=bankAccountList
+ HttpUtils.xhr.mockImplementationOnce(() => Promise.resolve({
+ jsonCode: 200,
+ bankAccountList: [{
+ bankAccountID: TEST_BANK_ACCOUNT_ID,
+ state: BankAccount.STATE.PENDING,
+ }],
+ }));
// WHEN we call setupWithdrawalAccount via the ACHContractStep
BankAccounts.setupWithdrawalAccount({
diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js
index 3955959b2d1f..da8c4de756fb 100644
--- a/tests/actions/ReportTest.js
+++ b/tests/actions/ReportTest.js
@@ -204,6 +204,7 @@ describe('actions/Report', () => {
it('should be updated correctly when new comments are added, deleted or marked as unread', () => {
const REPORT_ID = 1;
+
let report;
Onyx.connect({
key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`,
diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.js
index 0b5ce51ba248..b09b23d49645 100644
--- a/tests/unit/NetworkTest.js
+++ b/tests/unit/NetworkTest.js
@@ -260,7 +260,7 @@ test('Request will not run until credentials are read from Onyx', () => {
const spyHttpUtilsXhr = jest.spyOn(HttpUtils, 'xhr').mockImplementation(() => Promise.resolve({}));
// When we make a request
- Session.beginSignIn(TEST_USER_LOGIN);
+ Session.fetchAccountDetails(TEST_USER_LOGIN);
// Then we should expect that no requests have been made yet
expect(spyHttpUtilsXhr).not.toHaveBeenCalled();
diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js
index e6a7dc5b3e97..d619e9d72ab2 100644
--- a/tests/utils/TestHelper.js
+++ b/tests/utils/TestHelper.js
@@ -20,32 +20,27 @@ function signInWithTestUser(accountID = 1, login = 'test@user.com', password = '
const originalXhr = HttpUtils.xhr;
HttpUtils.xhr = jest.fn();
HttpUtils.xhr.mockImplementation(() => Promise.resolve({
- onyxData: [
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: ONYXKEYS.CREDENTIALS,
- value: {
- login,
- },
- },
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: ONYXKEYS.ACCOUNT,
- value: {
- validated: true,
- },
- },
- ],
jsonCode: 200,
+ accountExists: true,
+ requiresTwoFactorAuth: false,
+ normalizedLogin: login,
}));
// Simulate user entering their login and populating the credentials.login
- Session.beginSignIn(login);
+ Session.fetchAccountDetails(login);
return waitForPromisesToResolve()
.then(() => {
- // Response is the same for calls to Authenticate and CreateLogin
+ // First call to Authenticate
HttpUtils.xhr
- .mockImplementation(() => Promise.resolve({
+ .mockImplementationOnce(() => Promise.resolve({
+ jsonCode: 200,
+ accountID,
+ authToken,
+ email: login,
+ }))
+
+ // Next call to CreateLogin
+ .mockImplementationOnce(() => Promise.resolve({
jsonCode: 200,
accountID,
authToken,