@zeniai/client-epic-state 5.1.89 → 5.1.91-betaAK0

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 (51) hide show
  1. package/lib/entity/snackbar/snackbarApiError.d.ts +8 -0
  2. package/lib/entity/snackbar/snackbarApiError.js +15 -0
  3. package/lib/entity/tenant/tenantPayload.d.ts +1 -0
  4. package/lib/entity/tenant/tenantReducer.js +1 -0
  5. package/lib/entity/tenant/tenantSelector.d.ts +2 -0
  6. package/lib/entity/tenant/tenantSelector.js +11 -1
  7. package/lib/entity/tenant/tenantState.d.ts +1 -0
  8. package/lib/esm/entity/snackbar/snackbarApiError.js +11 -0
  9. package/lib/esm/entity/tenant/tenantReducer.js +1 -0
  10. package/lib/esm/entity/tenant/tenantSelector.js +9 -0
  11. package/lib/esm/index.js +3 -3
  12. package/lib/esm/view/invoicing/createCreditNote/createCreditNoteEpic.js +2 -0
  13. package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +2 -0
  14. package/lib/esm/view/invoicing/discountAction/discountActionEpic.js +6 -3
  15. package/lib/esm/view/invoicing/dunningAction/dunningActionEpic.js +6 -3
  16. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +6 -4
  17. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -0
  18. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +2 -0
  19. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +2 -0
  20. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +2 -0
  21. package/lib/esm/view/invoicing/invoiceAction/invoiceActionEpic.js +2 -0
  22. package/lib/esm/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +2 -0
  23. package/lib/esm/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +2 -0
  24. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +2 -0
  25. package/lib/esm/view/invoicing/recordPayment/recordPaymentEpic.js +3 -0
  26. package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +6 -3
  27. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +52 -0
  28. package/lib/esm/view/invoicing/subscriptionAction/subscriptionActionEpic.js +6 -3
  29. package/lib/esm/view/vendorTabView/vendorTabViewReducer.js +1 -1
  30. package/lib/index.d.ts +4 -4
  31. package/lib/index.js +46 -43
  32. package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.js +2 -0
  33. package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +2 -0
  34. package/lib/view/invoicing/discountAction/discountActionEpic.js +5 -2
  35. package/lib/view/invoicing/dunningAction/dunningActionEpic.js +5 -2
  36. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +0 -4
  37. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +6 -4
  38. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -0
  39. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +2 -0
  40. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +2 -0
  41. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +2 -0
  42. package/lib/view/invoicing/invoiceAction/invoiceActionEpic.js +2 -0
  43. package/lib/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +2 -0
  44. package/lib/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +2 -0
  45. package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +2 -0
  46. package/lib/view/invoicing/recordPayment/recordPaymentEpic.js +3 -0
  47. package/lib/view/invoicing/settingsView/settingsViewEpics.js +5 -2
  48. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +41 -1
  49. package/lib/view/invoicing/settingsView/settingsViewSelector.js +55 -1
  50. package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.js +5 -2
  51. package/package.json +2 -2
