diff --git a/src/CONST.ts b/src/CONST.ts index 5a0c6f395eb4..69dd4d9538f7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -716,8 +716,6 @@ 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`, COMPANY_CARDS_HELP: 'https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds', CUSTOM_REPORT_NAME_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/spending-insights/Custom-Templates', @@ -2305,6 +2303,12 @@ const CONST = { billCom: 'Bill.com', zenefits: 'Zenefits', }, + AUTH_HELP_LINKS: { + intacct: + "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.", + netsuite: + 'https://help.expensify.com/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting#expensierror-ns0109-failed-to-login-to-netsuite-please-verify-your-credentials', + }, SYNC_STAGE_NAME: { STARTING_IMPORT_QBO: 'startingImportQBO', STARTING_IMPORT_XERO: 'startingImportXero', diff --git a/src/components/ConnectToNetSuiteFlow/index.tsx b/src/components/ConnectToNetSuiteFlow/index.tsx index f946742f8cc3..5eb548c2acb5 100644 --- a/src/components/ConnectToNetSuiteFlow/index.tsx +++ b/src/components/ConnectToNetSuiteFlow/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 useResponsiveLayout from '@hooks/useResponsiveLayout'; +import {isAuthenticationError} from '@libs/actions/connections'; import {getAdminPoliciesConnectedToNetSuite} 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'; import type {ConnectToNetSuiteFlowProps} from './types'; @@ -20,6 +23,9 @@ function ConnectToNetSuiteFlow({policyID}: ConnectToNetSuiteFlowProps) { const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState({horizontal: 0, vertical: 0}); const {popoverAnchorRefs} = useAccountingContext(); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const shouldGoToCredentialsPage = isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.NETSUITE); + const threeDotsMenuContainerRef = popoverAnchorRefs?.current?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]; const connectionOptions = [ @@ -42,7 +48,7 @@ function ConnectToNetSuiteFlow({policyID}: ConnectToNetSuiteFlowProps) { ]; useEffect(() => { - if (!hasPoliciesConnectedToNetSuite) { + if (shouldGoToCredentialsPage || !hasPoliciesConnectedToNetSuite) { Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID)); return; } diff --git a/src/languages/en.ts b/src/languages/en.ts index eae87b70ecca..da7556c2c7fb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2232,6 +2232,8 @@ export default { reuseExistingConnection: 'Reuse existing connection', existingConnections: 'Existing connections', lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Last synced ${formattedDate}`, + authenticationError: (connectionName: string) => `Can’t connect to ${connectionName} due to an authentication error.`, + learnMore: 'Learn more.', memberAlternateText: 'Members can submit and approve reports.', adminAlternateText: 'Admins have full edit access to all reports and workspace settings.', auditorAlternateText: 'Auditors can view and comment on reports.', @@ -2474,8 +2476,6 @@ 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 d94db88f2720..763a2432c801 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2262,6 +2262,8 @@ export default { existingConnections: 'Conexiones existentes', lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Última sincronización ${formattedDate}`, topLevel: 'Nivel superior', + authenticationError: (connectionName: string) => `No se puede conectar a ${connectionName} debido a un error de autenticación.`, + learnMore: 'Más información.', memberAlternateText: 'Los miembros pueden presentar y aprobar informes.', adminAlternateText: 'Los administradores tienen acceso total para editar todos los informes y la configuración del área de trabajo.', auditorAlternateText: 'Los auditores pueden ver y comentar los informes.', @@ -2515,8 +2517,6 @@ 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/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 20581d40ed0b..79875a161103 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -307,9 +307,6 @@ function hasSynchronizationErrorMessage(policy: OnyxEntry, connectionNam } function isAuthenticationError(policy: OnyxEntry, connectionName: PolicyConnectionName) { - if (connectionName === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) { - return false; - } const connection = policy?.connections?.[connectionName]; return connection?.lastSync?.isAuthenticationError === true; } diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index a7a5382fbd8a..7ad01eece1da 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -87,9 +87,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? connectionSyncProgress?.connectionName; 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.NETSUITE; + const shouldShowEnterCredentials = connectedIntegration && !!synchronizationError && isAuthenticationError(policy, connectedIntegration); 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. @@ -106,7 +104,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( () => [ - ...(shouldShowEnterCredentials + ...(shouldShowEnterCredentials && (connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT || connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) ? [ { icon: Expensicons.Key, @@ -115,7 +113,6 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { shouldCallAfterModalHide: true, disabled: isOffline, iconRight: Expensicons.NewWindow, - shouldShowRightIcon: connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT, }, ] : [ diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx index 178db5534f49..556cc589796c 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx @@ -7,10 +7,12 @@ import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSu import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isAuthenticationError} from '@libs/actions/connections'; import Navigation from '@libs/Navigation/Navigation'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import CONST from '@src/CONST'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import NetSuiteTokenInputForm from './substeps/NetSuiteTokenInputForm'; import NetSuiteTokenSetupContent from './substeps/NetSuiteTokenSetupContent'; @@ -51,6 +53,8 @@ function NetSuiteTokenInputPage({policy}: WithPolicyConnectionsProps) { nextScreen(); }; + const shouldPageBeBlocked = !isEmptyObject(policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]) && !isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.NETSUITE); + return ( + {translate('workspace.common.authenticationError', CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName])} + {connectionName in CONST.POLICY.CONNECTIONS.AUTH_HELP_LINKS && ( + + {translate('workspace.common.learnMore')} + + )} + + ); + } + 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) { @@ -274,19 +290,6 @@ function getSynchronizationErrorMessage( 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}")`; } diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1f1be7e89c20..aae6486c8130 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -995,8 +995,8 @@ type NetSuiteConnection = { /** Date when the connection's last failed sync occurred */ lastErrorSyncDate: string; - /** Where did the connection's last sync came from */ - source: JobSourceValues; + /** State of the last synchronization */ + lastSync?: ConnectionLastSync; /** Config object used solely to store autosync settings */ config: OnyxCommon.OnyxValueWithOfflineFeedback<{