@zeniai/client-epic-state 5.0.80 → 5.0.81-beta0ND

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/lib/commonStateTypes/cacheOverrideFetchReducer.d.ts +17 -0
  2. package/lib/commonStateTypes/cacheOverrideFetchReducer.js +20 -0
  3. package/lib/commonStateTypes/filterPrimitives.d.ts +23 -0
  4. package/lib/commonStateTypes/filterPrimitives.js +21 -0
  5. package/lib/commonStateTypes/pusherActions.d.ts +11 -0
  6. package/lib/commonStateTypes/pusherActions.js +6 -0
  7. package/lib/coreEpics.js +1 -1
  8. package/lib/entity/account/accountSelector.d.ts +27 -1
  9. package/lib/entity/account/accountSelector.js +47 -1
  10. package/lib/entity/account/accountState.d.ts +6 -1
  11. package/lib/entity/account/accountState.js +11 -4
  12. package/lib/entity/account/subAccountSelector.d.ts +1 -1
  13. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +1 -3
  14. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +1 -2
  15. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.d.ts +1 -1
  16. package/lib/entity/chargeCard/chargeCardReducer.d.ts +19 -2
  17. package/lib/entity/chargeCard/chargeCardReducer.js +35 -2
  18. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.d.ts +14 -1
  19. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.js +36 -2
  20. package/lib/entity/class/classSelector.d.ts +35 -0
  21. package/lib/entity/class/classSelector.js +50 -0
  22. package/lib/entity/paymentAccount/paymentAccountSelector.d.ts +1 -0
  23. package/lib/entity/paymentAccount/paymentAccountSelector.js +4 -0
  24. package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +1 -1
  25. package/lib/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  26. package/lib/entity/tenant/SessionManager.js +6 -3
  27. package/lib/entity/tenant/clearAllEpic.d.ts +2 -2
  28. package/lib/entity/tenant/clearAllEpic.js +2 -2
  29. package/lib/entity/tenant/epic/deleteConnectionEpic.d.ts +1 -1
  30. package/lib/entity/tenant/epic/initEmailConnectOAuthEpic.d.ts +21 -0
  31. package/lib/entity/tenant/epic/initEmailConnectOAuthEpic.js +54 -0
  32. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.d.ts +1 -1
  33. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.js +6 -2
  34. package/lib/entity/tenant/epic/saveConnectorCredentialsEpic.d.ts +3 -3
  35. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.d.ts +1 -1
  36. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.js +6 -2
  37. package/lib/entity/tenant/tenantPayload.d.ts +1 -0
  38. package/lib/entity/tenant/tenantReducer.d.ts +38 -25
  39. package/lib/entity/tenant/tenantReducer.js +71 -6
  40. package/lib/entity/tenant/tenantState.d.ts +1 -0
  41. package/lib/entity/transaction/payloadTypes/transactionLinePayload.js +3 -1
  42. package/lib/epic.d.ts +13 -11
  43. package/lib/epic.js +15 -12
  44. package/lib/esm/commonStateTypes/cacheOverrideFetchReducer.js +16 -0
  45. package/lib/esm/commonStateTypes/filterPrimitives.js +20 -0
  46. package/lib/esm/commonStateTypes/pusherActions.js +3 -0
  47. package/lib/esm/coreEpics.js +1 -1
  48. package/lib/esm/entity/account/accountSelector.js +46 -1
  49. package/lib/esm/entity/account/accountState.js +7 -1
  50. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +1 -3
  51. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +1 -2
  52. package/lib/esm/entity/chargeCard/chargeCardReducer.js +34 -1
  53. package/lib/esm/entity/chargeCardTransaction/chargeCardTransactionReducer.js +35 -1
  54. package/lib/esm/entity/class/classSelector.js +48 -0
  55. package/lib/esm/entity/paymentAccount/paymentAccountSelector.js +3 -0
  56. package/lib/esm/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  57. package/lib/esm/entity/tenant/SessionManager.js +4 -1
  58. package/lib/esm/entity/tenant/clearAllEpic.js +2 -2
  59. package/lib/esm/entity/tenant/epic/initEmailConnectOAuthEpic.js +50 -0
  60. package/lib/esm/entity/tenant/epic/saveAPIKeyConnectionEpic.js +6 -2
  61. package/lib/esm/entity/tenant/epic/saveOAuthConnectionEpic.js +6 -2
  62. package/lib/esm/entity/tenant/tenantReducer.js +69 -4
  63. package/lib/esm/entity/transaction/payloadTypes/transactionLinePayload.js +3 -1
  64. package/lib/esm/epic.js +15 -12
  65. package/lib/esm/index.js +38 -25
  66. package/lib/esm/reducer.js +2 -2
  67. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +2 -5
  68. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +2 -5
  69. package/lib/esm/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +4 -1
  70. package/lib/esm/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
  71. package/lib/esm/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  72. package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -1
  73. package/lib/esm/view/companyView/helpers/cockpitHelpers.js +1 -1
  74. package/lib/esm/view/companyView/selector/companyManagementViewSelector.js +1 -1
  75. package/lib/esm/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
  76. package/lib/esm/view/creditAgentView/creditAgentViewReducer.js +1 -16
  77. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  78. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +2 -1
  79. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +8 -6
  80. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +7 -1
  81. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +11 -1
  82. package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +21 -1
  83. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -1
  84. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +253 -14
  85. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +147 -13
  86. package/lib/esm/view/expenseAutomationView/transactionFilterHelpers.js +258 -0
  87. package/lib/esm/view/expenseAutomationView/types/completedSubTab.js +17 -0
  88. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +0 -2
  89. package/lib/esm/view/financeStatement/financeStatementSelector.js +1 -1
  90. package/lib/esm/view/onboardingView/customerView/onboardingCustomerViewReducer.js +61 -50
  91. package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +87 -0
  92. package/lib/esm/view/recommendation/recommendationHelper.js +29 -0
  93. package/lib/esm/view/recommendation/recommendationReducer.js +26 -1
  94. package/lib/esm/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +3 -2
  95. package/lib/esm/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +3 -2
  96. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +55 -18
  97. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +16 -2
  98. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +5 -4
  99. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +26 -9
  100. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +9 -6
  101. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +3 -2
  102. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  103. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +7 -3
  104. package/lib/esm/view/spendManagement/commonSetup/epic/setup/parseUploadedKybDocumentEpic.js +49 -0
  105. package/lib/esm/view/spendManagement/commonSetup/epic/setup/parseUploadedKycDocumentEpic.js +58 -0
  106. package/lib/esm/view/spendManagement/commonSetup/kycKybAutofillActions.js +12 -0
  107. package/lib/esm/view/spendManagement/commonSetup/kycKybParseMapper.js +205 -0
  108. package/lib/esm/view/spendManagement/commonSetup/setupViewReducer.js +71 -52
  109. package/lib/esm/view/spendManagement/commonSetup/setupViewSelector.js +2 -0
  110. package/lib/esm/view/spendManagement/commonSetup/types/kycKybAutofill.js +28 -0
  111. package/lib/esm/view/spendManagement/spendManagementFilterHelpers.js +4 -2
  112. package/lib/esm/view/subscriptionView/subscriptionViewSelector.js +1 -1
  113. package/lib/esm/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +1 -1
  114. package/lib/esm/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +1 -1
  115. package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +1 -1
  116. package/lib/esm/view/taskManager/taskListView/taskListReducer.js +1 -1
  117. package/lib/esm/view/transactionDetail/transactionDetailReducer.js +121 -6
  118. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +29 -1
  119. package/lib/index.d.ts +39 -28
  120. package/lib/index.js +97 -60
  121. package/lib/reducer.d.ts +2 -2
  122. package/lib/reducer.js +2 -2
  123. package/lib/view/aiAccountantView/aiAccountantViewPayload.d.ts +2 -2
  124. package/lib/view/aiAccountantView/aiAccountantViewReducer.d.ts +1 -1
  125. package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +2 -5
  126. package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +2 -5
  127. package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +4 -1
  128. package/lib/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
  129. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.d.ts +1 -1
  130. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  131. package/lib/view/companyView/helpers/cockpitHelpers.js +1 -1
  132. package/lib/view/companyView/selector/companyManagementViewSelector.js +1 -1
  133. package/lib/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
  134. package/lib/view/companyView/types/cockpitTypes.d.ts +1 -1
  135. package/lib/view/creditAgentView/creditAgentViewPayload.d.ts +0 -3
  136. package/lib/view/creditAgentView/creditAgentViewReducer.d.ts +1 -1
  137. package/lib/view/creditAgentView/creditAgentViewReducer.js +2 -17
  138. package/lib/view/creditAgentView/creditAgentViewState.d.ts +0 -5
  139. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  140. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +2 -1
  141. package/lib/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +8 -6
  142. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +7 -1
  143. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.d.ts +2 -2
  144. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +10 -0
  145. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +2 -1
  146. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +23 -2
  147. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +21 -0
  148. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +2 -1
  149. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -1
  150. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +28 -5
  151. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +253 -14
  152. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +2 -1
  153. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +16 -0
  154. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +7 -1
  155. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +150 -13
  156. package/lib/view/expenseAutomationView/transactionFilterHelpers.d.ts +66 -0
  157. package/lib/view/expenseAutomationView/transactionFilterHelpers.js +262 -0
  158. package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +10 -0
  159. package/lib/view/expenseAutomationView/types/completedSubTab.js +21 -0
  160. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +1 -2
  161. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +1 -4
  162. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +44 -2
  163. package/lib/view/financeStatement/financeStatementSelector.js +1 -1
  164. package/lib/view/onboardingView/customerView/onboardingCustomerViewReducer.d.ts +25 -2
  165. package/lib/view/onboardingView/customerView/onboardingCustomerViewReducer.js +62 -51
  166. package/lib/view/onboardingView/customerView/onboardingCustomerViewState.d.ts +10 -0
  167. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewSelectorTypes.d.ts +1 -1
  168. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +9 -0
  169. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +91 -0
  170. package/lib/view/recommendation/recommendationHelper.d.ts +9 -0
  171. package/lib/view/recommendation/recommendationHelper.js +31 -1
  172. package/lib/view/recommendation/recommendationReducer.d.ts +20 -1
  173. package/lib/view/recommendation/recommendationReducer.js +27 -2
  174. package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
  175. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.d.ts +3 -1
  176. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +3 -2
  177. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.d.ts +3 -1
  178. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +3 -2
  179. package/lib/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +55 -18
  180. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +16 -2
  181. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +5 -4
  182. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.d.ts +18 -3
  183. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +27 -10
  184. package/lib/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +9 -6
  185. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.d.ts +1 -1
  186. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.d.ts +3 -1
  187. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +3 -2
  188. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  189. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.d.ts +3 -1
  190. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +7 -3
  191. package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKybDocumentEpic.d.ts +8 -0
  192. package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKybDocumentEpic.js +53 -0
  193. package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKycDocumentEpic.d.ts +8 -0
  194. package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKycDocumentEpic.js +62 -0
  195. package/lib/view/spendManagement/commonSetup/kycKybAutofillActions.d.ts +27 -0
  196. package/lib/view/spendManagement/commonSetup/kycKybAutofillActions.js +15 -0
  197. package/lib/view/spendManagement/commonSetup/kycKybParseMapper.d.ts +24 -0
  198. package/lib/view/spendManagement/commonSetup/kycKybParseMapper.js +213 -0
  199. package/lib/view/spendManagement/commonSetup/setupViewReducer.d.ts +24 -2
  200. package/lib/view/spendManagement/commonSetup/setupViewReducer.js +72 -53
  201. package/lib/view/spendManagement/commonSetup/setupViewSelector.d.ts +4 -0
  202. package/lib/view/spendManagement/commonSetup/setupViewSelector.js +2 -0
  203. package/lib/view/spendManagement/commonSetup/setupViewState.d.ts +12 -0
  204. package/lib/view/spendManagement/commonSetup/types/kycKybAutofill.d.ts +125 -0
  205. package/lib/view/spendManagement/commonSetup/types/kycKybAutofill.js +32 -0
  206. package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +1 -1
  207. package/lib/view/spendManagement/spendManagementFilterHelpers.d.ts +12 -3
  208. package/lib/view/spendManagement/spendManagementFilterHelpers.js +4 -2
  209. package/lib/view/subscriptionView/subscriptionViewSelector.js +1 -1
  210. package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
  211. package/lib/view/taskManager/taskListView/taskListReducer.js +1 -1
  212. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +11 -1
  213. package/lib/view/transactionDetail/transactionDetailReducer.js +122 -7
  214. package/lib/view/transactionDetail/transactionDetailSelector.d.ts +2 -0
  215. package/lib/view/transactionDetail/transactionDetailSelector.js +31 -2
  216. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +1 -0
  217. package/lib/view/zeniOAuthView/zeniOAuthSelector.d.ts +1 -1
  218. package/package.json +10 -4
  219. package/lib/esm/view/creditAgentView/epics/fetchCreditAgentAccessEpic.js +0 -13
  220. package/lib/tsconfig.typecheck.tsbuildinfo +0 -1
  221. package/lib/view/creditAgentView/epics/fetchCreditAgentAccessEpic.d.ts +0 -11
  222. package/lib/view/creditAgentView/epics/fetchCreditAgentAccessEpic.js +0 -17