@@ -0,0 +1,8 @@
1
+ import { ZeniAPIStatus } from '../../responsePayload';
2
+ interface SnackbarMessageVariable {
3
+ variableName: string;
4
+ variableValue: string;
5
+ }
6
+ /** Undefined when the status carries no message, so the snackbar falls back to its generic copy. */
7
+ export declare const apiErrorSnackbarVariables: (status: ZeniAPIStatus | undefined) => SnackbarMessageVariable[] | undefined;
8
+ export {};
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiErrorSnackbarVariables = void 0;
4
+ /** Placeholder in every `failed` snackbar string, swapped for the API's message. */
5
+ const API_ERROR_TOKEN = '_api-error_';
6
+ /** Undefined when the status carries no message, so the snackbar falls back to its generic copy. */
7
+ // Pass only platform statuses: a local `createZeniAPIStatus` holds internal debug text.
8
+ const apiErrorSnackbarVariables = (status) => {
9
+ const message = status?.message?.trim() ?? '';
10
+ if (message === '') {
11
+ return undefined;
12
+ }
13
+ return [{ variableName: API_ERROR_TOKEN, variableValue: message }];
14
+ };
15
+ exports.apiErrorSnackbarVariables = apiErrorSnackbarVariables;
@@ -96,6 +96,7 @@ interface TenantPayload {
96
96
  capitalization_onboarding_shown_for_fixed_asset?: boolean;
97
97
  capitalization_onboarding_shown_for_prepaid?: boolean;
98
98
  master_tos_info?: MasterTOSInfoPayload;
99
+ receipts_email?: string | null;
99
100
  reimbursement_info?: UserReimbursementInfoDataPayload;
100
101
  zeni_book_close_date?: string;
101
102
  zeni_book_close_month_and_year?: string;
@@ -1044,6 +1044,7 @@ function mapTenantPayloadToTenant(payload, userTenantPayload) {
1044
1044
  masterTOSInfo: payload.master_tos_info != null
1045
1045
  ? (0, userRolePayload_1.toMasterTOSInfo)(payload.master_tos_info)
1046
1046
  : undefined,
1047
+ receiptsEmail: payload.receipts_email ?? undefined,
1047
1048
  rewardsLastViewedTime: userTenantPayload?.rewards_feature_last_viewed_time != null
1048
1049
  ? (0, zeniDayJS_1.date)(userTenantPayload.rewards_feature_last_viewed_time)
1049
1050
  : undefined,
@@ -92,4 +92,6 @@ export declare const getIsAccountingProjectsEnabled: (state: RootState) => boole
92
92
  export declare const isTenantBookkeepingEnabled: (tenant: Tenant | undefined) => boolean;
93
93
  export declare const isOtherProductsEnabledForTenant: (tenant: Tenant | undefined) => boolean;
94
94
  export declare const isTenantTreasuryEnabled: (tenant: Tenant | undefined) => boolean;
95
+ export declare const DEFAULT_RECEIPTS_EMAIL = "receipts@zeni.ai";
96
+ export declare function getReceiptsEmail(tenantState: TenantState, tenantId: ID | undefined): string;
95
97
  export {};
@@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isTenantTreasuryEnabled = exports.isOtherProductsEnabledForTenant = exports.isTenantBookkeepingEnabled = exports.getIsAccountingProjectsEnabled = exports.getIsAccountingClassesEnabled = exports.isTenantAccountingServicesEnabled = exports.isTenantCardsOnly = exports.isTenantBankingOnly = exports.toTenantCoreDetailsView = exports.getCurrentTenantExternalConnections = exports.isTenantUsingZeniCOA = exports.isZeniDomainTenant = exports.getTenantBaseViewForTenantView = exports.getTenantsCoreDetailsByCheckInDateSelector = exports.getTenantsBaseByCheckInDateSelector = exports.getTenantBaseById = exports.getTenantsByCheckInDateSelector = exports.getCurrentTenant = exports.getTenantBaseViewForTenantId = void 0;
6
+ exports.DEFAULT_RECEIPTS_EMAIL = exports.isTenantTreasuryEnabled = exports.isOtherProductsEnabledForTenant = exports.isTenantBookkeepingEnabled = exports.getIsAccountingProjectsEnabled = exports.getIsAccountingClassesEnabled = exports.isTenantAccountingServicesEnabled = exports.isTenantCardsOnly = exports.isTenantBankingOnly = exports.toTenantCoreDetailsView = exports.getCurrentTenantExternalConnections = exports.isTenantUsingZeniCOA = exports.isZeniDomainTenant = exports.getTenantBaseViewForTenantView = exports.getTenantsCoreDetailsByCheckInDateSelector = exports.getTenantsBaseByCheckInDateSelector = exports.getTenantBaseById = exports.getTenantsByCheckInDateSelector = exports.getCurrentTenant = exports.getTenantBaseViewForTenantId = void 0;
7
7
  exports.getTenant = getTenant;
8
8
  exports.getTenantIdByCompanyId = getTenantIdByCompanyId;
9
9
  exports.getOnboardingTenantByTenantId = getOnboardingTenantByTenantId;
10
+ exports.getReceiptsEmail = getReceiptsEmail;
10
11
  const toolkit_1 = require("@reduxjs/toolkit");
11
12
  const orderBy_1 = __importDefault(require("lodash/orderBy"));
12
13
  const companySelector_1 = require("../company/companySelector");
@@ -330,3 +331,12 @@ const isTenantTreasuryEnabled = (tenant) => {
330
331
  : false;
331
332
  };
332
333
  exports.isTenantTreasuryEnabled = isTenantTreasuryEnabled;
334
+ exports.DEFAULT_RECEIPTS_EMAIL = 'receipts@zeni.ai';
335
+ function getReceiptsEmail(tenantState, tenantId) {
336
+ const receiptsEmail = tenantId != null
337
+ ? tenantState.tenantsById[tenantId]?.receiptsEmail
338
+ : undefined;
339
+ // API can send empty string; treat it as "not configured"
340
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- intentional empty-string fallback
341
+ return receiptsEmail || exports.DEFAULT_RECEIPTS_EMAIL;
342
+ }
@@ -111,6 +111,7 @@ export interface Tenant {
111
111
  bookCloseDate?: ZeniDate;
112
112
  hubSpotId?: ID;
113
113
  masterTOSInfo?: MasterTOSInfo;
114
+ receiptsEmail?: string;
114
115
  rewardsLastViewedTime?: ZeniDate;
115
116
  userReimbursementInfo?: UserReimbursementInfo;
116
117
  zeniBookCloseDate?: ZeniDate;
@@ -0,0 +1,11 @@
1
+ /** Placeholder in every `failed` snackbar string, swapped for the API's message. */
2
+ const API_ERROR_TOKEN = '_api-error_';
3
+ /** Undefined when the status carries no message, so the snackbar falls back to its generic copy. */
4
+ // Pass only platform statuses: a local `createZeniAPIStatus` holds internal debug text.
5
+ export const apiErrorSnackbarVariables = (status) => {
6
+ const message = status?.message?.trim() ?? '';
7
+ if (message === '') {
8
+ return undefined;
9
+ }
10
+ return [{ variableName: API_ERROR_TOKEN, variableValue: message }];
11
+ };
@@ -1032,6 +1032,7 @@ function mapTenantPayloadToTenant(payload, userTenantPayload) {
1032
1032
  masterTOSInfo: payload.master_tos_info != null
1033
1033
  ? toMasterTOSInfo(payload.master_tos_info)
1034
1034
  : undefined,
1035
+ receiptsEmail: payload.receipts_email ?? undefined,
1035
1036
  rewardsLastViewedTime: userTenantPayload?.rewards_feature_last_viewed_time != null
1036
1037
  ? date(userTenantPayload.rewards_feature_last_viewed_time)
1037
1038
  : undefined,
@@ -308,3 +308,12 @@ export const isTenantTreasuryEnabled = (tenant) => {
308
308
  ? tenant.productSettings.isTreasuryEnabled === true
309
309
  : false;
310
310
  };
311
+ export const DEFAULT_RECEIPTS_EMAIL = 'receipts@zeni.ai';
312
+ export function getReceiptsEmail(tenantState, tenantId) {
313
+ const receiptsEmail = tenantId != null
314
+ ? tenantState.tenantsById[tenantId]?.receiptsEmail
315
+ : undefined;
316
+ // API can send empty string; treat it as "not configured"
317
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- intentional empty-string fallback
318
+ return receiptsEmail || DEFAULT_RECEIPTS_EMAIL;
319
+ }
package/lib/esm/index.js CHANGED
@@ -71,7 +71,7 @@ import { getSnackbar } from './entity/snackbar/snackbarSelector';
71
71
  import { ALL_WEEK_DAYS, toDayOfWeek, toPriorityCodeType, toTaskStatusCodeType, } from './entity/task/taskState';
72
72
  import { getTaskGroupById } from './entity/taskGroup/taskGroupSelector';
73
73
  import { deleteConnection, doMagicLinkSignIn, doSignIn, doSignOut, fetchActiveTenant, fetchAllTenants, fetchExcludedResources as fetchExcludedResourcesForTenant, fetchExternalConnections as fetchExternalConnectionsForTenant, fetchSubscriptionSummaryForTenant, initEmailConnectOAuth, resendVerifyDeviceOTP, resetSignInState, saveAPIKeyConnection, saveConnectorCredentials, saveExternalConnection as saveExternalConnectionForTenant, saveOAuthConnection, sendEmailMagicLinkToUser, sendSessionHeartbeat, toExternalIntegrationType, toExternalSupportedTool, updateCurrentTenant, updateSignInState, verifyDeviceWithTwoFA, } from './entity/tenant/tenantReducer';
74
- import { getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector, getTenantsByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, isOtherProductsEnabledForTenant, isTenantBankingOnly, isTenantBookkeepingEnabled, isTenantCardsOnly, isTenantUsingZeniCOA, isZeniDomainTenant, toTenantCoreDetailsView, } from './entity/tenant/tenantSelector';
74
+ import { DEFAULT_RECEIPTS_EMAIL, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector, getTenantsByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, isOtherProductsEnabledForTenant, isTenantBankingOnly, isTenantBookkeepingEnabled, isTenantCardsOnly, isTenantUsingZeniCOA, isZeniDomainTenant, toTenantCoreDetailsView, } from './entity/tenant/tenantSelector';
75
75
  import { pushToastNotification } from './entity/toastNotification/toastNotificationReducer';
76
76
  import { getLastNotificationTime, getNotifications, } from './entity/toastNotification/toastNotificationSelector';
77
77
  import { mapFileEntityToAttachment, toContentType, toContentTypeStrict, } from './entity/transaction/stateTypes/attachment';
@@ -482,7 +482,7 @@ export { toTimeframeTick, mapTimePeriodtoTimeframeTick, toTimePeriod, toAbsolute
482
482
  export { toVendorSpendTrendFilterTabsTypeStrict, } from './entity/vendorExpense/vendorExpenseSelector';
483
483
  export { getNumberOfPeriods };
484
484
  export { SCHEDULE_DAYS_OF_MONTH, toScheduleDaysOfMonth, toMonth, toMonthStrict, toQuarter, toQuarterStrict, };
485
- export { getTenantBaseById, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getTenantsByCheckInDateSelector, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, toTenantCoreDetailsView, getTenantBaseViewForTenantView, getTenantBaseViewForTenantId, isZeniDomainTenant, isTenantUsingZeniCOA, fetchActiveTenant, fetchAllTenants, updateCurrentTenant, clearAll, doMagicLinkSignIn, verifyDeviceWithTwoFA, resendVerifyDeviceOTP, sendEmailMagicLinkToUser, doSignIn, updateSignInState, doSignOut, sendSessionHeartbeat, resetSignInState, fetchExcludedResourcesForTenant, fetchExternalConnectionsForTenant, saveExternalConnectionForTenant, deleteConnection, saveAPIKeyConnection, saveConnectorCredentials, saveOAuthConnection, initEmailConnectOAuth, toExternalIntegrationType, toExternalSupportedTool, fetchSubscriptionSummaryForTenant, isTenantBankingOnly, isTenantCardsOnly, isTenantBookkeepingEnabled, isOtherProductsEnabledForTenant, };
485
+ export { getTenantBaseById, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getTenantsByCheckInDateSelector, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, toTenantCoreDetailsView, getTenantBaseViewForTenantView, getTenantBaseViewForTenantId, isZeniDomainTenant, isTenantUsingZeniCOA, fetchActiveTenant, fetchAllTenants, updateCurrentTenant, clearAll, doMagicLinkSignIn, verifyDeviceWithTwoFA, resendVerifyDeviceOTP, sendEmailMagicLinkToUser, doSignIn, updateSignInState, doSignOut, sendSessionHeartbeat, resetSignInState, fetchExcludedResourcesForTenant, fetchExternalConnectionsForTenant, saveExternalConnectionForTenant, deleteConnection, saveAPIKeyConnection, saveConnectorCredentials, saveOAuthConnection, initEmailConnectOAuth, toExternalIntegrationType, toExternalSupportedTool, fetchSubscriptionSummaryForTenant, isTenantBankingOnly, isTenantCardsOnly, isTenantBookkeepingEnabled, isOtherProductsEnabledForTenant, DEFAULT_RECEIPTS_EMAIL, getReceiptsEmail, };
486
486
  export { toAccountType, toAccountGroupType, getAccountGroupKey, getAllAccounts, getTransactionFilterAccountOptions, };
487
487
  export { getAllClasses, getClassById, getClassFilterOptions, } from './entity/class/classSelector';
488
488
  export { getForecast };
@@ -839,7 +839,7 @@ export { getInvoicingPlanById } from './entity/invoicing/plan/planSelector';
839
839
  export { getInvoicingProductById } from './entity/invoicing/product/productSelector';
840
840
  // ── Invoicing: settings ───────────────────────────────────────────────────────
841
841
  export { clearInvoicingSettings, connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, resetInvoicingBrandingFormDraft, resetSaveInvoicingSettings, saveInvoicingSettings, submitInvoicingBrandingForm, updateInvoicingBrandingFormDraft, updateInvoicingSettings, } from './view/invoicing/settingsView/settingsViewReducer';
842
- export { getInvoicingBrandingFormView, getInvoicingSettings, getInvoicingSettingsFetchState, getInvoicingSettingsView, } from './view/invoicing/settingsView/settingsViewSelector';
842
+ export { getInvoicingBrandingFormView, getInvoicingSettings, getInvoicingSettingsFetchState, getInvoicingSettingsHubView, getInvoicingSettingsView, } from './view/invoicing/settingsView/settingsViewSelector';
843
843
  export { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, initialInvoicingBrandingFormLocalData, } from './view/invoicing/settingsView/invoicingBrandingFormConfig';
844
844
  export { isStripeConnectedFromSettings } from './view/invoicing/settingsView/invoicingSettingsHelpers';
845
845
  // ── Invoicing: QBO integration ────────────────────────────────────────────────
@@ -1,6 +1,7 @@
1
1
  import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingCreditNotes } from '../../../entity/invoicing/creditNote/creditNoteReducer';
4
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
4
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
7
  import { localDataToCreateCreditNotePayload, } from './createCreditNotePayload';
@@ -29,6 +30,7 @@ export const createCreditNoteEpic = (actions$, state$, zeniAPI) => actions$.pipe
29
30
  messageSection: 'invoicing_credit_note_saved',
30
31
  messageText: 'failed',
31
32
  type: 'error',
33
+ variables: apiErrorSnackbarVariables(response.status),
32
34
  }),
33
35
  ]);
