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

Add error handling when user gives wrong credentials. #48001

2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@ const CONST = {
EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT_FILE_NAME: 'ExpensifyPackageForSageIntacct',
SAGE_INTACCT_INSTRUCTIONS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct',
HOW_TO_CONNECT_TO_SAGE_INTACCT: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct#how-to-connect-to-sage-intacct',
SAGE_INTACCT_HELP_LINK:
"https://help.expensify.com/articles/expensify-classic/connections/sage-intacct/Sage-Intacct-Troubleshooting#:~:text=First%20make%20sure%20that%20you,your%20company's%20Web%20Services%20authorizations.",
Copy link
Contributor

Choose a reason for hiding this comment

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

This period at the end shouldn't be there but it doesn't break the link for me, so NAB

PRICING: `https://www.expensify.com/pricing`,

// Use Environment.getEnvironmentURL to get the complete URL with port number
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh weird, I thought we already deleted these files a while ago. Can you merge main?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Merged main, I think there was some strange merge conflict a while ago and somebody didn't notice it.

Empty file.
Empty file.
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions src/components/ConnectToSageIntacctFlow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, {useEffect, useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import * as Expensicons from '@components/Icon/Expensicons';
import PopoverMenu from '@components/PopoverMenu';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {isAuthenticationError} from '@libs/actions/connections';
import {getAdminPoliciesConnectedToSageIntacct} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
import {useAccountingContext} from '@pages/workspace/accounting/AccountingContext';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

type ConnectToSageIntacctFlowProps = {
Expand All @@ -23,6 +26,8 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) {
const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
const {popoverAnchorRefs} = useAccountingContext();
const threeDotsMenuContainerRef = popoverAnchorRefs?.current?.[CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT];
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const shouldGoToEnterCredentials = isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT);

const connectionOptions = [
{
Expand All @@ -44,6 +49,10 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) {
];

useEffect(() => {
if (shouldGoToEnterCredentials) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ENTER_CREDENTIALS.getRoute(policyID));
return;
}
if (!hasPoliciesConnectedToSageIntacct) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.getRoute(policyID));
return;
Expand Down
2 changes: 1 addition & 1 deletion src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ type MenuItemBaseProps = {
shouldShowDescriptionOnTop?: boolean;

/** Error to display at the bottom of the component */
errorText?: string;
errorText?: string | ReactNode;

/** Any additional styles to pass to error text. */
errorTextStyle?: StyleProp<ViewStyle>;
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2443,6 +2443,8 @@ export default {
syncReimbursedReports: 'Sync reimbursed reports',
syncReimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the Sage Intacct account below.',
paymentAccount: 'Sage Intacct payment account',
authenticationError: 'Can’t connect to Sage Intacct due to an authentication error. ',
learnMore: 'Learn more.',
},
netsuite: {
subsidiary: 'Subsidiary',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2487,6 +2487,8 @@ export default {
syncReimbursedReportsDescription:
'Cuando un informe se reembolsa utilizando Expensify ACH, la factura de compra correspondiente se creará en la cuenta de Sage Intacct a continuación.',
paymentAccount: 'Cuenta de pago Sage Intacct',
authenticationError: 'No se puede conectar a Sage Intacct debido a un error de autenticación. ',
learnMore: 'Más información.',
},
netsuite: {
subsidiary: 'Subsidiaria',
Expand Down
4 changes: 2 additions & 2 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type {
} from '@src/types/onyx/Policy';
import type PolicyEmployee from '@src/types/onyx/PolicyEmployee';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {getSynchronizationErrorMessage} from './actions/connections';
import {hasSynchronizationErrorMessage} from './actions/connections';
import * as Localize from './Localize';
import Navigation from './Navigation/Navigation';
import * as NetworkStore from './Network/NetworkStore';
Expand Down Expand Up @@ -90,7 +90,7 @@ function hasPolicyCategoriesError(policyCategories: OnyxEntry<PolicyCategories>)
* Checks if the policy had a sync error.
*/
function hasSyncError(policy: OnyxEntry<Policy>, isSyncInProgress: boolean): boolean {
return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, isSyncInProgress));
return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!hasSynchronizationErrorMessage(policy, connection, isSyncInProgress));
}

/**
Expand Down
14 changes: 6 additions & 8 deletions src/libs/actions/connections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as API from '@libs/API';
import type {RemovePolicyConnectionParams, UpdateManyPolicyConnectionConfigurationsParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as Localize from '@libs/Localize';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
Expand Down Expand Up @@ -370,25 +369,24 @@ function updateManyPolicyConnectionConfigs<TConnectionName extends ConnectionNam
API.write(WRITE_COMMANDS.UPDATE_MANY_POLICY_CONNECTION_CONFIGS, parameters, {optimisticData, failureData, successData});
}

function getSynchronizationErrorMessage(policy: OnyxEntry<Policy>, connectionName: PolicyConnectionName, isSyncInProgress: boolean): string | undefined {
const syncError = Localize.translateLocal('workspace.accounting.syncError', connectionName);
function hasSynchronizationErrorMessage(policy: OnyxEntry<Policy>, connectionName: PolicyConnectionName, isSyncInProgress: boolean): boolean {
// NetSuite does not use the conventional lastSync object, so we need to check for lastErrorSyncDate
if (connectionName === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) {
if (
!isSyncInProgress &&
(!!policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE].lastErrorSyncDate || policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]?.verified === false)
) {
return syncError;
return true;
}
return;
return false;
}

const connection = policy?.connections?.[connectionName];

if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful !== false || !connection?.lastSync?.errorDate) {
return;
return false;
}
return `${syncError} ("${connection?.lastSync?.errorMessage}")`;
return true;
}

function isAuthenticationError(policy: OnyxEntry<Policy>, connectionName: PolicyConnectionName) {
Expand Down Expand Up @@ -469,10 +467,10 @@ export {
updatePolicyConnectionConfig,
updatePolicyXeroConnectionConfig,
updateManyPolicyConnectionConfigs,
getSynchronizationErrorMessage,
isAuthenticationError,
syncConnection,
copyExistingPolicyConnection,
isConnectionUnverified,
isConnectionInProgress,
hasSynchronizationErrorMessage,
};
14 changes: 5 additions & 9 deletions src/pages/workspace/accounting/PolicyAccountingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {getSynchronizationErrorMessage, isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections';
import {isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections';
import {
areSettingsInErrorFields,
findCurrentXeroOrganization,
Expand All @@ -43,7 +43,7 @@ import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {AccountingContextProvider, useAccountingContext} from './AccountingContext';
import type {MenuItemData, PolicyAccountingPageProps} from './types';
import {getAccountingIntegrationData} from './utils';
import {getAccountingIntegrationData, getSynchronizationErrorMessage} from './utils';

function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy.id}`);
Expand All @@ -64,15 +64,11 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {

const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME);
const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? connectionSyncProgress?.connectionName;
const synchronizationError = connectedIntegration && getSynchronizationErrorMessage(policy, connectedIntegration, isSyncInProgress);
const synchronizationError = connectedIntegration && getSynchronizationErrorMessage(policy, connectedIntegration, isSyncInProgress, translate, styles);

// Enter credentials item shouldn't be shown for SageIntacct and NetSuite integrations
const shouldShowEnterCredentials =
connectedIntegration &&
!!synchronizationError &&
isAuthenticationError(policy, connectedIntegration) &&
connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT &&
connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.NETSUITE;
connectedIntegration && !!synchronizationError && isAuthenticationError(policy, connectedIntegration) && connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.NETSUITE;

const policyID = policy?.id ?? '-1';
// Get the last successful date of the integration. Then, if `connectionSyncProgress` is the same integration displayed and the state is 'jobDone', get the more recent update time of the two.
Expand All @@ -95,7 +91,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
shouldCallAfterModalHide: true,
disabled: isOffline,
iconRight: Expensicons.NewWindow,
shouldShowRightIcon: true,
shouldShowRightIcon: connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT,
},
]
: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/SageIntactCredentialsForm';

Expand Down Expand Up @@ -58,7 +57,7 @@ function EnterSageIntacctCredentialsPage({route}: SageIntacctPrerequisitesPagePr
>
<HeaderWithBackButton
title={translate('workspace.intacct.sageIntacctSetup')}
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.getRoute(policyID))}
onBackButtonPress={() => Navigation.goBack()}
/>
<FormProvider
style={[styles.flexGrow1, styles.ph5]}
Expand Down
50 changes: 48 additions & 2 deletions src/pages/workspace/accounting/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import React from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import ConnectToNetSuiteFlow from '@components/ConnectToNetSuiteFlow';
import ConnectToQuickbooksOnlineFlow from '@components/ConnectToQuickbooksOnlineFlow';
import ConnectToSageIntacctFlow from '@components/ConnectToSageIntacctFlow';
import ConnectToXeroFlow from '@components/ConnectToXeroFlow';
import * as Expensicons from '@components/Icon/Expensicons';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import {isAuthenticationError} from '@libs/actions/connections';
import * as Localize from '@libs/Localize';
import Navigation from '@navigation/Navigation';
import type {ThemeStyles} from '@styles/index';
import {getTrackingCategories} from '@userActions/connections/Xero';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Policy} from '@src/types/onyx';
import type {PolicyConnectionName} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {AccountingIntegration} from './types';

function getAccountingIntegrationData(
Expand Down Expand Up @@ -136,5 +143,44 @@ function getAccountingIntegrationData(
}
}

// eslint-disable-next-line import/prefer-default-export
export {getAccountingIntegrationData};
function getSynchronizationErrorMessage(
policy: OnyxEntry<Policy>,
connectionName: PolicyConnectionName,
isSyncInProgress: boolean,
translate: LocaleContextProps['translate'],
styles?: ThemeStyles,
): React.ReactNode | undefined {
const syncError = Localize.translateLocal('workspace.accounting.syncError', connectionName);
// NetSuite does not use the conventional lastSync object, so we need to check for lastErrorSyncDate
if (connectionName === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) {
if (
!isSyncInProgress &&
(!!policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE].lastErrorSyncDate || policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]?.verified === false)
) {
return syncError;
}
return;
}

const connection = policy?.connections?.[connectionName];
if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful !== false || !connection?.lastSync?.errorDate) {
return;
}

if (connectionName === CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT && isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT)) {
return (
<Text style={[styles?.formError]}>
<Text style={[styles?.formError]}>{translate('workspace.sageIntacct.authenticationError')}</Text>
<TextLink
style={[styles?.link, styles?.fontSizeLabel]}
href={CONST.SAGE_INTACCT_HELP_LINK}
>
{translate('workspace.sageIntacct.learnMore')}
</TextLink>
</Text>
);
}
return `${syncError} ("${connection?.lastSync?.errorMessage}")`;
}

export {getAccountingIntegrationData, getSynchronizationErrorMessage};
Loading