@@ -1,7 +1,8 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
1
2
  import flatMap from 'lodash/flatMap';
2
3
  import { getCOABalances } from '../../commonStateTypes/coaBalance/coaBalance';
3
4
  import { sortComparator } from '../../commonStateTypes/coaBalance/sortBalancesByFreeRangeTotal';
4
- import { getAccountKey, } from './accountState';
5
+ import { getAccountKey, isPaymentAccountType, } from './accountState';
5
6
  function getCommonAccountFields(accountState, id) {
6
7
  const key = getAccountKey(id.reportId, id.accountId, id.classesViewParentId, id.accountsViewParentId, id.projectsViewParentId);
7
8
  const account = accountState.accountsByKey[key];
@@ -179,3 +180,47 @@ export const getAccountIdsForLabels = (accountState, labels) => {
179
180
  });
180
181
  return allAccountIdsForLabels;
181
182
  };
183
+ /**
184
+ * Returns all accounts currently in state (single pass over accountsByKey).
185
+ * Use for dropdowns that need account names and types; map to
186
+ * { accountId, accountName, accountType } as needed.
187
+ */
188
+ export const getAllAccounts = (accountState) => Object.values(accountState.accountsByKey);
189
+ /**
190
+ * Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
191
+ * all other account types → categoryOptions. Use for payment_account_name and category filter options.
192
+ *
193
+ * Memoised via `createSelector` so the composite `{categoryOptions,
194
+ * paymentAccountNames}` keeps a stable identity across Redux dispatches that
195
+ * don't change account data. Without this, every consumer using
196
+ * `useSelector(state => getTransactionFilterAccountOptions(state.accountState))`
197
+ * would re-render on every dispatch (the inline call returns a fresh outer
198
+ * object each time, defeating reference equality).
199
+ */
200
+ export const getTransactionFilterAccountOptions = createSelector(
201
+ // Key the memo on the underlying `accountsByKey` record rather than on
202
+ // `getAllAccounts` output: the latter returns a fresh `Object.values(...)`
203
+ // array on every call, which would defeat reselect's reference-equality
204
+ // check and re-run the projection on every dispatch.
205
+ [(accountState) => accountState.accountsByKey], (accountsByKey) => {
206
+ const accounts = Object.values(accountsByKey);
207
+ const paymentAccountNames = [];
208
+ const categoryOptions = [];
209
+ for (const account of accounts) {
210
+ if (account.accountType == null) {
211
+ continue;
212
+ }
213
+ const option = {
214
+ accountId: account.accountId,
215
+ accountName: account.accountName,
216
+ accountType: account.accountType,
217
+ };
218
+ if (isPaymentAccountType(account.accountType)) {
219
+ paymentAccountNames.push(option);
220
+ }
221
+ else {
222
+ categoryOptions.push(option);
223
+ }
224
+ }
225
+ return { categoryOptions, paymentAccountNames };
226
+ });
@@ -1,7 +1,7 @@
1
1
  import { getNestedAccountIDStr, } from '../../commonStateTypes/accountView/nestedAccountID';