34
36
  }), catchError((error) => from([
@@ -3,6 +3,7 @@ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingInvoiceAddresses } from '../../../entity/invoicing/invoice/invoicePayload';
5
5
  import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
6
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
9
  import { addInvoiceToList } from '../invoiceList/invoiceListReducer';
@@ -36,6 +37,7 @@ export const createInvoiceEpic = (actions$, _state$, zeniAPI) => actions$.pipe(f
36
37
  messageSection: 'invoicing_invoice_saved',
37
38
  messageText: 'failed',
38
39
  type: 'error',
40
+ variables: apiErrorSnackbarVariables(response.status),
39
41
  }),
40
42
  ]);
41
43
  }), catchError((error) => from([
@@ -1,8 +1,9 @@
1
1
  import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingCoupons } from '../../../entity/invoicing/coupon/couponReducer';
4
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
4
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
6
7
  import { runInvoicingDiscountAction, runInvoicingDiscountActionFailure, runInvoicingDiscountActionSuccess, } from './discountActionReducer';
7
8
  /** Snackbar copy per action kind; unlisted kinds run without one. */
8
9
  const SNACKBAR_SECTION_BY_KIND = {
@@ -14,13 +15,15 @@ export const runInvoicingDiscountActionEpic = (actions$, _state$, zeniAPI) => ac
14
15
  const { action: kind, id } = action.payload;
15
16
  const requestBody = { action: kind };
16
17
  const snackbarSection = SNACKBAR_SECTION_BY_KIND[kind];
17
- const snackbar = (messageText) => snackbarSection == null
18
+ /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
19
+ const snackbar = (messageText, status) => snackbarSection == null
18
20
  ? []
19
21
  : [
20
22
  openSnackbar({
21
23
  messageSection: snackbarSection,
22
24
  messageText,
23
25
  type: messageText === 'success' ? 'success' : 'error',
26
+ variables: apiErrorSnackbarVariables(status),
24
27
  }),
25
28
  ];
26
29
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/discounts/${encodeURIComponent(id)}/actions`;
@@ -39,7 +42,7 @@ export const runInvoicingDiscountActionEpic = (actions$, _state$, zeniAPI) => ac
39
42
  error: response.status,
40
43
  id,
41
44
  }),
42
- ...snackbar('failed'),
45
+ ...snackbar('failed', response.status),
43
46
  ]);
44
47
  }), catchError((error) => from([
45
48
  runInvoicingDiscountActionFailure({
@@ -1,8 +1,9 @@
1
1
  import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingDunningCases } from '../../../entity/invoicing/dunningCase/dunningCaseReducer';
4
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
4
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
6
7
  import { runInvoicingDunningAction, runInvoicingDunningActionFailure, runInvoicingDunningActionSuccess, } from './dunningActionReducer';
7
8
  /**
8
9
  * Snackbar copy is per action kind; anything not listed here runs without one.
@@ -15,13 +16,15 @@ const SNACKBAR_SECTION_BY_KIND = {
15
16
  export const runInvoicingDunningActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingDunningAction.match), mergeMap((action) => {
16
17
  const { action: kind, body, id } = action.payload;
17
18
  const snackbarSection = SNACKBAR_SECTION_BY_KIND[kind];
18
- const snackbar = (messageText) => snackbarSection == null
19
+ /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
20
+ const snackbar = (messageText, status) => snackbarSection == null
19
21
  ? []
20
22
  : [
21
23
  openSnackbar({
22
24
  messageSection: snackbarSection,
23
25
  messageText,
24
26
  type: messageText === 'success' ? 'success' : 'error',
27
+ variables: apiErrorSnackbarVariables(status),
25
28
  }),
26
29
  ];
27
30
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/dunning/${encodeURIComponent(id)}/${kind}`;
@@ -40,7 +43,7 @@ export const runInvoicingDunningActionEpic = (actions$, _state$, zeniAPI) => act
40
43
  error: response.status,
41
44
  id,
42
45
  }),
43
- ...snackbar('failed'),
46
+ ...snackbar('failed', response.status),
44
47
  ]);
