diff --git a/src/CONST/index.ts b/src/CONST/index.ts index abde1d6daf5d..9b560a95cff8 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -5611,6 +5611,7 @@ const CONST = { DIGITS_SYNC_PAYMENTS: 'digitsSyncPayments', DIGITS_SYNC_CARD_SETTLEMENTS: 'digitsSyncCardSettlements', DIGITS_SYNC_TRAVEL_SETTLEMENTS: 'digitsSyncTravelSettlements', + ZOHO_BOOKS_SYNC_TITLE: 'zohoBooksSyncTitle', ZOHO_BOOKS_SYNC_CONNECTION: 'zohoBooksSyncConnection', BUSINESS_CENTRAL_SYNC_TITLE: 'businessCentralSyncTitle', BUSINESS_CENTRAL_SYNC_CONNECTION: 'businessCentralSyncConnection', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7eb28788940b..cdf87ad00e44 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -4682,6 +4682,14 @@ const ROUTES = { route: 'workspaces/:policyID/accounting/zoho-books/oauth', getRoute: (policyID: string) => `workspaces/${policyID}/accounting/zoho-books/oauth` as const, }, + POLICY_ACCOUNTING_ZOHO_BOOKS_EXISTING_CONNECTIONS: { + route: 'workspaces/:policyID/accounting/zoho-books/existing-connections', + getRoute: (policyID: string) => `workspaces/${policyID}/accounting/zoho-books/existing-connections` as const, + }, + POLICY_ACCOUNTING_ZOHO_BOOKS_ORGANIZATION_SELECTOR: { + route: 'workspaces/:policyID/accounting/zoho-books/organization-selector', + getRoute: (policyID: string) => `workspaces/${policyID}/accounting/zoho-books/organization-selector` as const, + }, POLICY_ACCOUNTING_XERO_IMPORT: { route: 'workspaces/:policyID/accounting/xero/import', getRoute: (policyID: string) => `workspaces/${policyID}/accounting/xero/import` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 55ed0e80ebd5..34a894bc0385 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -605,6 +605,8 @@ const SCREENS = { QUICKBOOKS_DESKTOP_ITEMS: 'Policy_Accounting_Quickbooks_Desktop_Import_Items', XERO_SETUP: 'Policy_Accounting_Xero_Setup', ZOHO_BOOKS_OAUTH: 'Policy_Accounting_Zoho_Books_OAuth', + ZOHO_BOOKS_EXISTING_CONNECTIONS: 'Policy_Accounting_Zoho_Books_Existing_Connections', + ZOHO_BOOKS_ORGANIZATION_SELECTOR: 'Policy_Accounting_Zoho_Books_Organization_Selector', XERO_IMPORT: 'Policy_Accounting_Xero_Import', CLAIM_OFFER: 'Policy_Accounting_Claim_Offer', XERO_ORGANIZATION: 'Policy_Accounting_Xero_Customers', diff --git a/src/components/ConnectToZohoBooksFlow/index.native.tsx b/src/components/ConnectToZohoBooksFlow/index.native.tsx deleted file mode 100644 index 86c08cc52d17..000000000000 --- a/src/components/ConnectToZohoBooksFlow/index.native.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Navigation from '@libs/Navigation/Navigation'; - -import ROUTES from '@src/ROUTES'; - -import {useEffect} from 'react'; - -import type {ConnectToZohoBooksFlowProps} from './types'; - -function ConnectToZohoBooksFlow({policyID}: ConnectToZohoBooksFlowProps) { - useEffect(() => { - // On native the OAuth setup loads inside an in-app WebView screen. - Navigation.navigate(ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_OAUTH.getRoute(policyID)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return null; -} - -export default ConnectToZohoBooksFlow; diff --git a/src/components/ConnectToZohoBooksFlow/index.tsx b/src/components/ConnectToZohoBooksFlow/index.tsx index 552cecf90a76..b70a14fd66c7 100644 --- a/src/components/ConnectToZohoBooksFlow/index.tsx +++ b/src/components/ConnectToZohoBooksFlow/index.tsx @@ -1,20 +1,29 @@ -import useEnvironment from '@hooks/useEnvironment'; +import useHasReusablePoliciesConnectedTo from '@hooks/useHasReusablePoliciesConnectedTo'; -import {getZohoBooksSetupLink} from '@libs/actions/connections/ZohoBooks'; +import Navigation from '@libs/Navigation/Navigation'; -import {openLink} from '@userActions/Link'; +import useStartZohoBooksOAuth from '@pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth'; + +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import {useEffect} from 'react'; -import type {ConnectToZohoBooksFlowProps} from './types'; +type ConnectToZohoBooksFlowProps = { + policyID: string; +}; function ConnectToZohoBooksFlow({policyID}: ConnectToZohoBooksFlowProps) { - const {environmentURL} = useEnvironment(); + const hasReusablePoliciesConnectedToZohoBooks = useHasReusablePoliciesConnectedTo(CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS, policyID); + const startZohoBooksOAuth = useStartZohoBooksOAuth(); useEffect(() => { - // On web the OAuth setup opens in a new browser tab. Open it inline here (within the connect click's - // user-gesture window) instead of navigating to a setup screen, otherwise the popup blocker stops the tab. - openLink(getZohoBooksSetupLink(policyID), environmentURL); + if (hasReusablePoliciesConnectedToZohoBooks) { + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_EXISTING_CONNECTIONS.getRoute(policyID)); + return; + } + startZohoBooksOAuth(policyID); + // This needs to run once as it starts the connection flow // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/ConnectToZohoBooksFlow/types.ts b/src/components/ConnectToZohoBooksFlow/types.ts deleted file mode 100644 index aea62fb07161..000000000000 --- a/src/components/ConnectToZohoBooksFlow/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -type ConnectToZohoBooksFlowProps = { - policyID: string; -}; - -// eslint-disable-next-line import/prefer-default-export -export type {ConnectToZohoBooksFlowProps}; diff --git a/src/languages/de.ts b/src/languages/de.ts index 50b52a7f0065..2cb8c734ee39 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7758,6 +7758,8 @@ Der Control-Tarif beginnt bei 9 $ pro aktivem Mitglied und Monat.`, return 'Campfire-Daten werden synchronisiert'; case 'campfireSyncConnection': return 'Verbindung zu Campfire wird initialisiert'; + case 'zohoBooksSyncTitle': + return 'Zoho Books-Daten werden synchronisiert'; case 'zohoBooksSyncConnection': return 'Verbindung zu Zoho Books wird initialisiert'; case 'campfireSyncImportData': @@ -9336,6 +9338,10 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc }, zohoBooks: { organization: 'Zoho Books-Organisation', + organizationSelectDescription: 'Wählen Sie die Organisation in Zoho Books aus, aus der Sie Daten importieren möchten.', + noOrganizationsFound: 'Keine Organisationen gefunden', + noOrganizationsFoundDescription: 'Bitte fügen Sie eine Organisation in Zoho Books hinzu und synchronisieren Sie die Verbindung erneut', + multiLocationNotSupported: 'Organisationen mit mehreren Standorten werden noch nicht unterstützt', }, businessCentral: { businessCentralSetup: 'Dynamics 365 Business Central-Einrichtung', diff --git a/src/languages/el.ts b/src/languages/el.ts index d971e3d7f6b7..da7584934c21 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -7886,6 +7886,8 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ return 'Γίνεται συγχρονισμός δεδομένων Campfire'; case 'campfireSyncConnection': return 'Γίνεται αρχικοποίηση σύνδεσης με το Campfire'; + case 'zohoBooksSyncTitle': + return 'Γίνεται συγχρονισμός δεδομένων Zoho Books'; case 'zohoBooksSyncConnection': return 'Γίνεται αρχικοποίηση σύνδεσης με το Zoho Books'; case 'campfireSyncImportData': @@ -9499,6 +9501,10 @@ ${reportName}`, }, zohoBooks: { organization: 'Οργανισμός Zoho Books', + organizationSelectDescription: 'Επιλέξτε τον οργανισμό στο Zoho Books από τον οποίο θέλετε να εισαγάγετε δεδομένα.', + noOrganizationsFound: 'Δεν βρέθηκαν οργανισμοί', + noOrganizationsFoundDescription: 'Προσθέστε έναν οργανισμό στο Zoho Books και συγχρονίστε ξανά τη σύνδεση', + multiLocationNotSupported: 'Οι οργανισμοί με πολλές τοποθεσίες δεν υποστηρίζονται ακόμη', }, businessCentral: { businessCentralSetup: 'Ρύθμιση Dynamics 365 Business Central', diff --git a/src/languages/en.ts b/src/languages/en.ts index 68dbcfcf5e91..b2f970055c4b 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6651,6 +6651,10 @@ const translations = { }, zohoBooks: { organization: 'Zoho Books organization', + organizationSelectDescription: "Choose the organization in Zoho Books that you'd like to import data from.", + noOrganizationsFound: 'No organizations found', + noOrganizationsFoundDescription: 'Please add an organization in Zoho Books and sync the connection again', + multiLocationNotSupported: "Multi-location organizations aren't supported yet", }, businessCentral: { businessCentralSetup: 'Dynamics 365 Business Central setup', @@ -8200,6 +8204,8 @@ const translations = { return 'Syncing Campfire data'; case 'campfireSyncConnection': return 'Initializing connection to Campfire'; + case 'zohoBooksSyncTitle': + return 'Syncing Zoho Books data'; case 'zohoBooksSyncConnection': return 'Initializing connection to Zoho Books'; case 'campfireSyncImportData': diff --git a/src/languages/es.ts b/src/languages/es.ts index ac2b51f02a8f..1df8e778e21a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7555,6 +7555,8 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`, return 'Sincronizando datos de Campfire'; case 'campfireSyncConnection': return 'Iniciando conexión con Campfire'; + case 'zohoBooksSyncTitle': + return 'Sincronizando datos de Zoho Books'; case 'zohoBooksSyncConnection': return 'Iniciando conexión con Zoho Books'; case 'campfireSyncImportData': @@ -9156,6 +9158,10 @@ ${reportName}`, }, zohoBooks: { organization: 'Organización Zoho Books', + organizationSelectDescription: 'Elige la organización de Zoho Books desde la que deseas importar datos.', + noOrganizationsFound: 'No se encontraron organizaciones', + noOrganizationsFoundDescription: 'Añade una organización en Zoho Books y vuelve a sincronizar la conexión', + multiLocationNotSupported: 'Las organizaciones con varias ubicaciones aún no son compatibles', }, businessCentral: { businessCentralSetup: 'Configuración de Dynamics 365 Business Central', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index ddf946bf0993..d1689b079dfd 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7779,6 +7779,8 @@ Le forfait Control commence à 9 $ par Membre actif et par mois.`, return 'Synchronisation des données Campfire'; case 'campfireSyncConnection': return 'Initialisation de la connexion à Campfire'; + case 'zohoBooksSyncTitle': + return 'Synchronisation des données Zoho Books'; case 'zohoBooksSyncConnection': return 'Initialisation de la connexion à Zoho Books'; case 'campfireSyncImportData': @@ -9359,6 +9361,10 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e }, zohoBooks: { organization: 'Organisation Zoho Books', + organizationSelectDescription: 'Choisissez l’organisation dans Zoho Books à partir de laquelle vous souhaitez importer des données.', + noOrganizationsFound: 'Aucune organisation trouvée', + noOrganizationsFoundDescription: 'Veuillez ajouter une organisation dans Zoho Books et synchroniser à nouveau la connexion', + multiLocationNotSupported: 'Les organisations multi-sites ne sont pas encore prises en charge', }, businessCentral: { businessCentralSetup: 'Configuration de Dynamics 365 Business Central', diff --git a/src/languages/it.ts b/src/languages/it.ts index 483c54e659da..c413f19fec31 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7718,6 +7718,8 @@ Il piano Control parte da 9 $ al mese per ogni membro attivo.`, return 'Sincronizzazione dei dati Campfire'; case 'campfireSyncConnection': return 'Inizializzazione della connessione a Campfire'; + case 'zohoBooksSyncTitle': + return 'Sincronizzazione dei dati Zoho Books'; case 'zohoBooksSyncConnection': return 'Inizializzazione della connessione a Zoho Books'; case 'campfireSyncImportData': @@ -9285,6 +9287,10 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, }, zohoBooks: { organization: 'Organizzazione Zoho Books', + organizationSelectDescription: 'Scegli l’organizzazione in Zoho Books da cui desideri importare i dati.', + noOrganizationsFound: 'Nessuna organizzazione trovata', + noOrganizationsFoundDescription: 'Aggiungi un’organizzazione in Zoho Books e sincronizza di nuovo la connessione', + multiLocationNotSupported: 'Le organizzazioni con più sedi non sono ancora supportate', }, businessCentral: { businessCentralSetup: 'Configurazione Dynamics 365 Business Central', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 29d279a7f9ce..6752daf70396 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7622,6 +7622,8 @@ Control プランは、アクティブメンバー1人あたり月額 $9 から return 'Campfire データを同期しています'; case 'campfireSyncConnection': return 'Campfire への接続を初期化しています'; + case 'zohoBooksSyncTitle': + return 'Zoho Books データを同期しています'; case 'zohoBooksSyncConnection': return 'Zoho Books への接続を初期化しています'; case 'campfireSyncImportData': @@ -9151,6 +9153,10 @@ ${reportName}`, }, zohoBooks: { organization: 'Zoho Books 組織', + organizationSelectDescription: 'データをインポートする Zoho Books の組織を選択してください。', + noOrganizationsFound: '組織が見つかりません', + noOrganizationsFoundDescription: 'Zoho Books で組織を追加して、接続を再度同期してください', + multiLocationNotSupported: '複数拠点の組織にはまだ対応していません', }, businessCentral: { businessCentralSetup: 'Dynamics 365 Business Central のセットアップ', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 03187d0f7333..e5ac007baabf 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7699,6 +7699,8 @@ Het Control-abonnement begint bij $9 per actieve deelnemer per maand.`, return 'Campfire-gegevens synchroniseren'; case 'campfireSyncConnection': return 'Verbinding met Campfire initialiseren'; + case 'zohoBooksSyncTitle': + return 'Zoho Books-gegevens synchroniseren'; case 'zohoBooksSyncConnection': return 'Verbinding met Zoho Books initialiseren'; case 'campfireSyncImportData': @@ -9258,6 +9260,10 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, }, zohoBooks: { organization: 'Zoho Books-organisatie', + organizationSelectDescription: 'Kies de organisatie in Zoho Books waaruit je gegevens wilt importeren.', + noOrganizationsFound: 'Geen organisaties gevonden', + noOrganizationsFoundDescription: 'Voeg een organisatie toe in Zoho Books en synchroniseer de verbinding opnieuw', + multiLocationNotSupported: 'Organisaties met meerdere locaties worden nog niet ondersteund', }, businessCentral: { businessCentralSetup: 'Dynamics 365 Business Central-configuratie', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 6753f10bbd05..830a7a961643 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7727,6 +7727,8 @@ Plan Control zaczyna się od 9 USD za aktywnego członka miesięcznie.`, return 'Synchronizowanie danych Campfire'; case 'campfireSyncConnection': return 'Inicjowanie połączenia z Campfire'; + case 'zohoBooksSyncTitle': + return 'Synchronizowanie danych Zoho Books'; case 'zohoBooksSyncConnection': return 'Inicjowanie połączenia z Zoho Books'; case 'campfireSyncImportData': @@ -9276,6 +9278,10 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, }, zohoBooks: { organization: 'Organizacja Zoho Books', + organizationSelectDescription: 'Wybierz organizację w Zoho Books, z której chcesz importować dane.', + noOrganizationsFound: 'Nie znaleziono organizacji', + noOrganizationsFoundDescription: 'Dodaj organizację w Zoho Books i ponownie zsynchronizuj połączenie', + multiLocationNotSupported: 'Organizacje z wieloma lokalizacjami nie są jeszcze obsługiwane', }, businessCentral: { businessCentralSetup: 'Konfiguracja Dynamics 365 Business Central', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index b6d0576fbb2c..1a37bdb8ea4f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7700,6 +7700,8 @@ O plano Control começa em US$ 9 por membro ativo por mês.`, return 'Sincronizando dados do Campfire'; case 'campfireSyncConnection': return 'Inicializando conexão com o Campfire'; + case 'zohoBooksSyncTitle': + return 'Sincronizando dados do Zoho Books'; case 'zohoBooksSyncConnection': return 'Inicializando conexão com o Zoho Books'; case 'campfireSyncImportData': @@ -9260,6 +9262,10 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, }, zohoBooks: { organization: 'Organização Zoho Books', + organizationSelectDescription: 'Escolha a organização no Zoho Books da qual você deseja importar dados.', + noOrganizationsFound: 'Nenhuma organização encontrada', + noOrganizationsFoundDescription: 'Adicione uma organização no Zoho Books e sincronize a conexão novamente', + multiLocationNotSupported: 'Organizações com vários locais ainda não são compatíveis', }, businessCentral: { businessCentralSetup: 'Configuração do Dynamics 365 Business Central', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index edd57f3d721a..908d74e2171e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7444,6 +7444,8 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM return '正在同步 Campfire 数据'; case 'campfireSyncConnection': return '正在初始化与 Campfire 的连接'; + case 'zohoBooksSyncTitle': + return '正在同步 Zoho Books 数据'; case 'zohoBooksSyncConnection': return '正在初始化与 Zoho Books 的连接'; case 'campfireSyncImportData': @@ -8914,6 +8916,10 @@ ${reportName}`, }, zohoBooks: { organization: 'Zoho Books 组织', + organizationSelectDescription: '选择您要从中导入数据的 Zoho Books 组织。', + noOrganizationsFound: '未找到组织', + noOrganizationsFoundDescription: '请在 Zoho Books 中添加一个组织,然后重新同步连接', + multiLocationNotSupported: '尚不支持多地点组织', }, businessCentral: { businessCentralSetup: 'Dynamics 365 Business Central 设置', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 80f87114a204..924bfed2445c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -800,6 +800,10 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/xero/XeroSetupPage').default, [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_OAUTH]: () => require('../../../../pages/workspace/accounting/zohoBooks/ZohoBooksOAuthPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_EXISTING_CONNECTIONS]: () => + require('../../../../pages/workspace/accounting/zohoBooks/ZohoBooksExistingConnectionsPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_ORGANIZATION_SELECTOR]: () => + require('../../../../pages/workspace/accounting/zohoBooks/ZohoBooksOrganizationSelector').default, [SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT]: () => require('../../../../pages/workspace/accounting/xero/XeroImportPage').default, [SCREENS.WORKSPACE.ACCOUNTING.CLAIM_OFFER]: () => require('../../../../pages/workspace/accounting/ClaimOfferPage').default, [SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: () => require('../../../../pages/workspace/accounting/xero/XeroOrganizationConfigurationPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index cf229962ec63..8de369fc19a6 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -118,6 +118,8 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_ITEMS]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_DESKTOP_ITEMS.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_SETUP]: {path: ROUTES.POLICY_ACCOUNTING_XERO_SETUP.route}, [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_OAUTH]: {path: ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_OAUTH.route}, + [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_EXISTING_CONNECTIONS]: {path: ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_EXISTING_CONNECTIONS.route}, + [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_ORGANIZATION_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_ORGANIZATION_SELECTOR.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_IMPORT.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_CHART_OF_ACCOUNTS]: {path: ROUTES.POLICY_ACCOUNTING_XERO_CHART_OF_ACCOUNTS.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: {path: ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.route}, diff --git a/src/libs/Navigation/paramLists/WorkspaceSettingsParamList.ts b/src/libs/Navigation/paramLists/WorkspaceSettingsParamList.ts index f123b90c4591..0dba8413414e 100644 --- a/src/libs/Navigation/paramLists/WorkspaceSettingsParamList.ts +++ b/src/libs/Navigation/paramLists/WorkspaceSettingsParamList.ts @@ -672,6 +672,12 @@ type WorkspaceSettingsParamList = { [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_OAUTH]: { policyID: string; }; + [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_EXISTING_CONNECTIONS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.ZOHO_BOOKS_ORGANIZATION_SELECTOR]: { + policyID: string; + }; [SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT]: { policyID: string; }; diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 8afc6927d12c..ec2bb292540e 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -393,6 +393,9 @@ function copyExistingPolicyConnection(connectedPolicyID: string, targetPolicyID: case CONST.POLICY.CONNECTIONS.NAME.CAMPFIRE: stageInProgress = CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.CAMPFIRE_SYNC_CONNECTION; break; + case CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS: + stageInProgress = CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.ZOHO_BOOKS_SYNC_CONNECTION; + break; case CONST.POLICY.CONNECTIONS.NAME.DIGITS: stageInProgress = CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.DIGITS_SYNC_CONNECTION; break; diff --git a/src/pages/workspace/accounting/useConnectedAccountingIntegration.tsx b/src/pages/workspace/accounting/useConnectedAccountingIntegration.tsx index e80dfa8525b3..0e1f8c382fc1 100644 --- a/src/pages/workspace/accounting/useConnectedAccountingIntegration.tsx +++ b/src/pages/workspace/accounting/useConnectedAccountingIntegration.tsx @@ -272,6 +272,7 @@ function useConnectedAccountingIntegration(policy: OnyxEntry, {menuItemW const campfireSubsidiaryList = policy?.connections?.campfire?.data?.subsidiaries; const businessCentralCompanyList = policy?.connections?.businessCentral?.data?.companies; const zohoBooksOrganizationList = policy?.connections?.zohoBooks?.data?.organizations; + const currentZohoBooksOrganization = zohoBooksOrganizationList?.find((organization) => organization.id === policy?.connections?.zohoBooks?.config?.subsidiaryID); const certiniaConfig = policy?.connections?.financialforce?.config; const certiniaCompanies = policy?.connections?.financialforce?.data?.companies ?? []; const certiniaCompanyID = getCertiniaSelectedCompanyID(certiniaConfig); @@ -454,13 +455,24 @@ function useConnectedAccountingIntegration(policy: OnyxEntry, {menuItemW ? {} : { description: translate('workspace.zohoBooks.organization'), - title: zohoBooksOrganizationList.find((organization) => organization.id === policy?.connections?.zohoBooks?.config?.subsidiaryID)?.name ?? '', + iconRight: icons.ArrowRight, + title: currentZohoBooksOrganization?.name ?? '', wrapperStyle: menuItemWrapperStyle, titleStyle: styles.fontWeightNormal, + shouldShowRightIcon: canWriteAccounting && zohoBooksOrganizationList.length > 1, shouldShowDescriptionOnTop: true, - interactive: false, + interactive: canWriteAccounting && zohoBooksOrganizationList.length > 1, + // Multi-location organizations are not supported yet, so explain why the connection doesn't work. + errorText: currentZohoBooksOrganization?.isMultiLocation ? translate('workspace.zohoBooks.multiLocationNotSupported') : undefined, pendingAction: policy?.connections?.zohoBooks?.config?.pendingFields?.subsidiaryID, - brickRoadIndicator: policy?.connections?.zohoBooks?.config?.errorFields?.subsidiaryID ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + brickRoadIndicator: + !!policy?.connections?.zohoBooks?.config?.errorFields?.subsidiaryID || !!currentZohoBooksOrganization?.isMultiLocation + ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR + : undefined, + onPress: + policyID && canWriteAccounting && zohoBooksOrganizationList.length > 1 + ? () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_ORGANIZATION_SELECTOR.getRoute(policyID)) + : undefined, }; default: diff --git a/src/pages/workspace/accounting/zohoBooks/ZohoBooksExistingConnectionsPage.tsx b/src/pages/workspace/accounting/zohoBooks/ZohoBooksExistingConnectionsPage.tsx new file mode 100644 index 000000000000..fd2ac2c19195 --- /dev/null +++ b/src/pages/workspace/accounting/zohoBooks/ZohoBooksExistingConnectionsPage.tsx @@ -0,0 +1,81 @@ +import HeaderWithBackButtonAndTitle from '@components/Header/composed/HeaderWithBackButtonAndTitle'; +import MenuItemNavigation from '@components/MenuItem/presets/MenuItemNavigation'; +import MenuItemList from '@components/MenuItemList'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; + +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useReusablePoliciesConnectedTo from '@hooks/useReusablePoliciesConnectedTo'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import {copyExistingPolicyConnection} from '@libs/actions/connections'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; + +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; + +import React from 'react'; + +import useStartZohoBooksOAuth from './useStartZohoBooksOAuth'; + +type ZohoBooksExistingConnectionsPageProps = PlatformStackScreenProps; + +function ZohoBooksExistingConnectionsPage({route}: ZohoBooksExistingConnectionsPageProps) { + const {translate, datetimeToRelative} = useLocalize(); + const styles = useThemeStyles(); + const icons = useMemoizedLazyExpensifyIcons(['LinkCopy']); + const policyID: string = route.params.policyID; + const {reusablePoliciesConnectedTo: reusablePoliciesConnectedToZohoBooks} = useReusablePoliciesConnectedTo(CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS, policyID); + const startZohoBooksOAuth = useStartZohoBooksOAuth(); + + const menuItems = reusablePoliciesConnectedToZohoBooks.map((policy) => { + const lastSuccessfulSyncDate = policy.connections?.zohoBooks?.lastSync?.successfulDate; + const date = lastSuccessfulSyncDate ? datetimeToRelative(lastSuccessfulSyncDate) : undefined; + return { + title: policy.name, + key: policy.id, + avatarID: policy.id, + icon: policy.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), + iconType: CONST.ICON_TYPE_WORKSPACE, + shouldShowRightIcon: true, + description: date ? translate('workspace.common.lastSyncDate', CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.zohoBooks, date) : translate('workspace.accounting.zohoBooks'), + onPress: () => { + copyExistingPolicyConnection(policy.id, policyID, CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS); + Navigation.dismissModal(); + }, + }; + }); + + return ( + + Navigation.goBack()} + /> + + {translate('workspace.common.existingConnectionsDescription', CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS)} + startZohoBooksOAuth(policyID)} + /> + {translate('workspace.common.existingConnections')} + + + + ); +} + +export default ZohoBooksExistingConnectionsPage; diff --git a/src/pages/workspace/accounting/zohoBooks/ZohoBooksOrganizationSelector.tsx b/src/pages/workspace/accounting/zohoBooks/ZohoBooksOrganizationSelector.tsx new file mode 100644 index 000000000000..f575bc0403be --- /dev/null +++ b/src/pages/workspace/accounting/zohoBooks/ZohoBooksOrganizationSelector.tsx @@ -0,0 +1,104 @@ +import BlockingView from '@components/BlockingViews/BlockingView'; +import type {ListItem} from '@components/SelectionList/types'; +import SelectionScreen from '@components/SelectionScreen'; +import type {SelectorType} from '@components/SelectionScreen'; +import Text from '@components/Text'; + +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useSelectionListSearch from '@hooks/useSelectionListSearch'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import {clearZohoBooksErrorField, updateZohoBooksSubsidiary} from '@libs/actions/connections/ZohoBooks'; +import {getLatestErrorField} from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import {settingsPendingAction} from '@libs/PolicyUtils'; + +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; + +import variables from '@styles/variables'; + +import CONST from '@src/CONST'; +import type {ZohoBooksOrganization} from '@src/types/onyx/Policy'; + +import React from 'react'; +import {View} from 'react-native'; + +type OrganizationListItem = ListItem & { + value: ZohoBooksOrganization['id']; +}; + +function ZohoBooksOrganizationSelector({policy}: WithPolicyConnectionsProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const organizations = policy?.connections?.zohoBooks?.data?.organizations; + const zohoBooksConfig = policy?.connections?.zohoBooks?.config; + const currentOrganizationID = zohoBooksConfig?.subsidiaryID ?? CONST.DEFAULT_NUMBER_ID.toString(); + const policyID = policy?.id ?? CONST.DEFAULT_NUMBER_ID.toString(); + + const illustrations = useMemoizedLazyIllustrations(['Telescope']); + + // Multi-location organizations are not supported yet, so they are listed but can't be selected. + const data: OrganizationListItem[] = organizations + ? organizations.map((organization) => ({ + text: organization.name, + alternateText: organization.isMultiLocation ? translate('workspace.zohoBooks.multiLocationNotSupported') : undefined, + keyForList: organization.id, + isSelected: organization.id === currentOrganizationID, + isDisabled: !!organization.isMultiLocation, + value: organization.id, + })) + : []; + const {filteredData, textInputOptions} = useSelectionListSearch(data); + + const updateOrganization = ({keyForList, value, isDisabled}: SelectorType) => { + if (!keyForList || keyForList === currentOrganizationID || isDisabled) { + return; + } + + updateZohoBooksSubsidiary(policyID, value, currentOrganizationID); + Navigation.goBack(); + }; + + const listEmptyContent = ( + + ); + + const listHeaderComponent = ( + + {translate('workspace.zohoBooks.organizationSelectDescription')} + + ); + + return ( + Navigation.goBack()} + title="workspace.zohoBooks.organization" + listEmptyContent={listEmptyContent} + pendingAction={settingsPendingAction([CONST.ZOHO_BOOKS_CONFIG.SUBSIDIARY_ID], zohoBooksConfig?.pendingFields)} + errors={getLatestErrorField(zohoBooksConfig ?? {}, CONST.ZOHO_BOOKS_CONFIG.SUBSIDIARY_ID)} + errorRowStyles={[styles.ph5, styles.pv3]} + onClose={() => clearZohoBooksErrorField(policyID, CONST.ZOHO_BOOKS_CONFIG.SUBSIDIARY_ID)} + /> + ); +} + +export default withPolicyConnections(ZohoBooksOrganizationSelector); diff --git a/src/pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth/index.native.ts b/src/pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth/index.native.ts new file mode 100644 index 000000000000..300899ceed84 --- /dev/null +++ b/src/pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth/index.native.ts @@ -0,0 +1,10 @@ +import Navigation from '@libs/Navigation/Navigation'; + +import ROUTES from '@src/ROUTES'; + +/** On native the Zoho Books OAuth setup loads inside an in-app WebView screen. */ +function useStartZohoBooksOAuth() { + return (policyID: string) => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_ZOHO_BOOKS_OAUTH.getRoute(policyID)); +} + +export default useStartZohoBooksOAuth; diff --git a/src/pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth/index.ts b/src/pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth/index.ts new file mode 100644 index 000000000000..84e79e32a75d --- /dev/null +++ b/src/pages/workspace/accounting/zohoBooks/useStartZohoBooksOAuth/index.ts @@ -0,0 +1,17 @@ +import useEnvironment from '@hooks/useEnvironment'; + +import {getZohoBooksSetupLink} from '@libs/actions/connections/ZohoBooks'; + +import {openLink} from '@userActions/Link'; + +/** + * On web the Zoho Books OAuth setup opens in a new browser tab. Call the returned function from the connect click (within + * its user-gesture window) instead of navigating to a setup screen first, otherwise the popup blocker stops the tab. + */ +function useStartZohoBooksOAuth() { + const {environmentURL} = useEnvironment(); + + return (policyID: string) => openLink(getZohoBooksSetupLink(policyID), environmentURL); +} + +export default useStartZohoBooksOAuth; diff --git a/src/selectors/Policy.ts b/src/selectors/Policy.ts index 5469c1a96a57..3b6f5017850f 100644 --- a/src/selectors/Policy.ts +++ b/src/selectors/Policy.ts @@ -45,6 +45,7 @@ type ReusablePolicyConnectionName = | typeof CONST.POLICY.CONNECTIONS.NAME.RILLET | typeof CONST.POLICY.CONNECTIONS.NAME.DUALENTRY | typeof CONST.POLICY.CONNECTIONS.NAME.CAMPFIRE + | typeof CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS | typeof CONST.POLICY.CONNECTIONS.NAME.DIGITS; const ownerPoliciesSelector = (policies: OnyxCollection, currentUserAccountID: number) => getOwnedPaidPolicies(policies, currentUserAccountID); @@ -478,6 +479,9 @@ const adminPoliciesConnectedToDualEntrySelector = (policies: OnyxCollection) => Object.values(policies ?? {}).filter((policy): policy is Policy => isAdminPolicyConnectedTo(policy, CONST.POLICY.CONNECTIONS.NAME.CAMPFIRE)); +const adminPoliciesConnectedToZohoBooksSelector = (policies: OnyxCollection) => + Object.values(policies ?? {}).filter((policy): policy is Policy => isAdminPolicyConnectedTo(policy, CONST.POLICY.CONNECTIONS.NAME.ZOHO_BOOKS)); + const adminPoliciesConnectedToDigitsSelector = (policies: OnyxCollection) => Object.values(policies ?? {}).filter((policy): policy is Policy => isAdminPolicyConnectedTo(policy, CONST.POLICY.CONNECTIONS.NAME.DIGITS)); @@ -489,6 +493,7 @@ const reusableConnectionAdminSelectors: Record