From e8bb6d0d8003f1124bfa6b19a1dc040b9f572281 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 26 Aug 2024 14:26:43 +0200 Subject: [PATCH 1/4] add wrong credentials error handling --- src/components/ConnectToSageIntacctFlow/index.tsx | 9 +++++++++ src/pages/workspace/accounting/PolicyAccountingPage.tsx | 8 ++------ .../intacct/EnterSageIntacctCredentialsPage.tsx | 3 +-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/ConnectToSageIntacctFlow/index.tsx b/src/components/ConnectToSageIntacctFlow/index.tsx index 5d7b36eb0bbb..2d1913b83438 100644 --- a/src/components/ConnectToSageIntacctFlow/index.tsx +++ b/src/components/ConnectToSageIntacctFlow/index.tsx @@ -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 = { @@ -23,6 +26,8 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) { const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState({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 = [ { @@ -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; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 11c47a21f2e1..97793d454674 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -75,11 +75,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { // 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. @@ -101,7 +97,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { onSelected: () => Modal.close(() => startIntegrationFlow({name: connectedIntegration})), disabled: isOffline, iconRight: Expensicons.NewWindow, - shouldShowRightIcon: true, + shouldShowRightIcon: connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT, }, ] : [ diff --git a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx index 71728d523a37..9814e5cef996 100644 --- a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx +++ b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx @@ -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'; @@ -58,7 +57,7 @@ function EnterSageIntacctCredentialsPage({route}: SageIntacctPrerequisitesPagePr > Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.getRoute(policyID))} + onBackButtonPress={() => Navigation.goBack()} /> Date: Mon, 26 Aug 2024 16:31:51 +0200 Subject: [PATCH 2/4] delete empty ConnectToIntegration files --- src/components/ConnectToNetSuiteButton/index.tsx | 0 src/components/ConnectToQuickbooksOnlineButton/index.native.tsx | 0 src/components/ConnectToQuickbooksOnlineButton/index.tsx | 0 src/components/ConnectToSageIntacctButton/index.tsx | 0 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/components/ConnectToNetSuiteButton/index.tsx delete mode 100644 src/components/ConnectToQuickbooksOnlineButton/index.native.tsx delete mode 100644 src/components/ConnectToQuickbooksOnlineButton/index.tsx delete mode 100644 src/components/ConnectToSageIntacctButton/index.tsx diff --git a/src/components/ConnectToNetSuiteButton/index.tsx b/src/components/ConnectToNetSuiteButton/index.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToSageIntacctButton/index.tsx b/src/components/ConnectToSageIntacctButton/index.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 From 7d5169c429e552f99fa9eff7df298fa3e0353fa1 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 2 Sep 2024 23:43:44 +0200 Subject: [PATCH 3/4] adjust error to Sage Intacct design --- src/CONST.ts | 2 + src/components/MenuItem.tsx | 2 +- src/languages/en.ts | 2 + src/languages/es.ts | 2 + src/libs/PolicyUtils.ts | 4 +- src/libs/actions/connections/index.ts | 21 +++----- .../accounting/PolicyAccountingPage.tsx | 6 +-- src/pages/workspace/accounting/utils.tsx | 50 ++++++++++++++++++- 8 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 971fefc2d1b7..cefde1a9ecd0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -626,6 +626,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.", PRICING: `https://www.expensify.com/pricing`, // Use Environment.getEnvironmentURL to get the complete URL with port number diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 6757d0602691..a154a273376a 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -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; diff --git a/src/languages/en.ts b/src/languages/en.ts index 632da6f9c3ae..acec14419cde 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2402,6 +2402,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', diff --git a/src/languages/es.ts b/src/languages/es.ts index 77ba5e5abf45..4815d11808a3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2444,6 +2444,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', diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 3f3a2a96a1e1..a54b151cacbc 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -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'; @@ -90,7 +90,7 @@ function hasPolicyCategoriesError(policyCategories: OnyxEntry) * Checks if the policy had a sync error. */ function hasSyncError(policy: OnyxEntry): boolean { - return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, false)); + return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => hasSynchronizationErrorMessage(policy, connection, false)); } /** diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index fa2e274204a2..2e3ef7adae6d 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -5,7 +5,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'; @@ -369,24 +368,20 @@ function updateManyPolicyConnectionConfigs, connectionName: PolicyConnectionName, isSyncInProgress: boolean): string | undefined { - const syncError = Localize.translateLocal('workspace.accounting.syncError', connectionName); +function hasSynchronizationErrorMessage(policy: OnyxEntry, connectionName: PolicyConnectionName): 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; + if (!!policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE].lastErrorSyncDate || policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]?.verified === false) { + return true; } - return; + return false; } const connection = policy?.connections?.[connectionName]; - if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful) { - return; + if (isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful) { + return false; } - return `${syncError} ("${connection?.lastSync?.errorMessage}")`; + return true; } function isAuthenticationError(policy: OnyxEntry, connectionName: PolicyConnectionName) { @@ -453,9 +448,9 @@ export { updatePolicyConnectionConfig, updatePolicyXeroConnectionConfig, updateManyPolicyConnectionConfigs, - getSynchronizationErrorMessage, isAuthenticationError, syncConnection, copyExistingPolicyConnection, isConnectionUnverified, + hasSynchronizationErrorMessage, }; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 97793d454674..624e8129f332 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -23,7 +23,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {getSynchronizationErrorMessage, isAuthenticationError, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections'; +import {isAuthenticationError, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections'; import { areSettingsInErrorFields, findCurrentXeroOrganization, @@ -45,7 +45,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}`); @@ -71,7 +71,7 @@ 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 = diff --git a/src/pages/workspace/accounting/utils.tsx b/src/pages/workspace/accounting/utils.tsx index 22bc97236b70..e4b1be9fa9d3 100644 --- a/src/pages/workspace/accounting/utils.tsx +++ b/src/pages/workspace/accounting/utils.tsx @@ -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/ConnectToXero'; 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( @@ -136,5 +143,44 @@ function getAccountingIntegrationData( } } -// eslint-disable-next-line import/prefer-default-export -export {getAccountingIntegrationData}; +function getSynchronizationErrorMessage( + policy: OnyxEntry, + 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) { + return; + } + + if (connectionName === CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT && isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT)) { + return ( + + {translate('workspace.sageIntacct.authenticationError')} + + {translate('workspace.sageIntacct.learnMore')} + + + ); + } + return `${syncError} ("${connection?.lastSync?.errorMessage}")`; +} + +export {getAccountingIntegrationData, getSynchronizationErrorMessage}; From ffdf078b875e955efcfdfa64e255d654e1d41b2f Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 2 Sep 2024 23:49:39 +0200 Subject: [PATCH 4/4] fix translations --- src/languages/en.ts | 4 ++-- src/languages/es.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index acec14419cde..2e5fce4fb77f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2402,8 +2402,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', + authenticationError: 'Can’t connect to Sage Intacct due to an authentication error. ', + learnMore: 'Learn more.', }, netsuite: { subsidiary: 'Subsidiary', diff --git a/src/languages/es.ts b/src/languages/es.ts index 4815d11808a3..6aeee021b7d6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2444,8 +2444,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', + authenticationError: 'No se puede conectar a Sage Intacct debido a un error de autenticación. ', + learnMore: 'Más información.', }, netsuite: { subsidiary: 'Subsidiaria',