45
48
  }), catchError((error) => from([
46
49
  runInvoicingDunningActionFailure({
@@ -84,6 +84,8 @@ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues) => {
84
84
  * Build the parent catalog-item request body from the form draft. Creates omit
85
85
  * both parent and sub-family IDs; edits retain returned sub-family IDs.
86
86
  */
87
+ /** Sent for every catalog item now that the form does not collect a unit. */
88
+ const DEFAULT_CATALOG_ITEM_UNIT = 'USD';
87
89
  export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate) => {
88
90
  const description = trimOrUndefined(localData.description);
89
91
  const productCode = trimOrUndefined(localData.productCode) ?? toProductCode(localData.name);
@@ -101,10 +103,10 @@ export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate)
101
103
  if (description != null) {
102
104
  itemPayload.description = description;
103
105
  }
104
- const unit = trimOrUndefined(localData.unit);
105
- if (unit != null) {
106
- itemPayload.unit = unit;
107
- }
106
+ // The form no longer collects a unit, so a new item is created with the fixed
107
+ // default; an item that already carries one keeps it when saved from the form.
108
+ itemPayload.unit =
109
+ trimOrUndefined(localData.unit) ?? DEFAULT_CATALOG_ITEM_UNIT;
108
110
  if (localData.itemApplicability === 'restricted' &&
109
111
  localData.applicableItems.length > 0) {
110
112
  itemPayload.applicable_items = localData.applicableItems;
@@ -2,6 +2,7 @@ import { forkJoin, from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { mergeInvoicingPlanSubFamily, updateInvoicingPlans, } from '../../../entity/invoicing/plan/planReducer';
4
4
  import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
5
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
5
6
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
7
8
  import { addInvoicingCatalogItem } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
@@ -45,6 +46,7 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
45
46
  messageSection: 'invoicing_catalog_item_saved',
46
47
  messageText: 'failed',
47
48
  type: 'error',
49
+ variables: apiErrorSnackbarVariables(failedProductResponse.status),
48
50
  }),
49
51
  ]);
50
52
  }
@@ -94,6 +96,7 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
94
96
  messageSection: 'invoicing_catalog_item_saved',
95
97
  messageText: 'failed',
96
98
  type: 'error',
99
+ variables: apiErrorSnackbarVariables(response.status),
97
100
  }),
