@zeniai/client-epic-state 5.0.37 → 5.0.38

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 (144) hide show
  1. package/lib/commonStateTypes/fiscalYearHelpers/formatZeniDateFY.d.ts +1 -0
  2. package/lib/commonStateTypes/fiscalYearHelpers/formatZeniDateFY.js +2 -1
  3. package/lib/entity/file/fileState.d.ts +4 -4
  4. package/lib/entity/file/fileState.js +4 -0
  5. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  6. package/lib/entity/snackbar/snackbarTypes.js +4 -0
  7. package/lib/entity/task/taskPayload.d.ts +3 -0
  8. package/lib/entity/task/taskPayload.js +5 -0
  9. package/lib/entity/task/taskState.d.ts +3 -0
  10. package/lib/entity/tenant/clearAllEpic.d.ts +5 -2
  11. package/lib/entity/tenant/clearAllEpic.js +6 -0
  12. package/lib/entity/tenant/epic/deleteConnectionEpic.d.ts +19 -0
  13. package/lib/entity/tenant/epic/deleteConnectionEpic.js +29 -0
  14. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.d.ts +19 -0
  15. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.js +31 -0
  16. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.d.ts +19 -0
  17. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.js +33 -0
  18. package/lib/entity/tenant/tenantPayload.d.ts +1 -0
  19. package/lib/entity/tenant/tenantReducer.d.ts +59 -1
  20. package/lib/entity/tenant/tenantReducer.js +139 -4
  21. package/lib/entity/tenant/tenantState.d.ts +7 -0
  22. package/lib/entity/transaction/stateTypes/attachment.d.ts +2 -2
  23. package/lib/epic.d.ts +9 -1
  24. package/lib/epic.js +12 -1
  25. package/lib/esm/commonStateTypes/fiscalYearHelpers/formatZeniDateFY.js +1 -0
  26. package/lib/esm/entity/file/fileState.js +4 -0
  27. package/lib/esm/entity/snackbar/snackbarTypes.js +4 -0
  28. package/lib/esm/entity/task/taskPayload.js +5 -0
  29. package/lib/esm/entity/tenant/clearAllEpic.js +6 -0
  30. package/lib/esm/entity/tenant/epic/deleteConnectionEpic.js +25 -0
  31. package/lib/esm/entity/tenant/epic/saveAPIKeyConnectionEpic.js +27 -0
  32. package/lib/esm/entity/tenant/epic/saveOAuthConnectionEpic.js +29 -0
  33. package/lib/esm/entity/tenant/tenantReducer.js +135 -2
  34. package/lib/esm/epic.js +12 -1
  35. package/lib/esm/index.js +15 -6
  36. package/lib/esm/init.js +71 -16
  37. package/lib/esm/reducer.js +9 -0
  38. package/lib/esm/view/featureNotificationView/epics/fetchRegisteredInterestsEpic.js +23 -0
  39. package/lib/esm/view/featureNotificationView/epics/notifyMeForFeatureEpic.js +25 -0
  40. package/lib/esm/view/featureNotificationView/featureNotificationViewPayload.js +11 -0
  41. package/lib/esm/view/featureNotificationView/featureNotificationViewReducer.js +106 -0
  42. package/lib/esm/view/featureNotificationView/featureNotificationViewSelector.js +4 -0
  43. package/lib/esm/view/featureNotificationView/featureNotificationViewState.js +1 -0
  44. package/lib/esm/view/taskManager/cannedResponsesView/cannedResponses.js +1 -0
  45. package/lib/esm/view/taskManager/cannedResponsesView/cannedResponsesPayload.js +12 -0
  46. package/lib/esm/view/taskManager/cannedResponsesView/cannedResponsesReducer.js +64 -0
  47. package/lib/esm/view/taskManager/cannedResponsesView/cannedResponsesSelector.js +3 -0
  48. package/lib/esm/view/taskManager/cannedResponsesView/epics/deleteCannedResponseEpic.js +17 -0
  49. package/lib/esm/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +21 -0
  50. package/lib/esm/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +29 -0
  51. package/lib/esm/view/taskManager/taskDetailView/epics/archiveTaskEpic.js +3 -0
  52. package/lib/esm/view/taskManager/taskDetailView/epics/deleteTaskEpic.js +5 -0
  53. package/lib/esm/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +1 -0
  54. package/lib/esm/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +1 -0
  55. package/lib/esm/view/taskManager/taskDetailView/epics/snoozeTaskEpic.js +66 -0
  56. package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +57 -0
  57. package/lib/esm/view/taskManager/taskDetailView/taskDetail.js +4 -0
  58. package/lib/esm/view/taskManager/taskDetailView/taskDetailReducer.js +60 -7
  59. package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +12 -0
  60. package/lib/esm/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.js +56 -21
  61. package/lib/esm/view/taskManager/taskListView/epics/dragNDropTasksEpic.js +1 -0
  62. package/lib/esm/view/taskManager/taskListView/epics/fetchTaskListEpic.js +13 -4
  63. package/lib/esm/view/taskManager/taskListView/epics/updateTaskFromListViewEpic.js +3 -2
  64. package/lib/esm/view/taskManager/taskListView/taskList.js +7 -0
  65. package/lib/esm/view/taskManager/taskListView/taskListReducer.js +106 -12
  66. package/lib/esm/view/taskManager/taskListView/taskListSelector.js +48 -39
  67. package/lib/esm/view/zeniOAuthView/epics/approveOAuthConsentEpic.js +65 -0
  68. package/lib/esm/view/zeniOAuthView/zeniOAuthParamsParser.js +41 -0
  69. package/lib/esm/view/zeniOAuthView/zeniOAuthReducer.js +49 -0
  70. package/lib/esm/view/zeniOAuthView/zeniOAuthSelector.js +3 -0
  71. package/lib/esm/view/zeniOAuthView/zeniOAuthState.js +1 -0
  72. package/lib/index.d.ts +18 -6
  73. package/lib/index.js +67 -30
  74. package/lib/init.d.ts +5 -3
  75. package/lib/init.js +71 -16
  76. package/lib/reducer.d.ts +9 -0
  77. package/lib/reducer.js +9 -0
  78. package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
  79. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  80. package/lib/view/featureNotificationView/epics/fetchRegisteredInterestsEpic.d.ts +16 -0
  81. package/lib/view/featureNotificationView/epics/fetchRegisteredInterestsEpic.js +27 -0
  82. package/lib/view/featureNotificationView/epics/notifyMeForFeatureEpic.d.ts +14 -0
  83. package/lib/view/featureNotificationView/epics/notifyMeForFeatureEpic.js +29 -0
  84. package/lib/view/featureNotificationView/featureNotificationViewPayload.d.ts +23 -0
  85. package/lib/view/featureNotificationView/featureNotificationViewPayload.js +15 -0
  86. package/lib/view/featureNotificationView/featureNotificationViewReducer.d.ts +19 -0
  87. package/lib/view/featureNotificationView/featureNotificationViewReducer.js +110 -0
  88. package/lib/view/featureNotificationView/featureNotificationViewSelector.d.ts +6 -0
  89. package/lib/view/featureNotificationView/featureNotificationViewSelector.js +11 -0
  90. package/lib/view/featureNotificationView/featureNotificationViewState.d.ts +26 -0
  91. package/lib/view/featureNotificationView/featureNotificationViewState.js +2 -0
  92. package/lib/view/taskManager/cannedResponsesView/cannedResponses.d.ts +19 -0
  93. package/lib/view/taskManager/cannedResponsesView/cannedResponses.js +2 -0
  94. package/lib/view/taskManager/cannedResponsesView/cannedResponsesPayload.d.ts +21 -0
  95. package/lib/view/taskManager/cannedResponsesView/cannedResponsesPayload.js +15 -0
  96. package/lib/view/taskManager/cannedResponsesView/cannedResponsesReducer.d.ts +30 -0
  97. package/lib/view/taskManager/cannedResponsesView/cannedResponsesReducer.js +68 -0
  98. package/lib/view/taskManager/cannedResponsesView/cannedResponsesSelector.d.ts +3 -0
  99. package/lib/view/taskManager/cannedResponsesView/cannedResponsesSelector.js +6 -0
  100. package/lib/view/taskManager/cannedResponsesView/epics/deleteCannedResponseEpic.d.ts +7 -0
  101. package/lib/view/taskManager/cannedResponsesView/epics/deleteCannedResponseEpic.js +21 -0
  102. package/lib/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.d.ts +7 -0
  103. package/lib/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +25 -0
  104. package/lib/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.d.ts +7 -0
  105. package/lib/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +33 -0
  106. package/lib/view/taskManager/taskDetailView/epics/archiveTaskEpic.d.ts +2 -1
  107. package/lib/view/taskManager/taskDetailView/epics/archiveTaskEpic.js +3 -0
  108. package/lib/view/taskManager/taskDetailView/epics/deleteTaskEpic.d.ts +2 -1
  109. package/lib/view/taskManager/taskDetailView/epics/deleteTaskEpic.js +5 -0
  110. package/lib/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +1 -0
  111. package/lib/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +1 -0
  112. package/lib/view/taskManager/taskDetailView/epics/snoozeTaskEpic.d.ts +9 -0
  113. package/lib/view/taskManager/taskDetailView/epics/snoozeTaskEpic.js +70 -0
  114. package/lib/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.d.ts +9 -0
  115. package/lib/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +61 -0
  116. package/lib/view/taskManager/taskDetailView/taskDetail.d.ts +2 -0
  117. package/lib/view/taskManager/taskDetailView/taskDetail.js +4 -0
  118. package/lib/view/taskManager/taskDetailView/taskDetailReducer.d.ts +10 -1
  119. package/lib/view/taskManager/taskDetailView/taskDetailReducer.js +61 -8
  120. package/lib/view/taskManager/taskDetailView/taskDetailSelector.d.ts +5 -0
  121. package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +12 -0
  122. package/lib/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.d.ts +6 -7
  123. package/lib/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.js +56 -22
  124. package/lib/view/taskManager/taskListView/epics/dragNDropTasksEpic.js +1 -0
  125. package/lib/view/taskManager/taskListView/epics/fetchTaskListEpic.js +13 -4
  126. package/lib/view/taskManager/taskListView/epics/updateTaskFromListViewEpic.js +3 -2
  127. package/lib/view/taskManager/taskListView/taskList.d.ts +14 -1
  128. package/lib/view/taskManager/taskListView/taskList.js +8 -1
  129. package/lib/view/taskManager/taskListView/taskListPayload.d.ts +3 -0
  130. package/lib/view/taskManager/taskListView/taskListReducer.d.ts +9 -6
  131. package/lib/view/taskManager/taskListView/taskListReducer.js +107 -13
  132. package/lib/view/taskManager/taskListView/taskListSelector.d.ts +9 -3
  133. package/lib/view/taskManager/taskListView/taskListSelector.js +48 -39
  134. package/lib/view/zeniOAuthView/epics/approveOAuthConsentEpic.d.ts +27 -0
  135. package/lib/view/zeniOAuthView/epics/approveOAuthConsentEpic.js +69 -0
  136. package/lib/view/zeniOAuthView/zeniOAuthParamsParser.d.ts +16 -0
  137. package/lib/view/zeniOAuthView/zeniOAuthParamsParser.js +44 -0
  138. package/lib/view/zeniOAuthView/zeniOAuthReducer.d.ts +29 -0
  139. package/lib/view/zeniOAuthView/zeniOAuthReducer.js +53 -0
  140. package/lib/view/zeniOAuthView/zeniOAuthSelector.d.ts +5 -0
  141. package/lib/view/zeniOAuthView/zeniOAuthSelector.js +9 -0
  142. package/lib/view/zeniOAuthView/zeniOAuthState.d.ts +4 -0
  143. package/lib/view/zeniOAuthView/zeniOAuthState.js +2 -0
  144. package/package.json +1 -1