2
2
  import { getNestedClassIDStr, } from '../../commonStateTypes/classesView/nestedClassID';
3
3
  import { stringToUnion, stringToUnionStrict, } from '../../commonStateTypes/stringToUnion';
4
- const ALL_ACCOUNT_TYPES = [
4
+ export const ALL_ACCOUNT_TYPES = [
5
5
  'bank',
6
6
  'credit_card',
7
7
  'expenses',
@@ -25,6 +25,12 @@ const ALL_ACCOUNT_TYPES = [
25
25
  ];
26
26
  export const toAccountType = (v) => stringToUnion(v, ALL_ACCOUNT_TYPES);
27
27
  export const toAccountTypeStrict = (v) => stringToUnionStrict(v ?? '', ALL_ACCOUNT_TYPES);
28
+ /** Account types used for payment-account filters (e.g. transaction filter "payment account name"). */
29
+ export const PAYMENT_ACCOUNT_TYPES = ['bank', 'credit_card'];
30
+ export function isPaymentAccountType(accountType) {
31
+ return (accountType != null &&
32
+ PAYMENT_ACCOUNT_TYPES.includes(accountType));
33
+ }
28
34
  const ALL_ACCOUNT_LABELS = [
29
35
  'uncategorized_income',
30
36
  'uncategorized_expense',
@@ -19,9 +19,7 @@ const toAccuracyByModel = (accuracyPayload) => {
19
19
  export const toAiAccountantEnrollment = (payload) => ({
20
20
  status: toAiAccountantEnrollmentStatus(payload.status),
21
21
  accuracy: toAccuracyByModel(payload.accuracy),
22
- latestTraining: payload.latest_training != null
23
- ? date(payload.latest_training)
24
- : undefined,
22
+ latestTraining: payload.latest_training != null ? date(payload.latest_training) : undefined,
25
23
  latestTransactionSync: payload.latest_transaction_sync != null
26
24
  ? date(payload.latest_transaction_sync)
27
25
  : undefined,
@@ -27,8 +27,7 @@ const aiAccountantCustomer = createSlice({
27
27
  const existing = draft.jobsByTenantId[action.payload.tenantId] ?? [];
28
28
  const jobMap = new Map(existing.map((job) => [job.jobId, job]));
29
29
  mappedJobs.forEach((job) => jobMap.set(job.jobId, job));
30
- draft.jobsByTenantId[action.payload.tenantId] =
31
- Array.from(jobMap.values());
30
+ draft.jobsByTenantId[action.payload.tenantId] = Array.from(jobMap.values());
32
31
  }
33
32
  else {
34
33
  draft.jobsByTenantId[action.payload.tenantId] = mappedJobs;
@@ -1,5 +1,7 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import assignWith from 'lodash/assignWith';
3
+ import { toAmount } from '../../commonStateTypes/amount';
4
+ import { toCardStatusCodeType } from './chargeCard';
3
5
  import { mapChargeCardPayloadToChargeCard, } from './chargeCardPayload';
4
6
  export const initialState = {
5
7
  chargeCardByID: {},
@@ -35,6 +37,37 @@ const chargeCard = createSlice({
35
37
  const latestChargeCard = mapChargeCardPayloadToChargeCard(action.payload);
36
38
  draft.chargeCardByID[latestChargeCard.id] = latestChargeCard;
37
39
  },
40
+ updateChargeCardStatusFromPusher(draft, action) {
41
+ const { cardId, cardStatus, isLockedByAdmin } = action.payload;
42
+ const card = draft.chargeCardByID[cardId];
43
+ if (card != null) {
44
+ card.status = {
45
+ code: toCardStatusCodeType(cardStatus.code),
46
+ name: cardStatus.name,
47
+ };
48
+ if (isLockedByAdmin !== undefined) {
49
+ card.isLockedByAdmin = isLockedByAdmin;
50
+ }
51
+ }
52
+ },
53
+ updateChargeCardSpendingFromPusher(draft, action) {
54
+ const { cardId, currencyCode, currencySymbol } = action.payload;
55
+ const card = draft.chargeCardByID[cardId];
56
+ if (card == null) {
57
+ return;
58
+ }
59
+ const amt = (v) => v !== undefined ? toAmount(v, currencyCode, currencySymbol) : undefined;
60
+ card.balance = amt(action.payload.balance) ?? card.balance;
61
+ card.hold = amt(action.payload.hold) ?? card.hold;
62
+ card.dailyPurchaseSpends =
63
+ amt(action.payload.dailyPurchaseSpends) ?? card.dailyPurchaseSpends;
64
+ card.monthlyPurchaseSpends =
65
+ amt(action.payload.monthlyPurchaseSpends) ?? card.monthlyPurchaseSpends;
66
+ card.dailyWithdrawals =
67
+ amt(action.payload.dailyWithdrawals) ?? card.dailyWithdrawals;
68
+ card.monthlyWithdrawals =
69
+ amt(action.payload.monthlyWithdrawals) ?? card.monthlyWithdrawals;
70
+ },
38
71
  removeChargeCard(draft, action) {
39
72
  delete draft.chargeCardByID[action.payload];
40
73
  },
@@ -43,5 +76,5 @@ const chargeCard = createSlice({
43
76
  },
44
77
  },
45
78
  });
46
- export const { updateChargeCards, updateChargeCard, removeChargeCard, clearAllChargeCards, } = chargeCard.actions;
79
+ export const { updateChargeCards, updateChargeCard, updateChargeCardStatusFromPusher, updateChargeCardSpendingFromPusher, removeChargeCard, clearAllChargeCards, } = chargeCard.actions;
47
80
  export default chargeCard.reducer;
@@ -1,4 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
+ import { addChargeCardTransactionFromPusher, addDeclinedChargeCardTransactionFromPusher, } from '../../commonStateTypes/pusherActions';
3
+ import { toCardTransactionStatusCode, } from './chargeCardTransaction';
2
4
  import { toChargeCardTransaction, } from './chargeCardTransactionPayload';
3
5
  export const initialState = {
4
6
  chargeCardTransactionById: {},
@@ -46,10 +48,42 @@ const chargeCardTransaction = createSlice({
46
48
  }
47
49
  }
48
50
  },
51
+ updateChargeCardTransactionStatusFromPusher(draft, action) {
52
+ const { transactionId, transactionStatus } = action.payload;
53
+ const transaction = draft.chargeCardTransactionById[transactionId];
54
+ if (transaction != null) {
55
+ transaction.transactionStatus = {
56
+ code: toCardTransactionStatusCode(transactionStatus.code),
57
+ name: transactionStatus.name,
58
+ };
59
+ }
60
+ },
61
+ updateChargeCardTransactionFromPusher(draft, action) {
62
+ const entity = toChargeCardTransaction(action.payload.transaction);
63
+ draft.chargeCardTransactionById[entity.id] = entity;
64
+ },
65
+ updateChargeCardTransactionReceiptFromPusher(draft, action) {
66
+ const { transactionId, attachmentFilePaths } = action.payload;
67
+ const transaction = draft.chargeCardTransactionById[transactionId];
68
+ if (transaction != null) {
69
+ transaction.attachmentFilePaths = attachmentFilePaths;
70
+ }
71
+ },
49
72
  clearAllChargeCardTransactions(draft) {
50
73
  draft.chargeCardTransactionById = {};
51
74
  },
52
75
  },
76
+ extraReducers: (builder) => {
77
+ builder
78
+ .addCase(addChargeCardTransactionFromPusher, (draft, action) => {
79
+ const entity = toChargeCardTransaction(action.payload.transaction);
80
+ draft.chargeCardTransactionById[entity.id] = entity;
81
+ })
82
+ .addCase(addDeclinedChargeCardTransactionFromPusher, (draft, action) => {
83
+ const entity = toChargeCardTransaction(action.payload.transaction);
84
+ draft.chargeCardTransactionById[entity.id] = entity;
85
+ });
86
+ },
53
87
  });
54
- export const { updateChargeCardTransactions, updateChargeCardTransaction, updateChargeCardTransactionAttachments, removeChargeCardTransaction, removeChargeCardTransactionAttachmentByThirdPartyTransactionId, clearAllChargeCardTransactions, } = chargeCardTransaction.actions;
88
+ export const { updateChargeCardTransactions, updateChargeCardTransaction, updateChargeCardTransactionAttachments, updateChargeCardTransactionStatusFromPusher, updateChargeCardTransactionFromPusher, updateChargeCardTransactionReceiptFromPusher, removeChargeCardTransaction, removeChargeCardTransactionAttachmentByThirdPartyTransactionId, clearAllChargeCardTransactions, } = chargeCardTransaction.actions;
55
89
  export default chargeCardTransaction.reducer;
@@ -1,3 +1,4 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
1
2
  import recordGet from 'lodash/get';
2
3
  import { getCOABalances } from '../../commonStateTypes/coaBalance/coaBalance';
3
4
  import { getAccount } from '../account/accountSelector';
@@ -50,3 +51,50 @@ export function getClassReport(classState, accountState, id, filter) {
50
51
  accounts: accountReport,
51
52
  };
52
53
  }
54
+ /**
55
+ * Returns all classes currently in state (single pass over classesByKey).
56
+ * Use for dropdowns that need class names; map to { classId, className } as needed.
57
+ */
58
+ export function getAllClasses(classState) {
59
+ return Object.values(classState.classesByKey);
60
+ }
61
+ /**
62
+ * Memoised projection of `getAllClasses` to the `{classId, className}` shape
63
+ * expected by filter dropdowns. Consumers should prefer this over inlining a
64
+ * `getAllClasses(...).map(...)` inside `useSelector`: the inline map allocates
65
+ * a fresh array on every Redux dispatch, breaking `useSelector`'s
66
+ * reference-equality optimisation and triggering re-renders on every state
67
+ * change unrelated to class data.
68
+ *
69
+ * Two cleanups happen here, intentionally inside the selector so every
70
+ * consumer benefits uniformly:
71
+ * 1. **Drop entries with a blank `className`.** Soft-deleted classes can
72
+ * retain their classId in state while the human name is blanked, which
73
+ * would otherwise render as an empty row in the filter dropdown.
74
+ * 2. **Dedupe by `classId`.** `classesByKey` is keyed by
75
+ * `${classId}-${reportId}` (see `getClassKey`), so a class loaded under
76
+ * multiple report contexts appears as multiple entries from
77
+ * `Object.values(classesByKey)`. The first non-blank entry per
78
+ * `classId` wins.
79
+ */
80
+ export const getClassFilterOptions = createSelector(
81
+ // Key the memo on the underlying `classesByKey` record rather than on
82
+ // `getAllClasses` output: the latter returns a fresh `Object.values(...)`
83
+ // array on every call, which would defeat reselect's reference-equality
84
+ // check and re-run the projection on every dispatch.
85
+ [(classState) => classState.classesByKey], (classesByKey) => {
86
+ const classes = Object.values(classesByKey);
87
+ const seenClassIds = new Set();
88
+ const options = [];
89
+ for (const c of classes) {
90
+ if (c.classId == null || seenClassIds.has(c.classId)) {
91
+ continue;
92
+ }
93
+ if (c.className == null || c.className.trim() === '') {
94
+ continue;
95
+ }
96
+ seenClassIds.add(c.classId);
97
+ options.push({ classId: c.classId, className: c.className });
98
+ }
99
+ return options;
100
+ });
@@ -19,3 +19,6 @@ export function getPaymentAccountsByBankAccountIds(paymentAccountState, bankAcco
19
19
  .filter((value) => value != null);
20
20
  return paymentAccounts;
21
21
  }
22
+ export function getPlaidPaymentAccounts(paymentAccountState) {
23
+ return values(paymentAccountState.paymentAccountByID).filter((account) => account.provider === 'plaid');
24
+ }
@@ -1,6 +1,6 @@
1
1
  import recordGet from 'lodash/get';
2
- import { getCOABalances, } from '../../commonStateTypes/coaBalance/coaBalance';
3
2
  import { getSumOfBalancesByGroupedByPeriod } from '../../commonStateTypes/coaBalance/classBalances/getSumOfBalancesGroupedByPeriod';
3
+ import { getCOABalances, } from '../../commonStateTypes/coaBalance/coaBalance';
4
4
  import { getNestedAccountReportForAccount, } from '../account/subAccountSelector';
5
5
  import { getProjectById } from '../project/projectSelector';
6
6
  import { getSectionProjectViewKey } from './sectionProjectView';
@@ -260,7 +260,10 @@ export class SessionManager {
260
260
  this.callbacks?.onHeartbeat();
261
261
  // Let other tabs know we just fired a heartbeat — they can skip theirs
262
262
  // this cycle to avoid N tabs all firing heartbeats.
263
- this.broadcast({ type: 'heartbeat-fired', timestamp: this.lastHeartbeatTime });
263
+ this.broadcast({
264
+ type: 'heartbeat-fired',
265
+ timestamp: this.lastHeartbeatTime,
266
+ });
264
267
  }
265
268
  // ─── BroadcastChannel (cross-tab activity sync) ───────────
266
269
  setupBroadcastChannel() {
@@ -3,13 +3,13 @@ import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { clearAllAiAccountantCustomers } from '../../entity/aiAccountantCustomer/aiAccountantCustomerReducer';
4
4
  import { clearAiCfo } from '../../entity/aiCfo/aiCfoReducer';
5
5
  import { clearAllApprovalRules } from '../../entity/approvalRule/approvalRuleReducer';
6
+ import { clearAllCreditAgent } from '../../entity/creditAgent/creditAgentReducer';
6
7
  import { clearAllEntityApprovalStatus } from '../../entity/entityApprovalStatus/entityApprovalStatusReducer';
7
8
  import { clearAllMonthEndCloseChecks } from '../../entity/monthEndCloseChecks/monthEndCloseChecksReducer';
8
9
  import { clearAllNotifications } from '../../entity/notification/notificationReducer';
9
10
  import { clearAllSectionsClassesViewV2 } from '../../entity/sectionClassesViewV2/sectionClassesViewReducer';
10
11
  import { clearAllSectionsProjectView } from '../../entity/sectionProjectView/sectionProjectViewReducer';
11
12
  import { clearAllTaskGroups } from '../../entity/taskGroup/taskGroupReducer';
12
- import { clearCannedResponses } from '../../view/taskManager/cannedResponsesView/cannedResponsesReducer';
13
13
  import { clearAllToastNotifications } from '../../entity/toastNotification/toastNotificationReducer';
14
14
  import { clearAccountList } from '../../view/accountList/accountListReducer';
15
15
  import { clearAccountMappingView } from '../../view/accountMappingView/accountMappingReducer';
@@ -40,7 +40,6 @@ import { clearCompanyHealthMetricView } from '../../view/companyHealthMetricView
40
40
  import { clearCompanyMonthEndReportView } from '../../view/companyMonthEndReportView/companyMonthEndReportViewReducer';
41
41
  import { clearCompanyTaskManagerView } from '../../view/companyTaskManagerView/companyTaskManagerViewReducer';
42
42
  import { clearCompanyView } from '../../view/companyView/companyViewReducer';
43
- import { clearAllCreditAgent } from '../../entity/creditAgent/creditAgentReducer';
44
43
  import { clearCreditAgentView } from '../../view/creditAgentView/creditAgentViewReducer';
45
44
  import { clearDashboard } from '../../view/dashboard/dashboardReducer';
46
45
  import { clearDashboardLayout } from '../../view/dashboardLayout/dashboardLayoutReducer';
@@ -135,6 +134,7 @@ import { clearZeniAccountSetupView } from '../../view/spendManagement/zeniAccoun
135
134
  import { clearZeniAccountsConfig } from '../../view/spendManagement/zeniAccounts/zeniAccountsConfig/zeniAccountsConfigReducer';
136
135
  import { clearSubscriptionView } from '../../view/subscriptionView/subscriptionViewReducer';
137
136
  import { clearTagView } from '../../view/tagView/tagViewReducer';
137
+ import { clearCannedResponses } from '../../view/taskManager/cannedResponsesView/cannedResponsesReducer';
138
138
  import { clearTaskDetail } from '../../view/taskManager/taskDetailView/taskDetailReducer';
139
139
  import { clearTaskGroupTemplatesView } from '../../view/taskManager/taskGroupTemplateView/taskGroupTemplateViewReducer';
140
140
  import { clearTaskGroupView } from '../../view/taskManager/taskGroupView/taskGroupViewReducer';
@@ -0,0 +1,50 @@
1
+ import { from } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
4
+ import { initEmailConnectOAuth, initEmailConnectOAuthFailure, initEmailConnectOAuthSuccess, } from '../tenantReducer';
5
+ const defaultNavigate = (url) => {
6
+ // Production path: full-page redirect. The user's session continues on the
7
+ // provider's consent screen and lands back on this app via auth's HTML
8
+ // callback page (which itself redirects to redirect_after_url with the
9
+ // result encoded as a query param).
10
+ if (typeof window !== 'undefined') {
11
+ window.location.href = url;
12
+ }
13
+ };
14
+ // Backs the new POST /1.0/connectors/email/<provider>/init endpoint on the
15
+ // auth service. The endpoint creates a CSRF state token in the auth NDB
16
+ // store and returns the provider's authorization_url; this epic then drives
17
+ // a full-page redirect so the user lands on Google's / Microsoft's consent
18
+ // screen. After consent the user lands on auth's callback, which writes the
19
+ // resulting tokens through to tenant and redirects the browser back to
20
+ // ``redirectAfterUrl`` with ``?email_connect_result=<json>`` for the
21
+ // integrations page to consume on mount.
22
+ export const initEmailConnectOAuthEpic = (actions$, _state$, zeniAPI,
23
+ // Test seam: callers can override to assert intended URL without actually
24
+ // navigating. Default is full-page redirect via window.location.
25
+ navigate = defaultNavigate) => actions$.pipe(filter(initEmailConnectOAuth.match), switchMap((action) => zeniAPI
26
+ .postAndGetJSON(`${zeniAPI.apiEndPoints.authMicroServiceBaseUrl}/1.0/connectors/email/${action.payload.provider}/init`, { redirect_after_url: action.payload.redirectAfterUrl }, { 'zeni-tenant-id': action.payload.tenantId })
27
+ .pipe(mergeMap((response) => {
28
+ if (isSuccessResponse(response) && response.data != null) {
29
+ // Fire-and-forget side effect — once the browser navigates,
30
+ // any further state we'd dispatch here is irrelevant. We still
31
+ // emit the success action so subscribers (tests, analytics)
32
+ // can observe the URL the epic decided to navigate to.
33
+ navigate(response.data.authorization_url);
34
+ return from([
35
+ initEmailConnectOAuthSuccess(action.payload.tenantId, action.payload.provider, response.data.authorization_url),
36
+ ]);
37
+ }
38
+ const status = response.status ??
39
+ createZeniAPIStatus(`Failed to start ${action.payload.provider} connection. Please try again.`);
40
+ return from([
41
+ initEmailConnectOAuthFailure(action.payload.tenantId, action.payload.provider, status),
42
+ ]);
43
+ }), catchError((error) => {
44
+ const message = error instanceof Error
45
+ ? error.message
46
+ : `Unexpected error starting ${action.payload.provider} connection.`;
47
+ return from([
48
+ initEmailConnectOAuthFailure(action.payload.tenantId, action.payload.provider, createZeniAPIStatus(message)),
49
+ ]);
50
+ }))));
@@ -18,9 +18,13 @@ export const saveAPIKeyConnectionEpic = (actions$, _, zeniAPI) => actions$.pipe(
18
18
  }
19
19
  const status = response.status ??
20
20
  createZeniAPIStatus('Failed to save connection. Please try again.');
21
- return from([saveAPIKeyConnectionFailure(action.payload.tenantId, status)]);
21
+ return from([
22
+ saveAPIKeyConnectionFailure(action.payload.tenantId, status),
23
+ ]);
22
24
  }), catchError((error) => {
23
- const message = error instanceof Error ? error.message : 'Unexpected error saving connection.';
25
+ const message = error instanceof Error
26
+ ? error.message
27
+ : 'Unexpected error saving connection.';
24
28
  return from([
25
29
  saveAPIKeyConnectionFailure(action.payload.tenantId, createZeniAPIStatus(message)),
26
30
  ]);
@@ -20,9 +20,13 @@ export const saveOAuthConnectionEpic = (actions$, _, zeniAPI) => actions$.pipe(f
20
20
  }
21
21
  const status = response.status ??
22
22
  createZeniAPIStatus('Failed to save connection. Please try again.');
23
- return from([saveOAuthConnectionFailure(action.payload.tenantId, status)]);
23
+ return from([
24
+ saveOAuthConnectionFailure(action.payload.tenantId, status),
25
+ ]);
24
26
  }), catchError((error) => {
25
- const message = error instanceof Error ? error.message : 'Unexpected error saving connection.';
27
+ const message = error instanceof Error
28
+ ? error.message
29
+ : 'Unexpected error saving connection.';
26
30
  return from([
27
31
  saveOAuthConnectionFailure(action.payload.tenantId, createZeniAPIStatus(message)),
28
32
  ]);
@@ -9,7 +9,7 @@ import { toNumberOfMonthForAverage, toTimeSpanIdForAverage, } from '../../view/n
9
9
  import { date, dateNow } from '../../zeniDayJS';
10
10
  import { toSubscriptionSummary } from '../subscription/subscriptionSummary/subscriptionSummaryReducer';
11
11
  import { getInvitationStatus, getUserRoles, toMasterTOSInfo, toUserReimbursementInfo, } from '../userRole/userRolePayload';
12
- const EXTERNAL_INTEGRATION_TYPES = ['revenue', 'payments'];
12
+ const EXTERNAL_INTEGRATION_TYPES = ['revenue', 'payments', 'email'];
13
13
  export const toExternalIntegrationType = (v) => stringToUnion(v, EXTERNAL_INTEGRATION_TYPES);
14
14
  const EXTERNAL_SUPPORTED_TOOLS = [
15
15
  'chargebee',
@@ -17,6 +17,8 @@ const EXTERNAL_SUPPORTED_TOOLS = [
17
17
  'stripe',
18
18
  'mercury',
19
19
  'paypal',
20
+ 'gmail',
21
+ 'outlook',
20
22
  ];
21
23
  export const toExternalSupportedTool = (v) => stringToUnion(v, EXTERNAL_SUPPORTED_TOOLS);
22
24
  export const initialState = {
@@ -342,6 +344,7 @@ const tenant = createSlice({
342
344
  draft.externalConnectionsByTenant[action.payload.tenantId] = {
343
345
  fetchState: 'In-Progress',
344
346
  accounting: [],
347
+ email: [],
345
348
  payments: [],
346
349
  revenue: [],
347
350
  saveConnectionState: 'Not-Started',
@@ -359,10 +362,11 @@ const tenant = createSlice({
359
362
  return { payload: { tenantId, externalConnections } };
360
363
  },
361
364
  reducer(draft, action) {
362
- const { accounting, payments, revenue } = mapConnectionsPayloadToState(action.payload.externalConnections);
365
+ const { accounting, email, payments, revenue } = mapConnectionsPayloadToState(action.payload.externalConnections);
363
366
  draft.externalConnectionsByTenant[action.payload.tenantId] = {
364
367
  fetchState: 'Completed',
365
368
  accounting,
369
+ email,
366
370
  payments,
367
371
  revenue,
368
372
  saveConnectionState: 'Completed',
@@ -384,6 +388,7 @@ const tenant = createSlice({
384
388
  draft.externalConnectionsByTenant[tenantId] = {
385
389
  fetchState: 'Error',
386
390
  accounting: [],
391
+ email: [],
387
392
  payments: [],
388
393
  revenue: [],
389
394
  saveConnectionState: 'Error',
@@ -563,7 +568,12 @@ const tenant = createSlice({
563
568
  saveConnectorCredentialsSuccess: {
564
569
  prepare(tenantId, connectionType, connectionName, connectionPayload) {
565
570
  return {
566
- payload: { tenantId, connectionType, connectionName, connectionPayload },
571
+ payload: {
572
+ tenantId,
573
+ connectionType,
574
+ connectionName,
575
+ connectionPayload,
576
+ },
567
577
  };
568
578
  },
569
579
  reducer(draft, action) {
@@ -592,6 +602,56 @@ const tenant = createSlice({
592
602
  }
593
603
  },
594
604
  },
605
+ // Email-connect (Gmail / Outlook) OAuth init. Distinct from the other
606
+ // connector saves because the FE never sees the access_token — the
607
+ // user-facing redirect lands on the auth service's callback, auth
608
+ // exchanges code → tokens and posts them to tenant, then redirects the
609
+ // browser back to ``redirectAfterUrl`` with ``?email_connect_result=...``.
610
+ // The success action carries the provider's authorization_url so the
611
+ // epic can drive a full-page redirect, mirroring the legacy HubSpot
612
+ // flow in zeni-web-app-ui/IntegrationsScreen.
613
+ initEmailConnectOAuth: {
614
+ prepare(tenantId, provider, redirectAfterUrl) {
615
+ return { payload: { tenantId, provider, redirectAfterUrl } };
616
+ },
617
+ reducer(draft, action) {
618
+ const existing = draft.externalConnectionsByTenant[action.payload.tenantId];
619
+ if (existing != null) {
620
+ // Reuse the per-slug saveConnectorCredentialsStateBySlug bucket so
621
+ // the IntegrationsSettingsPage shows the same loading affordance
622
+ // it already uses for Stripe/Mercury/PayPal connect flows.
623
+ existing.saveConnectorCredentialsStateBySlug[action.payload.provider] = 'In-Progress';
624
+ delete existing.saveConnectorCredentialsErrorBySlug[action.payload.provider];
625
+ }
626
+ },
627
+ },
628
+ initEmailConnectOAuthSuccess: {
629
+ prepare(tenantId, provider, authorizationUrl) {
630
+ return { payload: { tenantId, provider, authorizationUrl } };
631
+ },
632
+ reducer(draft, action) {
633
+ // Intentionally leave state as 'In-Progress' — the epic is about to
634
+ // do a full-page redirect, so any UI state we set here would be
635
+ // overwritten by the navigation anyway. Setting Completed would
636
+ // briefly flash a success state before the redirect, which reads
637
+ // wrong because the connection isn't actually saved yet at this
638
+ // point (it lands after the OAuth round-trip).
639
+ void draft;
640
+ void action;
641
+ },
642
+ },
643
+ initEmailConnectOAuthFailure: {
644
+ prepare(tenantId, provider, status) {
645
+ return { payload: { tenantId, provider, status } };
646
+ },
647
+ reducer(draft, action) {
648
+ const existing = draft.externalConnectionsByTenant[action.payload.tenantId];
649
+ if (existing != null) {
650
+ existing.saveConnectorCredentialsStateBySlug[action.payload.provider] = 'Error';
651
+ existing.saveConnectorCredentialsErrorBySlug[action.payload.provider] = action.payload.status;
652
+ }
653
+ },
654
+ },
595
655
  deleteConnection: {
596
656
  prepare(tenantId, connectionId, connectionType) {
597
657
  return { payload: { tenantId, connectionId, connectionType } };
@@ -780,7 +840,7 @@ const tenant = createSlice({
780
840
  },
781
841
  },
782
842
  });
783
- export const { updateTenants, fetchAllTenants, updateTenantsSuccess, updateTenantsFailure, fetchActiveTenant, updateTenantSuccess, updateTenantFailure, updateCurrentTenant, fetchExcludedResources, updateExcludedResourcesSuccess, updateExcludedResourcesFailure, doSignIn, doMagicLinkSignIn, magicLinkSignInSuccess, magicLinkSignInFailure, sendEmailMagicLinkToUser, sendEmailMagicLinkToUserSuccess, sendEmailMagicLinkToUserFailure, updateSignInState, doSignOut, signOutSuccess, sendSessionHeartbeat, sessionHeartbeatSuccess, sessionHeartbeatFailure, updateLoggedInUser, fetchExternalConnections, saveExternalConnection, saveExternalConnectionSuccess, saveExternalConnectionFailure, fetchExternalConnectionsFailure, fetchExternalConnectionsSuccess, saveAPIKeyConnection, saveAPIKeyConnectionSuccess, saveAPIKeyConnectionFailure, saveOAuthConnection, saveOAuthConnectionSuccess, saveOAuthConnectionFailure, saveConnectorCredentials, saveConnectorCredentialsSuccess, saveConnectorCredentialsFailure, deleteConnection, deleteConnectionSuccess, deleteConnectionFailure, clearAll, fetchSubscriptionSummaryForTenant, updateSubscriptionSummaryForTenantSuccess, updateSubscriptionSummaryForTenantFailure, updateOnboardingTenants, removeOnboardingTenant, updateTenantReimbursementInfo, updateReferViewedForLoggedInUser, updateTreasuryVideoViewedForLoggedInUser, resetSignInState, trigger2FA, updateTenantAccountingClassesEnabled, updateTenantMasterTOSInfo, verifyDeviceWithTwoFA, verifyDeviceWithTwoFASuccess, verifyDeviceWithTwoFAFailure, resendVerifyDeviceOTP, resendVerifyDeviceOTPSuccess, resendVerifyDeviceOTPFailure, } = tenant.actions;
843
+ export const { updateTenants, fetchAllTenants, updateTenantsSuccess, updateTenantsFailure, fetchActiveTenant, updateTenantSuccess, updateTenantFailure, updateCurrentTenant, fetchExcludedResources, updateExcludedResourcesSuccess, updateExcludedResourcesFailure, doSignIn, doMagicLinkSignIn, magicLinkSignInSuccess, magicLinkSignInFailure, sendEmailMagicLinkToUser, sendEmailMagicLinkToUserSuccess, sendEmailMagicLinkToUserFailure, updateSignInState, doSignOut, signOutSuccess, sendSessionHeartbeat, sessionHeartbeatSuccess, sessionHeartbeatFailure, updateLoggedInUser, fetchExternalConnections, saveExternalConnection, saveExternalConnectionSuccess, saveExternalConnectionFailure, fetchExternalConnectionsFailure, fetchExternalConnectionsSuccess, saveAPIKeyConnection, saveAPIKeyConnectionSuccess, saveAPIKeyConnectionFailure, saveOAuthConnection, saveOAuthConnectionSuccess, saveOAuthConnectionFailure, saveConnectorCredentials, saveConnectorCredentialsSuccess, saveConnectorCredentialsFailure, initEmailConnectOAuth, initEmailConnectOAuthSuccess, initEmailConnectOAuthFailure, deleteConnection, deleteConnectionSuccess, deleteConnectionFailure, clearAll, fetchSubscriptionSummaryForTenant, updateSubscriptionSummaryForTenantSuccess, updateSubscriptionSummaryForTenantFailure, updateOnboardingTenants, removeOnboardingTenant, updateTenantReimbursementInfo, updateReferViewedForLoggedInUser, updateTreasuryVideoViewedForLoggedInUser, resetSignInState, trigger2FA, updateTenantAccountingClassesEnabled, updateTenantMasterTOSInfo, verifyDeviceWithTwoFA, verifyDeviceWithTwoFASuccess, verifyDeviceWithTwoFAFailure, resendVerifyDeviceOTP, resendVerifyDeviceOTPSuccess, resendVerifyDeviceOTPFailure, } = tenant.actions;
784
844
  export default tenant.reducer;
785
845
  /**
786
846
  * Converts tenants payload to Tenant and User State
@@ -907,6 +967,11 @@ export function mapConnectionsPayloadToState(connectionsPayload) {
907
967
  const { connections } = connectionsPayload;
908
968
  return {
909
969
  accounting: connections.accounting.map((payload) => toConnection(payload)),
970
+ // Tenant materializes gmail/outlook under the `email` bucket via
971
+ // _THIRD_PARTY_CONNECTORS_TO_MATERIALIZE when their slugs are enabled in
972
+ // third_party_enabled_connectors Statsig config. Default to [] so older
973
+ // tenant deploys (or disabled-everywhere envs) don't blow up here.
974
+ email: (connections.email ?? []).map((payload) => toConnection(payload)),
910
975
  payments: connections.payments.map((payload) => toConnection(payload)),
911
976
  revenue: (connections.revenue ?? []).map((payload) => toConnection(payload)),
912
977
  };
@@ -23,7 +23,9 @@ const toTransactionLineBase = (payload, lineType, currency) => ({
23
23
  });
24
24
  const toLinkedTransactionLine = (payload, currency) => ({
25
25
  ...toTransactionLineBase(payload, 'linked_transaction_line', currency),
26
- linkedTransactions: payload.linked_transactions != null ? payload.linked_transactions.map((transactionIDPayload) => toTransactionID(transactionIDPayload)) : [],
26
+ linkedTransactions: payload.linked_transactions != null
27
+ ? payload.linked_transactions.map((transactionIDPayload) => toTransactionID(transactionIDPayload))
28
+ : [],
27
29
  });
28
30
  /**
29
31
  * Transaction with account and class line payload with COT tracking