98
101
  ]);
99
102
  }), catchError((error) => from([
@@ -1,6 +1,7 @@
1
1
  import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingCoupons } from '../../../entity/invoicing/coupon/couponReducer';
4
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
4
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
7
  import { addInvoicingCouponToList } from '../couponView/couponViewReducer';
@@ -57,6 +58,7 @@ export const saveInvoicingCouponEpic = (actions$, state$, zeniAPI) => actions$.p
57
58
  messageSection: 'invoicing_coupon_saved',
58
59
  messageText: 'failed',
59
60
  type: 'error',
61
+ variables: apiErrorSnackbarVariables(response.status),
60
62
  }),
61
63
  ]);
62
64
  }), catchError((error) => from([
@@ -3,6 +3,7 @@ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingCustomerAddresses } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerPayload';
5
5
  import { updateInvoicingCustomers } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerReducer';
6
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
9
  import { resetNewAddressDataInLocalStore } from '../../addressView/addressViewReducer';
@@ -59,6 +60,7 @@ export const saveInvoicingCustomerEpic = (actions$, state$, zeniAPI) => actions$
59
60
  messageSection: 'invoicing_customer_saved',
60
61
  messageText: 'failed',
61
62
  type: 'error',
63
+ variables: apiErrorSnackbarVariables(response.status),
62
64
  }),