package/lib/esm/init.js CHANGED
@@ -1,4 +1,3 @@
1
- import Pusher from 'pusher-js';
2
1
  import configureNewStore from './configureStore';
3
2
  import { ZeniAPIClient } from './zeniAPI';
4
3
  export let zeniAPI;
@@ -24,10 +23,23 @@ export default function initialize(restEndPoints, userId, sessionId, tenantId, i
24
23
  return store;
25
24
  }
26
25
  let isPusherInitialized = false;
26
+ // Caches an in-flight initializePusher() promise so that concurrent callers
27
+ // share a single Pusher instance instead of each constructing their own and
28
+ // leaking WebSocket connections (see Bugbot review on PR #2983). Cleared in
29
+ // the IIFE's `finally` once construction settles.
30
+ let pusherInitPromise;
27
31
  /**
28
32
  * Call initializePusher before using pusher in web-app
29
33
  */
30
- export function initializePusher(pusherClientSecret, options, headers, reInitialize = false) {
34
+ export async function initializePusher(pusherClientSecret, options, headers, reInitialize = false) {
35
+ // Pusher subscribes/decrypts only make sense in the browser. `pusher-js/
36
+ // with-encryption` resolves to a browser-only bundle that references
37
+ // `window` at module init, so even *importing* it on the server crashes.
38
+ // Bail out early on SSR/Node so the dynamic import below never executes.
39
+ if (typeof window === 'undefined') {
40
+ console.warn(`initializePusher called on server-side; pusher-js/with-encryption is browser-only — skipping.`);
41
+ return undefined;
42
+ }
31
43
  if (Boolean(zeniAPI.userId) === false ||
32
44
  Boolean(zeniAPI.tenantId) === false ||
33
45
  !initialized) {
@@ -38,19 +50,59 @@ export function initializePusher(pusherClientSecret, options, headers, reInitial
38
50
  console.warn(`Already initialized pusher`);
39
51
  return pusher;
40
52
  }
41
- const endpoint = `${zeniAPI.apiEndPoints.tenantMicroServiceBaseUrl}/1.0/notifications/push_notification/auth`;
42
- pusher = new Pusher(pusherClientSecret, {
43
- ...options,
44
- channelAuthorization: {
45
- endpoint,
46
- transport: 'ajax',
47
- customHandler: (params, callback) => customHandler(params, headers, callback),
48
- },
49
- }).bind('error', function (err) {
50
- console.warn('Pusher error', err);
51
- });
52
- console.info(`initializing with ${JSON.stringify(options)}`);
53
- return pusher;
53
+ // De-dupe concurrent in-flight calls so we don't construct multiple Pusher
54
+ // instances (each one a live WebSocket) and leak the older ones. We don't
55
+ // de-dupe when reInitialize=true — that's the caller asking for a fresh
56
+ // instance, which is the existing contract.
57
+ if (pusherInitPromise && !reInitialize) {
58
+ return pusherInitPromise;
59
+ }
60
+ // The IIFE captures `currentInit` so that, when its `finally` runs, it can
61
+ // identity-check before clearing the module-level `pusherInitPromise`. This
62
+ // matters when a reInitialize=true call overwrites `pusherInitPromise`
63
+ // while we're still in flight: without the identity check, our finally
64
+ // would clobber the newer call's slot and the next concurrent caller
65
+ // would bypass the dedup guard. Capture is safe because the first action
66
+ // inside the try is `await import(...)`, which always yields control —
67
+ // so the finally runs in a future microtask, after `currentInit` has
68
+ // already been assigned below. We declare it as `let … | undefined` so
69
+ // TS's definite-assignment analysis (which doesn't model the await) accepts
70
+ // the closure read; at runtime it's always the IIFE promise by then.
71
+ let currentInit = undefined;
72
+ currentInit = (async () => {
73
+ try {
74
+ // Dynamic import so the browser-only bundle is loaded only when this
75
+ // function actually runs (browser path). Works in both build outputs:
76
+ // • ESM (lib/esm): preserved as native `import()` → Vite/Rollup code-split
77
+ // • CJS (lib): TypeScript lowers to `Promise.resolve().then(() => require(...))`
78
+ // The pusher-js UMD wrapper does `module.exports = factory()` for CJS,
79
+ // so the module result is the Pusher class directly. Some bundlers wrap
80
+ // CJS in `{ default: ... }` for ESM interop — fall back to the module
81
+ // itself when `.default` is absent.
82
+ const pusherModule = (await import('pusher-js/with-encryption'));
83
+ const PusherConstructor = 'default' in pusherModule ? pusherModule.default : pusherModule;
84
+ const endpoint = `${zeniAPI.apiEndPoints.tenantMicroServiceBaseUrl}/1.0/notifications/push_notification/auth`;
85
+ pusher = new PusherConstructor(pusherClientSecret, {
86
+ ...options,
87
+ channelAuthorization: {
88
+ endpoint,
89
+ transport: 'ajax',
90
+ customHandler: (params, callback) => customHandler(params, headers, callback),
91
+ },
92
+ }).bind('error', function (err) {
93
+ console.warn('Pusher error', err);
94
+ });
95
+ console.info(`initializing with ${JSON.stringify(options)}`);
96
+ return pusher;
97
+ }
98
+ finally {
99
+ if (pusherInitPromise === currentInit) {
100
+ pusherInitPromise = undefined;
101
+ }
102
+ }
103
+ })();
104
+ pusherInitPromise = currentInit;
105
+ return currentInit;
54
106
  }
55
107
  const customHandler = async (params, headers, callback) => {
56
108
  const { socketId, channelName } = params;
@@ -64,7 +116,10 @@ const customHandler = async (params, headers, callback) => {
64
116
  .toPromise()
65
117
  .then((response) => {
66
118
  if (response.data != null) {
67
- callback(null, { auth: response.data.auth });
119
+ callback(null, {
120
+ auth: response.data.auth,
121
+ shared_secret: response.data.shared_secret,
122
+ });
68
123
  isPusherInitialized = true;
69
124
  }
70
125
  })
@@ -107,6 +107,7 @@ import expenseAutomationReconciliationView, { initialState as initialExpenseAuto
107
107
  import expenseAutomationTransactionsView, { initialState as initialExpenseAutomationTransactionsViewState, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
108
108
  import fileView from './view/fileView/fileViewReducer';
109
109
  import { initialFileViewState, } from './view/fileView/fileViewState';
110
+ import featureNotificationView, { initialState as initialFeatureNotificationViewState, } from './view/featureNotificationView/featureNotificationViewReducer';
110
111
  import financeStatement, { initialFinanceStatementState, } from './view/financeStatement/financeStatementReducer';
111
112
  import forecastList, { initialState as initialForecastListState, } from './view/forecastList/forecastListReducer';
112
113
  import globalMerchantAutoComplete, { initialState as initialGlobalMerchantAutoCompleteState, } from './view/globalMerchantView/globalMerchantAutoCompleteView/globalMerchantAutoCompleteViewReducer';
@@ -192,6 +193,7 @@ import zeniAccountSetupView, { initialState as initialZeniAccountSetupViewState,
192
193
  import zeniAccountsConfig, { initialState as initialZeniAccountsConfigState, } from './view/spendManagement/zeniAccounts/zeniAccountsConfig/zeniAccountsConfigReducer';
193
194
  import subscriptionView, { initialState as initialSubscriptionViewState, } from './view/subscriptionView/subscriptionViewReducer';
194
195
  import tagView, { initialState as initialTagListState, } from './view/tagView/tagViewReducer';
196
+ import cannedResponsesView, { initialState as initialCannedResponsesState, } from './view/taskManager/cannedResponsesView/cannedResponsesReducer';
195
197
  import taskDetailView, { initialState as initialTaskDetailState, } from './view/taskManager/taskDetailView/taskDetailReducer';
196
198
  import taskGroupTemplateView, { initialState as initialTaskGroupTemplateViewState, } from './view/taskManager/taskGroupTemplateView/taskGroupTemplateViewReducer';
197
199
  import taskGroupView, { initialState as initialTaskGroupViewState, } from './view/taskManager/taskGroupView/taskGroupViewReducer';
@@ -218,6 +220,7 @@ import vendorTabView, { initialState as initialVendorTabViewState, } from './vie
218
220
  import vendorTypeList, { initialState as initialVendorTypeListState, } from './view/vendorTypeList/vendorTypeListReducer';
219
221
  import zeniAccStatementList, { initialState as initialZeniAccStatementListState, } from './view/zeniAccStatementList/zeniAccStatementListReducer';
220
222
  import zeniAccountsPromoCard, { initialState as initialZeniAccountsPromoCardState, } from './view/zeniAccountsPromoCard/zeniAccountsPromoCardReducer';
223
+ import zeniOAuthView, { initialState as initialZeniOAuthViewState, } from './view/zeniOAuthView/zeniOAuthReducer';
221
224
  // Note: Please maintain strict alphabetical order
222
225
  const initialEntitiesState = {
223
226
  accountGroupState: initialAccountGroupState,
@@ -353,6 +356,7 @@ const initialViewsState = {
353
356
  expenseAutomationTransactionsViewState: initialExpenseAutomationTransactionsViewState,
354
357
  expenseAutomationViewState: initialExpenseAutomationViewState,
355
358
  expressPayState: initialExpressPayState,
359
+ featureNotificationViewState: initialFeatureNotificationViewState,
356
360
  fileViewState: initialFileViewState,
357
361
  financeStatementState: initialFinanceStatementState,
358
362
  forecastListState: initialForecastListState,
@@ -403,6 +407,7 @@ const initialViewsState = {
403
407
  setupViewState: initialSetupViewState,
404
408
  subscriptionViewState: initialSubscriptionViewState,
405
409
  tagViewState: initialTagListState,
410
+ cannedResponsesState: initialCannedResponsesState,
406
411
  taskDetailState: initialTaskDetailState,
407
412
  taskGroupTemplateViewState: initialTaskGroupTemplateViewState,
408
413
  taskGroupViewState: initialTaskGroupViewState,
@@ -442,6 +447,7 @@ const initialViewsState = {
442
447
  zeniAccountSetupViewState: initialZeniAccountSetupViewState,
443
448
  zeniAccStatementListState: initialZeniAccStatementListState,
444
449
  zeniAccountsPromoCardState: initialZeniAccountsPromoCardState,
450
+ zeniOAuthViewState: initialZeniOAuthViewState,
445
451
  };
446
452
  export const initialRootState = {
447
453
  ...initialEntitiesState,
@@ -582,6 +588,7 @@ const viewReducers = {
582
588
  expenseAutomationTransactionsViewState: expenseAutomationTransactionsView,
583
589
  expenseAutomationViewState: expenseAutomationView,
584
590
  expressPayState: expressPay,
591
+ featureNotificationViewState: featureNotificationView,
585
592
  fileViewState: fileView,
586
593
  financeStatementState: financeStatement,
587
594
  forecastListState: forecastList,
@@ -632,6 +639,7 @@ const viewReducers = {
632
639
  settingsViewState: settings,
633
640
  setupViewState: setupView,
634
641
  subscriptionViewState: subscriptionView,
642
+ cannedResponsesState: cannedResponsesView,
635
643
  taskDetailState: taskDetailView,
636
644
  taskGroupTemplateViewState: taskGroupTemplateView,
637
645
  taskGroupViewState: taskGroupView,
@@ -672,6 +680,7 @@ const viewReducers = {
672
680
  zeniAccountSetupViewState: zeniAccountSetupView,
673
681
  zeniAccStatementListState: zeniAccStatementList,
674
682
  zeniAccountsPromoCardState: zeniAccountsPromoCard,
683
+ zeniOAuthViewState: zeniOAuthView,
675
684
  };
676
685
  const reducers = combineReducers({
677
686
  ...entityReducers,
@@ -0,0 +1,23 @@
1
+ import { from } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
+ import { toFeatureInterest, } from '../featureNotificationViewPayload';
5
+ import { fetchRegisteredInterests, fetchRegisteredInterestsFailure, fetchRegisteredInterestsSuccess, } from '../featureNotificationViewReducer';
6
+ export const fetchRegisteredInterestsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchRegisteredInterests.match), switchMap((action) => zeniAPI
7
+ .getJSON(`${zeniAPI.apiEndPoints.notificationMicroServiceBaseUrl}/1.0/feature-interests?feature=${encodeURIComponent(action.payload.feature)}`)
8
+ .pipe(mergeMap((response) => {
9
+ if (isSuccessResponse(response) && response.data != null) {
10
+ const interests = response.data.feature_interests.map(toFeatureInterest);
11
+ return from([fetchRegisteredInterestsSuccess(interests)]);
12
+ }
13
+ const status = response.status ??
14
+ createZeniAPIStatus('Failed to fetch registered interests. Please try again.');
15
+ return from([fetchRegisteredInterestsFailure(status)]);
16
+ }), catchError((error) => {
17
+ const message = error instanceof Error
18
+ ? error.message
19
+ : 'Unexpected error fetching registered interests.';
20
+ return from([
21
+ fetchRegisteredInterestsFailure(createZeniAPIStatus(message)),
22
+ ]);
23
+ }))));
@@ -0,0 +1,25 @@
1
+ import { from } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
+ import { notifyMeForFeature, notifyMeForFeatureFailure, notifyMeForFeatureSuccess, } from '../featureNotificationViewReducer';
5
+ export const notifyMeForFeatureEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(notifyMeForFeature.match), switchMap((action) => zeniAPI
6
+ .postAndGetJSON(`${zeniAPI.apiEndPoints.notificationMicroServiceBaseUrl}/1.0/feature-interests`, {
7
+ additional_info: action.payload.additionalInfo,
8
+ email: action.payload.email,
9
+ feature: action.payload.feature,
10
+ })
11
+ .pipe(mergeMap((response) => {
12
+ if (isSuccessResponse(response) && response.data != null) {
13
+ return from([notifyMeForFeatureSuccess(response.data)]);
14
+ }
15
+ const status = response.status ??
16
+ createZeniAPIStatus('Failed to register interest. Please try again.');
17
+ return from([notifyMeForFeatureFailure(status)]);
18
+ }), catchError((error) => {
19
+ const message = error instanceof Error
20
+ ? error.message
21
+ : 'Unexpected error registering interest.';
22
+ return from([
23
+ notifyMeForFeatureFailure(createZeniAPIStatus(message)),
24
+ ]);
25
+ }))));
@@ -0,0 +1,11 @@
1
+ import { date } from '../../zeniDayJS';
2
+ export const toFeatureInterest = (payload) => ({
3
+ interestId: payload.interest_id,
4
+ feature: payload.feature,
5
+ notified: payload.notified,
6
+ createTime: date(payload.create_time),
7
+ updateTime: date(payload.update_time),
8
+ additionalInfo: payload.additional_info ?? undefined,
9
+ email: payload.email ?? undefined,
10
+ notifiedTime: payload.notified_time != null ? date(payload.notified_time) : undefined,
11
+ });
@@ -0,0 +1,106 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { toFeatureInterest, } from './featureNotificationViewPayload';
3
+ export const initialState = {
4
+ fetchRegisteredInterestsState: {
5
+ fetchState: 'Not-Started',
6
+ error: undefined,
7
+ },
8
+ notifyMeForFeatureState: {
9
+ fetchState: 'Not-Started',
10
+ error: undefined,
11
+ },
12
+ registeredInterests: [],
13
+ };
14
+ const featureNotificationView = createSlice({
15
+ name: 'featureNotificationView',
16
+ initialState,
17
+ reducers: {
18
+ notifyMeForFeature: {
19
+ prepare(feature, additionalInfo, email) {
20
+ return { payload: { feature, additionalInfo, email } };
21
+ },
22
+ reducer(draft,
23
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
24
+ _action) {
25
+ draft.notifyMeForFeatureState = {
26
+ fetchState: 'In-Progress',
27
+ error: undefined,
28
+ };
29
+ },
30
+ },
31
+ notifyMeForFeatureSuccess: {
32
+ prepare(payload) {
33
+ return { payload };
34
+ },
35
+ reducer(draft, action) {
36
+ draft.notifyMeForFeatureState = {
37
+ fetchState: 'Completed',
38
+ error: undefined,
39
+ };
40
+ const interest = toFeatureInterest(action.payload);
41
+ // Idempotent: if a row for this (feature, additional_info) already
42
+ // exists in the slice, replace it; otherwise append.
43
+ const existingIndex = draft.registeredInterests.findIndex((r) => r.feature === interest.feature &&
44
+ r.additionalInfo === interest.additionalInfo);
45
+ if (existingIndex >= 0) {
46
+ draft.registeredInterests[existingIndex] = interest;
47
+ }
48
+ else {
49
+ draft.registeredInterests.push(interest);
50
+ }
51
+ },
52
+ },
53
+ notifyMeForFeatureFailure: {
54
+ prepare(status) {
55
+ return { payload: { status } };
56
+ },
57
+ reducer(draft, action) {
58
+ draft.notifyMeForFeatureState = {
59
+ fetchState: 'Error',
60
+ error: action.payload.status,
61
+ };
62
+ },
63
+ },
64
+ fetchRegisteredInterests: {
65
+ prepare(feature) {
66
+ return { payload: { feature } };
67
+ },
68
+ reducer(draft,
69
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
70
+ _action) {
71
+ draft.fetchRegisteredInterestsState = {
72
+ fetchState: 'In-Progress',
73
+ error: undefined,
74
+ };
75
+ },
76
+ },
77
+ fetchRegisteredInterestsSuccess: {
78
+ prepare(interests) {
79
+ return { payload: { interests } };
80
+ },
81
+ reducer(draft, action) {
82
+ draft.fetchRegisteredInterestsState = {
83
+ fetchState: 'Completed',
84
+ error: undefined,
85
+ };
86
+ draft.registeredInterests = action.payload.interests;
87
+ },
88
+ },
89
+ fetchRegisteredInterestsFailure: {
90
+ prepare(status) {
91
+ return { payload: { status } };
92
+ },
93
+ reducer(draft, action) {
94
+ draft.fetchRegisteredInterestsState = {
95
+ fetchState: 'Error',
96
+ error: action.payload.status,
97
+ };
98
+ },
99
+ },
100
+ clearFeatureNotificationView(draft) {
101
+ Object.assign(draft, initialState);
102
+ },
103
+ },
104
+ });
105
+ export const { clearFeatureNotificationView, fetchRegisteredInterests, fetchRegisteredInterestsFailure, fetchRegisteredInterestsSuccess, notifyMeForFeature, notifyMeForFeatureFailure, notifyMeForFeatureSuccess, } = featureNotificationView.actions;
106
+ export default featureNotificationView.reducer;
@@ -0,0 +1,4 @@
1
+ export const getFeatureNotificationView = (state) => state.featureNotificationViewState;
2
+ export const getRegisteredInterests = (state) => state.featureNotificationViewState.registeredInterests;
3
+ export const getRegisteredInterestsByFeature = (state, feature) => state.featureNotificationViewState.registeredInterests.filter((interest) => interest.feature === feature);
4
+ export const isFeatureInterestRegistered = (state, feature, additionalInfo) => state.featureNotificationViewState.registeredInterests.some((interest) => interest.feature === feature && interest.additionalInfo === additionalInfo);
@@ -0,0 +1,12 @@
1
+ import { date } from '../../../zeniDayJS';
2
+ export function mapCannedResponsePayloadToCannedResponse(payload) {
3
+ return {
4
+ cannedResponseId: payload.canned_response_id,
5
+ content: payload.content,
6
+ createTime: date(payload.create_time),
7
+ createdBy: payload.created_by,
8
+ isDeleted: payload.is_deleted,
9
+ name: payload.name,
10
+ updateTime: date(payload.update_time),
11
+ };
12
+ }
@@ -0,0 +1,64 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ export const initialState = {
3
+ cannedResponses: [],
4
+ fetchState: 'Not-Started',
5
+ saveFetchState: 'Not-Started',
6
+ deleteFetchState: 'Not-Started',
7
+ error: undefined,
8
+ };
9
+ const cannedResponsesView = createSlice({
10
+ name: 'cannedResponsesView',
11
+ initialState,
12
+ reducers: {
13
+ fetchCannedResponses(draft) {
14
+ draft.fetchState =
15
+ draft.cannedResponses.length > 0 ? 'Completed' : 'In-Progress';
16
+ draft.error = undefined;
17
+ },
18
+ updateCannedResponses(draft, action) {
19
+ draft.cannedResponses = action.payload.cannedResponses;
20
+ draft.fetchState = 'Completed';
21
+ draft.error = undefined;
22
+ },
23
+ updateCannedResponsesFetchStatus(draft, action) {
24
+ draft.fetchState = action.payload.fetchState;
25
+ draft.error = action.payload.error;
26
+ },
27
+ saveCannedResponse: {
28
+ reducer(draft) {
29
+ draft.saveFetchState = 'In-Progress';
30
+ },
31
+ prepare(payload) {
32
+ return { payload };
33
+ },
34
+ },
35
+ saveCannedResponseSuccess(draft) {
36
+ draft.saveFetchState = 'Completed';
37
+ },
38
+ saveCannedResponseFailure(draft, action) {
39
+ draft.saveFetchState = 'Error';
40
+ draft.error = action.payload.error;
41
+ },
42
+ deleteCannedResponse: {
43
+ reducer(draft) {
44
+ draft.deleteFetchState = 'In-Progress';
45
+ },
46
+ prepare(payload) {
47
+ return { payload };
48
+ },
49
+ },
50
+ deleteCannedResponseSuccess(draft, action) {
51
+ draft.deleteFetchState = 'Completed';
52
+ draft.cannedResponses = draft.cannedResponses.filter((r) => r.cannedResponseId !== action.payload.cannedResponseId);
53
+ },
54
+ deleteCannedResponseFailure(draft, action) {
55
+ draft.deleteFetchState = 'Error';
56
+ draft.error = action.payload.error;
57
+ },
58
+ clearCannedResponses() {
59
+ return initialState;
60
+ },
61
+ },
62
+ });
63
+ export const { fetchCannedResponses, updateCannedResponses, updateCannedResponsesFetchStatus, saveCannedResponse, saveCannedResponseSuccess, saveCannedResponseFailure, deleteCannedResponse, deleteCannedResponseSuccess, deleteCannedResponseFailure, clearCannedResponses, } = cannedResponsesView.actions;
64
+ export default cannedResponsesView.reducer;
@@ -0,0 +1,3 @@
1
+ export function getCannedResponsesView(state) {
2
+ return state.cannedResponsesState;
3
+ }
@@ -0,0 +1,17 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
+ import { deleteCannedResponse, deleteCannedResponseFailure, deleteCannedResponseSuccess, } from '../cannedResponsesReducer';
5
+ export const deleteCannedResponseEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteCannedResponse.match), mergeMap((action) => {
6
+ const { cannedResponseId } = action.payload;
7
+ return zeniAPI
8
+ .deleteAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/canned-responses/${cannedResponseId}`)
9
+ .pipe(mergeMap((response) => {
10
+ if (isSuccessResponse(response)) {
11
+ return of(deleteCannedResponseSuccess({ cannedResponseId }));
12
+ }
13
+ return of(deleteCannedResponseFailure({ error: response.status }));
14
+ }), catchError((error) => of(deleteCannedResponseFailure({
15
+ error: createZeniAPIStatus('Unexpected Error', 'Delete Canned Response errored: ' + JSON.stringify(error)),
16
+ }))));
17
+ }));
@@ -0,0 +1,21 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, switchMap, mergeMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
+ import { mapCannedResponsePayloadToCannedResponse, } from '../cannedResponsesPayload';
5
+ import { fetchCannedResponses, updateCannedResponses, updateCannedResponsesFetchStatus, } from '../cannedResponsesReducer';
6
+ export const fetchCannedResponsesEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchCannedResponses.match), switchMap(() => zeniAPI
7
+ .getJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/canned-responses`)
8
+ .pipe(mergeMap((response) => {
9
+ if (isSuccessResponse(response) && response.data != null) {
10
+ return of(updateCannedResponses({
11
+ cannedResponses: (response.data.canned_responses ?? []).map(mapCannedResponsePayloadToCannedResponse),
12
+ }));
13
+ }
14
+ return of(updateCannedResponsesFetchStatus({
15
+ fetchState: 'Error',
16
+ error: response.status,
17
+ }));
18
+ }), catchError((error) => of(updateCannedResponsesFetchStatus({
19
+ fetchState: 'Error',
20
+ error: createZeniAPIStatus('Unexpected Error', 'Fetch Canned Responses errored: ' + JSON.stringify(error)),
21
+ }))))));
@@ -0,0 +1,29 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, switchMap, mergeMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
+ import { fetchCannedResponses, saveCannedResponse, saveCannedResponseFailure, saveCannedResponseSuccess, } from '../cannedResponsesReducer';
5
+ export const saveCannedResponseEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveCannedResponse.match), switchMap((action) => {
6
+ const { name, content, cannedResponseId } = action.payload;
7
+ const isUpdate = cannedResponseId != null;
8
+ const url = isUpdate
9
+ ? `${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/canned-responses/${cannedResponseId}`
10
+ : `${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/canned-responses`;
11
+ const apiCall = isUpdate
12
+ ? zeniAPI.putAndGetJSON(url, {
13
+ name,
14
+ content,
15
+ })
16
+ : zeniAPI.postAndGetJSON(url, {
17
+ name,
18
+ content,
19
+ });
20
+ return apiCall.pipe(mergeMap((response) => {
21
+ if (isSuccessResponse(response)) {
22
+ return of(saveCannedResponseSuccess(), fetchCannedResponses() // Refresh list after save
23
+ );
24
+ }
25
+ return of(saveCannedResponseFailure({ error: response.status }));
26
+ }), catchError((error) => of(saveCannedResponseFailure({
27
+ error: createZeniAPIStatus('Unexpected Error', 'Save Canned Response errored: ' + JSON.stringify(error)),
28
+ }))));
29
+ }));
@@ -4,6 +4,7 @@ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
4
  import { updateTasks } from '../../../../entity/task/taskReducer';
5
5
  import { getTaskById } from '../../../../entity/task/taskSelector';
6
6
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
7
+ import { fetchTaskList, removeTaskFromList, } from '../../taskListView/taskListReducer';
7
8
  import { archiveTask, archiveTaskSuccessOrFailure } from '../taskDetailReducer';
8
9
  // Epic function to handle archiving of tasks
9
10
  export const archiveTaskEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(archiveTask.match), mergeMap((action) => handleArchiveTask(action.payload.taskId, state$, zeniAPI)));
@@ -25,6 +26,8 @@ const handleApiResponse = (response, taskId) => {
25
26
  response.data.tasks.length > 0) {
26
27
  const fetchActions = [
27
28
  updateTasks(response.data.tasks),
29
+ removeTaskFromList(taskId),
30
+ fetchTaskList(),
28
31
  archiveTaskSuccessOrFailure({
29
32
  fetchState: 'Completed',
30
33
  taskId,
@@ -2,6 +2,7 @@ import { from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
4
  import { createZeniAPIStatus, isSuccessStatus, } from '../../../../responsePayload';
5
+ import { fetchTaskList, removeTaskFromList, } from '../../taskListView/taskListReducer';
5
6
  import { deleteTask, deleteTaskSuccessOrFailure } from '../taskDetailReducer';
6
7
  export const deleteTaskEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteTask.match), mergeMap((action) => {
7
8
  const { taskId } = action.payload;
@@ -10,6 +11,10 @@ export const deleteTaskEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filt
10
11
  .pipe(mergeMap((response) => {
11
12
  if (isSuccessStatus(response)) {
12
13
  const actions = [];
14
+ if (taskId != null) {
15
+ actions.push(removeTaskFromList(taskId));
16
+ }
17
+ actions.push(fetchTaskList());
13
18
  actions.push(deleteTaskSuccessOrFailure({ fetchState: 'Completed', taskId }));
14
19
  return from(actions);
15
20
  }
@@ -25,6 +25,7 @@ export const initializeTaskToLocalStoreEpic = (actions$, state$) => actions$.pip
25
25
  savedRecurringEndDate: task.recurringEndDate,
26
26
  recurringStartDate: task.recurringStartDate,
27
27
  timeSpent: task.timeSpent,
28
+ isPrivate: task.isPrivate,
28
29
  };
29
30
  const actions = [
30
31
  saveTaskUpdatesToLocalStore({ taskDetailLocalData, taskId }),
@@ -94,5 +94,6 @@ const prepareTaskPayload = (state, taskId, taskGroupId) => {
94
94
  sync_token: syncToken,
95
95
  task_group_ids: taskGroupId != null ? [taskGroupId] : [],
96
96
  time_spent: convertHHMMStrToMinutes(localData.timeSpent),
97
+ ...(taskId == null ? { is_private: localData.isPrivate ?? false } : {}),
97
98
  };
98
99
  };
@@ -0,0 +1,66 @@
1
+ import { from, of } from 'rxjs';
2
+ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { SNOOZED_DISPLAY_FORMAT } from '../../../../commonStateTypes/fiscalYearHelpers/formatZeniDateFY';
4
+ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
5
+ import { updateTasks } from '../../../../entity/task/taskReducer';
6
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
7
+ import { date } from '../../../../zeniDayJS';
8
+ import { fetchTaskList, removeTaskFromList, } from '../../taskListView/taskListReducer';
9
+ import { snoozeTask, snoozeTaskSuccessOrFailure } from '../taskDetailReducer';
10
+ export const snoozeTaskEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(snoozeTask.match), mergeMap((action) => handleSnoozeTask(action.payload.taskId, action.payload.snoozedUntil, zeniAPI)));
11
+ const formatSnoozedUntilForDisplay = (snoozedUntil) => date(snoozedUntil).format(SNOOZED_DISPLAY_FORMAT);
12
+ const handleSnoozeTask = (taskId, snoozedUntil, zeniAPI) => {
13
+ const snoozeTaskApi$ = zeniAPI.postAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}/snooze`, { snoozed_until: snoozedUntil });
14
+ return snoozeTaskApi$.pipe(mergeMap((response) => handleApiResponse(response, taskId, snoozedUntil)), catchError((error) => of(snoozeTaskSuccessOrFailure({
15
+ fetchState: 'Error',
16
+ taskId,
17
+ error: createZeniAPIStatus('Unexpected Error', 'Snooze Task REST API call errored out: ' + JSON.stringify(error)),
18
+ }))));
19
+ };
20
+ const handleApiResponse = (response, taskId, snoozedUntil) => {
21
+ if (isSuccessResponse(response) &&
22
+ response.data != null &&
23
+ response.data.tasks.length > 0) {
24
+ const fetchActions = [
25
+ updateTasks(response.data.tasks),
26
+ removeTaskFromList(taskId),
27
+ fetchTaskList(),
28
+ snoozeTaskSuccessOrFailure({
29
+ fetchState: 'Completed',
30
+ taskId,
31
+ }),
32
+ openSnackbar({
33
+ messageSection: 'snooze_task',
34
+ messageText: 'success',
35
+ type: 'success',
36
+ variables: [
37
+ {
38
+ variableName: '_snoozed_until_',
39
+ variableValue: formatSnoozedUntilForDisplay(snoozedUntil),
40
+ },
41
+ ],
42
+ }),
43
+ ];
44
+ return from(fetchActions);
45
+ }
46
+ else {
47
+ return from([
48
+ snoozeTaskSuccessOrFailure({
49
+ fetchState: 'Error',
50
+ taskId,
51
+ error: response.status,
52
+ }),
53
+ openSnackbar({
54
+ messageSection: 'snooze_task',
55
+ messageText: 'failed',
56
+ type: 'error',
57
+ variables: [
58
+ {
59
+ variableName: '_api-error_',
60
+ variableValue: response.status.message,
61
+ },
62
+ ],
63
+ }),
64
+ ]);
65
+ }
66
+ };