63
65
  ]);
64
66
  }), catchError((error) => from([
@@ -3,6 +3,7 @@ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingSubscriptionAddresses } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload';
5
5
  import { updateInvoicingSubscriptions } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionReducer';
6
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
9
  import { resetNewAddressDataInLocalStore } from '../../addressView/addressViewReducer';
@@ -45,6 +46,7 @@ export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => acti
45
46
  messageSection: 'invoicing_subscription_saved',
46
47
  messageText: 'failed',
47
48
  type: 'error',
49
+ variables: apiErrorSnackbarVariables(response.status),
48
50
  }),
49
51
  ];
50
52
  }
@@ -3,6 +3,7 @@ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingInvoiceAddresses } from '../../../entity/invoicing/invoice/invoicePayload';
5
5
  import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
6
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
9
  import { runInvoicingInvoiceAction, runInvoicingInvoiceActionFailure, runInvoicingInvoiceActionSuccess, updateInvoicingInvoice, } from './invoiceActionReducer';
@@ -80,6 +81,7 @@ export const updateInvoicingInvoiceEpic = (actions$, _state$, zeniAPI) => action
80
81
  messageSection: 'invoicing_invoice_saved',
81
82
  messageText: 'failed',
82
83
  type: 'error',
84
+ variables: apiErrorSnackbarVariables(response.status),
83
85
  }),
84
86
  ]);
85
87
  }), catchError((error) => from([
@@ -1,5 +1,6 @@
1
1
  import { from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { apiErrorSnackbarVariables } from '../../../../entity/snackbar/snackbarApiError';
3
4
  import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
5
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
6
  import { fetchInvoicingQboSyncHealth, retryInvoicingQboSyncTask, retryInvoicingQboSyncTaskFailure, retryInvoicingQboSyncTaskSuccess, } from '../invoicingQboViewReducer';
@@ -28,6 +29,7 @@ export const retryInvoicingQboSyncTaskEpic = (actions$, _state$, zeniAPI) => act
28
29
  messageSection: 'invoicing_qbo_sync_health_retry',
29
30
  messageText: 'failed',
30
31
  type: 'error',
32
+ variables: apiErrorSnackbarVariables(response.status),
31
33
  }),
32
34
  ]);
33
35
  }), catchError((error) => from([
@@ -1,5 +1,6 @@
1
1
  import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { apiErrorSnackbarVariables } from '../../../../entity/snackbar/snackbarApiError';
3
4
  import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
5
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
6
  import { fetchInvoicingQboSyncHealth, startInvoicingQboBackfill, startInvoicingQboBackfillFailure, startInvoicingQboBackfillSuccess, } from '../invoicingQboViewReducer';
@@ -27,6 +28,7 @@ export const startInvoicingQboBackfillEpic = (actions$, _state$, zeniAPI) => act
27
28
  messageSection: 'invoicing_qbo_backfill',
28
29
  messageText: 'failed',
29
30
  type: 'error',
31
+ variables: apiErrorSnackbarVariables(response.status),
30
32
  }),
31
33
  ]);
32
34
  }), catchError((error) => from([
@@ -1,6 +1,7 @@
1
1
  import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingCreditNotes } from '../../../entity/invoicing/creditNote/creditNoteReducer';
4
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
4
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
7
  import { fetchInvoiceDetail } from '../invoiceDetail/invoiceDetailReducer';
@@ -32,6 +33,7 @@ export const issueCreditNoteEpic = (actions$, state$, zeniAPI) => actions$.pipe(
32
33
  messageSection: 'invoicing_credit_note_saved',
33
34
  messageText: 'failed',
34
35
  type: 'error',
36
+ variables: apiErrorSnackbarVariables(response.status),
35
37
  }),
36
38
  ]);
37
39
  }), catchError((error) => from([
@@ -2,6 +2,7 @@ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
4
4
  import { updateInvoicingTransactions } from '../../../entity/invoicing/invoicingTransaction/invoicingTransactionReducer';
5
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
5
6
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
7
8
  import { fetchInvoiceDetail } from '../invoiceDetail/invoiceDetailReducer';
@@ -40,6 +41,7 @@ export const recordPaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(fi
40
41
  messageSection: 'invoicing_payment_recorded',
41
42
  messageText: 'failed',
42
43
  type: 'error',
44
+ variables: apiErrorSnackbarVariables(response.status),
43
45
  }),
44
46
  ]);
45
47
  }), catchError((error) => from([
@@ -82,6 +84,7 @@ export const recordPaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(fi
82
84
  messageSection: 'invoicing_payment_recorded',
83
85
  messageText: 'failed',
84
86
  type: 'error',
87
+ variables: apiErrorSnackbarVariables(response.status),
85
88
  }),
86
89
  ]);
87
90
  }), catchError((error) => from([
@@ -1,8 +1,9 @@
1
1
  import { from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
4
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
6
7
  import { connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, invoicingStripeConnectionFailure, saveInvoicingSettings, saveInvoicingSettingsFailure, saveInvoicingSettingsSuccess, updateInvoicingSettings, updateInvoicingSettingsFailure, updateInvoicingStripeConnection, } from './settingsViewReducer';
7
8
  export const fetchInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingSettings.match), switchMap(() => {
8
9
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
@@ -59,13 +60,15 @@ export const disconnectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => act
59
60
  export const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveInvoicingSettings.match), mergeMap((action) => {
60
61
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
61
62
  const { settings, snackbarSection } = action.payload;
62
- const snackbar = (messageText) => snackbarSection == null
63
+ /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
64
+ const snackbar = (messageText, status) => snackbarSection == null
63
65
  ? []
64
66
  : [
65
67
  openSnackbar({
66
68
  messageSection: snackbarSection,
67
69
  messageText,
68
70
  type: messageText === 'success' ? 'success' : 'error',
71
+ variables: apiErrorSnackbarVariables(status),
69
72
  }),
70
73
  ];
71
74
  return zeniAPI
@@ -79,7 +82,7 @@ export const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions
79
82
  }
80
83
  return from([
81
84
  saveInvoicingSettingsFailure(response.status),
82
- ...snackbar('failed'),
85
+ ...snackbar('failed', response.status),
83
86
  ]);
84
87
  }), catchError((error) => from([
85
88
  saveInvoicingSettingsFailure(createZeniAPIStatus('Unexpected Error', 'Save settings REST API call errored out ' +
@@ -1,3 +1,4 @@
1
+ import { getInvoicingQboAccountMappingId, } from '../../../entity/invoicing/invoicingCommonPayload';
1
2
  import { getFormattedAddress } from '../../addressView/addressViewSelector';
2
3
  import { getInvoicingConfigView } from '../invoicingConfigView/invoicingConfigViewSelector';
3
4
  import { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, } from './invoicingBrandingFormConfig';
@@ -30,6 +31,57 @@ export const getInvoicingSettingsView = (state) => {
30
31
  stripeConnectState: stripeFetchState,
31
32
  };
32
33
  };
34
+ /**
35
+ * Gates the Enable Invoicing action: payments must be able to land (Stripe
36
+ * connected *and* accepting charges) and the QBO accounting step must be
37
+ * finished, so an enabled tenant can always book what it invoices.
38
+ *
39
+ * Deliberately not part of `deriveInvoicingOnboardingStatus`, which routes the
40
+ * module off the config flags alone. Folding these in there would (a) demote a
41
+ * live tenant to the settings tree the moment an integration broke, stranding
42
+ * its existing invoices, and (b) deadlock the route gate, which fetches only the
43
+ * config — never the settings or QBO connection these read.
44
+ */
45
+ export function deriveInvoicingEnablePrerequisites(settings, isQboConnected, qboAccountMappingKindOptions) {
46
+ const isStripeConnected = settings?.stripe_connected === true &&
47
+ settings?.stripe_account?.charges_enabled === true;
48
+ const accountMappings = settings?.qbo?.account_mappings ?? undefined;
49
+ // An empty option list means the config has not landed yet — treating that as
50
+ // configured would let `every` pass vacuously and unlock the button early.
51
+ const isQboConfigured = isQboConnected &&
52
+ qboAccountMappingKindOptions.length > 0 &&
53
+ qboAccountMappingKindOptions.every((option) => getInvoicingQboAccountMappingId(accountMappings, option.code) != null);
54
+ return {
55
+ canEnable: isStripeConnected && isQboConfigured,
56
+ isQboConfigured,
57
+ isStripeConnected,
58
+ };
59
+ }
60
+ /**
61
+ * The settings hub's composite of everything it derives from the store: whether
62
+ * to offer Enable Invoicing at all, the in-flight flag for that action, and the
63
+ * two integration prerequisites kept separate so the hub can name the step still
64
+ * outstanding instead of showing a dead button.
65
+ *
66
+ * `canEnableInvoicing` folds in the onboarding step, so the screen does not need
67
+ * to re-check it: the footer is only offered mid-onboarding and only once both
68
+ * integrations are ready.
69
+ *
70
+ * Deliberately not folded into `getInvoicingConfigView`: that selector gates
71
+ * every invoicing route and stays a pure read of the config slice. The settings
72
+ * and QBO slices read here are only fetched once a settings screen is mounted.
73
+ */
74
+ export const getInvoicingSettingsHubView = (state) => {
75
+ const { enableState, qboAccountMappingKindOptions, status } = getInvoicingConfigView(state);
76
+ const { canEnable, isQboConfigured, isStripeConnected } = deriveInvoicingEnablePrerequisites(state.invoicingSettingsViewState.settings, state.invoicingQboViewState.connection?.connected === true, qboAccountMappingKindOptions);
77
+ return {
78
+ canEnableInvoicing: status === 'settings' && canEnable,
79
+ isEnablingInvoicing: enableState.fetchState === 'In-Progress',
80
+ isQboConfigured,
81
+ isStripeConnected,
82
+ status,
83
+ };
84
+ };
33
85
  /**
34
86
  * Bundled view for the branding settings form: the draft `localData` (seeded
35
87
  * from the existing settings until the user edits), the business-address label
@@ -3,8 +3,9 @@ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingSubscriptionAddresses } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload';
5
5
  import { updateInvoicingSubscriptions } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionReducer';
6
+ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
8
9
  import { runInvoicingSubscriptionAction, runInvoicingSubscriptionActionFailure, runInvoicingSubscriptionActionSuccess, } from './subscriptionActionReducer';
9
10
  /**
10
11
  * Each lifecycle action reports with its own copy. Keyed loosely because the
@@ -24,13 +25,15 @@ export const runInvoicingSubscriptionActionEpic = (actions$, _state$, zeniAPI) =
24
25
  const { action: kind, body, id } = action.payload;
25
26
  const requestBody = { action: kind, ...(body ?? {}) };
26
27
  const snackbarSection = SNACKBAR_SECTION_BY_ACTION[kind];
27
- const snackbar = (messageText) => snackbarSection == null
28
+ /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
29
+ const snackbar = (messageText, status) => snackbarSection == null
28
30
  ? []
29
31
  : [
30
32
  openSnackbar({
31
33
  messageSection: snackbarSection,
32
34
  messageText,
33
35
  type: messageText === 'success' ? 'success' : 'error',
36
+ variables: apiErrorSnackbarVariables(status),
34
37
  }),
35
38
  ];
36
39
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/subscriptions/${encodeURIComponent(id)}/actions`;
@@ -50,7 +53,7 @@ export const runInvoicingSubscriptionActionEpic = (actions$, _state$, zeniAPI) =
50
53
  error: response.status,
51
54
  id,
52
55
  }),
53
- ...snackbar('failed'),
56
+ ...snackbar('failed', response.status),
54
57
  ]);
55
58
  }), catchError((error) => from([
56
59
  runInvoicingSubscriptionActionFailure({
@@ -53,5 +53,5 @@ const vendorTabView = createSlice({
53
53
  });
54
54
  },
55
55
  });
56
- export const { clearVendorTabView, fetchVendorTabView, updateVendorTabViewTab, } = vendorTabView.actions;
56
+ export const { clearVendorTabView, fetchVendorTabView, updateVendorTabViewTab } = vendorTabView.actions;
57
57
  export default vendorTabView.reducer;