@zeniai/client-epic-state 5.2.47 → 5.2.49-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 (36) hide show
  1. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +2 -1
  2. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
  3. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -1
  4. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +7 -1
  5. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +5 -0
  6. package/lib/entity/notificationRegistry/notificationRegistryState.js +7 -1
  7. package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +3 -1
  8. package/lib/epic.js +4 -4
  9. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
  10. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +5 -0
  11. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
  12. package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +3 -1
  13. package/lib/esm/epic.js +4 -4
  14. package/lib/esm/index.js +4 -4
  15. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +4 -2
  16. package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +3 -1
  17. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  18. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +20 -7
  19. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
  20. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +24 -4
  21. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
  22. package/lib/index.d.ts +4 -4
  23. package/lib/index.js +21 -14
  24. package/lib/view/aiCfoView/aiCfoViewReducer.js +4 -2
  25. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +1 -1
  26. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +3 -1
  27. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  28. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +19 -6
  29. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +8 -5
  30. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
  31. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +4 -0
  32. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +30 -5
  33. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +4 -3
  34. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
  35. package/lib/view/settingsView/settingsViewPayload.d.ts +2 -0
  36. package/package.json +1 -1
@@ -107,32 +107,27 @@ const expenseAutomationReconciliationView = (0, toolkit_1.createSlice)({
107
107
  draft.selectedTab = 'review';
108
108
  draft.selectedAccountId = action.payload.accountId;
109
109
  }
110
- const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(action.payload.selectedPeriod)];
111
- const { transactionFetchState } = accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId];
112
- if (transactionFetchState.fetchState !== 'Completed' ||
110
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(action.payload.selectedPeriod)]?.reconciliationByAccountID[action.payload.accountId];
111
+ if (accountRecon == null) {
112
+ return;
113
+ }
114
+ if (accountRecon.transactionFetchState.fetchState !== 'Completed' ||
113
115
  action.payload.cacheOverride === true) {
114
- const data = accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId];
115
116
  if (action.payload.refreshViewInBackground === true) {
116
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId] = {
117
- ...data,
118
- refreshStatus: {
119
- fetchState: 'In-Progress',
120
- error: undefined,
121
- },
122
- accountId: action.payload.accountId,
123
- reconciliationId: undefined,
117
+ accountRecon.refreshStatus = {
118
+ fetchState: 'In-Progress',
119
+ error: undefined,
124
120
  };
121
+ accountRecon.accountId = action.payload.accountId;
122
+ accountRecon.reconciliationId = undefined;
125
123
  }
126
124
  else {
127
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId] = {
128
- ...data,
129
- transactionFetchState: {
130
- fetchState: 'In-Progress',
131
- error: undefined,
132
- },
133
- accountId: action.payload.accountId,
134
- reconciliationId: undefined,
125
+ accountRecon.transactionFetchState = {
126
+ fetchState: 'In-Progress',
127
+ error: undefined,
135
128
  };
129
+ accountRecon.accountId = action.payload.accountId;
130
+ accountRecon.reconciliationId = undefined;
136
131
  }
137
132
  }
138
133
  }
@@ -184,14 +179,17 @@ const expenseAutomationReconciliationView = (0, toolkit_1.createSlice)({
184
179
  }
185
180
  }
186
181
  else {
187
- const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(action.payload.selectedPeriod)];
182
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(action.payload.selectedPeriod)]?.reconciliationByAccountID[action.payload.accountId];
183
+ if (accountRecon == null) {
184
+ return;
185
+ }
188
186
  if (action.payload.refreshViewInBackground === true) {
189
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].refreshStatus.error = action.payload.error;
190
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].refreshStatus.fetchState = 'Error';
187
+ accountRecon.refreshStatus.error = action.payload.error;
188
+ accountRecon.refreshStatus.fetchState = 'Error';
191
189
  }
192
190
  else {
193
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].transactionFetchState.error = action.payload.error;
194
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].transactionFetchState.fetchState = 'Error';
191
+ accountRecon.transactionFetchState.error = action.payload.error;
192
+ accountRecon.transactionFetchState.fetchState = 'Error';
195
193
  }
196
194
  }
197
195
  },
@@ -396,8 +394,11 @@ const expenseAutomationReconciliationView = (0, toolkit_1.createSlice)({
396
394
  },
397
395
  setStatementParseInProgress: (draft, action) => {
398
396
  const { accountId, selectedPeriod, statementParseInProgress } = action.payload;
399
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress =
400
- statementParseInProgress;
397
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
398
+ if (accountRecon == null) {
399
+ return;
400
+ }
401
+ accountRecon.statementParseInProgress = statementParseInProgress;
401
402
  },
402
403
  setStatementProcessingDismissedToBackground: (draft, action) => {
403
404
  const { accountId, selectedPeriod, dismissedToBackground } = action.payload;
@@ -481,15 +482,23 @@ const expenseAutomationReconciliationView = (0, toolkit_1.createSlice)({
481
482
  },
482
483
  uploadAccountStatementSuccess: (draft, action) => {
483
484
  const { accountId, selectedPeriod } = action.payload;
484
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
485
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
486
+ if (accountRecon == null) {
487
+ return;
488
+ }
489
+ accountRecon.statementUploadStatus = {
485
490
  fetchState: 'Completed',
486
491
  error: undefined,
487
492
  };
488
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress = true;
493
+ accountRecon.statementParseInProgress = true;
489
494
  },
490
495
  uploadAccountStatementFailure: (draft, action) => {
491
496
  const { accountId, selectedPeriod } = action.payload;
492
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
497
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
498
+ if (accountRecon == null) {
499
+ return;
500
+ }
501
+ accountRecon.statementUploadStatus = {
493
502
  fetchState: 'Error',
494
503
  error: action.payload.error,
495
504
  };
@@ -682,23 +691,34 @@ const expenseAutomationReconciliationView = (0, toolkit_1.createSlice)({
682
691
  },
683
692
  reparseStatementFailure: (draft, action) => {
684
693
  const { accountId, selectedPeriod } = action.payload;
685
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
694
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
695
+ if (accountRecon == null) {
696
+ return;
697
+ }
698
+ accountRecon.reparseStatementStatus = {
686
699
  fetchState: 'Error',
687
700
  error: action.payload.error,
688
701
  };
689
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].statementProcessingFailed = true;
702
+ accountRecon.statementProcessingFailed = true;
690
703
  },
691
704
  resetReparseStatementStatus: (draft, action) => {
692
705
  const { accountId, selectedPeriod } = action.payload;
693
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
706
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
707
+ if (accountRecon == null) {
708
+ return;
709
+ }
710
+ accountRecon.reparseStatementStatus = {
694
711
  fetchState: 'Not-Started',
695
712
  error: undefined,
696
713
  };
697
714
  },
698
715
  updateParsedStatementData: (draft, action) => {
699
716
  const { accountId, selectedPeriod, parsedStatementData } = action.payload;
700
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].parsedStatementData =
701
- parsedStatementData;
717
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
718
+ if (accountRecon == null) {
719
+ return;
720
+ }
721
+ accountRecon.parsedStatementData = parsedStatementData;
702
722
  },
703
723
  updateStatementUpdateLocalData: (draft, action) => {
704
724
  const { accountId, selectedPeriod, statementUpdateLocalData, statementUploadId, } = action.payload;
@@ -824,17 +844,21 @@ const expenseAutomationReconciliationView = (0, toolkit_1.createSlice)({
824
844
  },
825
845
  updateAccountReconciliationLocalData: (draft, action) => {
826
846
  const { accountId, selectedPeriod, localData } = action.payload;
827
- const oldLocalData = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].localData;
847
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
848
+ if (accountRecon == null) {
849
+ return;
850
+ }
851
+ const oldLocalData = accountRecon.localData;
828
852
  if (oldLocalData == null) {
829
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].localData = localData;
853
+ accountRecon.localData = localData;
830
854
  }
831
855
  else {
832
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].localData = {
856
+ accountRecon.localData = {
833
857
  ...oldLocalData,
834
858
  ...localData,
835
859
  };
836
860
  }
837
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId].localData = {
861
+ accountRecon.localData = {
838
862
  ...oldLocalData,
839
863
  ...localData,
840
864
  };
@@ -1124,7 +1148,10 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
1124
1148
  }
1125
1149
  }
1126
1150
  function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accountId, reconciliationData, refreshViewInBackground) {
1127
- const oldRecord = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId];
1151
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)]?.reconciliationByAccountID[accountId];
1152
+ if (accountRecon == null) {
1153
+ return;
1154
+ }
1128
1155
  const oldConnectionStatus = draft.accountConnectionStatusByAccountID[accountId];
1129
1156
  draft.accountConnectionStatusByAccountID[accountId] = {
1130
1157
  status: (0, accountReconState_1.toBankStatusCodeType)(reconciliationData.bank_status.code),
@@ -1134,21 +1161,17 @@ function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accoun
1134
1161
  : false,
1135
1162
  paymentAccountId: accounts.payment_account_id ?? undefined,
1136
1163
  };
1137
- const statementParseInProgress = resolveStatementParseInProgressAfterFetch(oldRecord, reconciliationData);
1164
+ const statementParseInProgress = resolveStatementParseInProgressAfterFetch(accountRecon, reconciliationData);
1165
+ accountRecon.reconciliationId =
1166
+ reconciliationData.reconciliation_id ?? undefined;
1167
+ accountRecon.statementParseInProgress = statementParseInProgress;
1138
1168
  if (refreshViewInBackground === true) {
1139
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId] = {
1140
- ...oldRecord,
1141
- reconciliationId: reconciliationData.reconciliation_id ?? undefined,
1142
- refreshStatus: { fetchState: 'Completed', error: undefined },
1143
- statementParseInProgress,
1144
- };
1169
+ accountRecon.refreshStatus = { fetchState: 'Completed', error: undefined };
1145
1170
  }
1146
1171
  else {
1147
- draft.accountReconciliationRecordsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)].reconciliationByAccountID[accountId] = {
1148
- ...oldRecord,
1149
- statementParseInProgress,
1150
- reconciliationId: reconciliationData.reconciliation_id ?? undefined,
1151
- transactionFetchState: { fetchState: 'Completed', error: undefined },
1172
+ accountRecon.transactionFetchState = {
1173
+ fetchState: 'Completed',
1174
+ error: undefined,
1152
1175
  };
1153
1176
  }
1154
1177
  }
@@ -1178,7 +1201,11 @@ const updateExcludedAccountExclusionInfo = (draft, account) => {
1178
1201
  };
1179
1202
  };
1180
1203
  const updateReconciliationByAccountIDOnIncludeAccount = (draft, accountId, reconciliation, monthYearPeriodId) => {
1181
- draft.accountReconciliationRecordsBySelectedPeriod[monthYearPeriodId].reconciliationByAccountID[accountId] = {
1204
+ const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[monthYearPeriodId];
1205
+ if (accountReconciliationRecords == null) {
1206
+ return;
1207
+ }
1208
+ accountReconciliationRecords.reconciliationByAccountID[accountId] = {
1182
1209
  accountId,
1183
1210
  reconciliationId: reconciliation.reconciliation_id ?? undefined,
1184
1211
  transactionFetchState: { fetchState: 'Not-Started', error: undefined },
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.saveNotificationPreferencesEpic = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
+ const notificationRegistryState_1 = require("../../../entity/notificationRegistry/notificationRegistryState");
6
7
  const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
7
8
  const responsePayload_1 = require("../../../responsePayload");
8
9
  const notificationPreferencesEndpoint_1 = require("../notificationPreferencesEndpoint");
@@ -16,9 +17,19 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
16
17
  // ignored, so stale success/failure actions cannot clobber the newer
17
18
  // batch's state. Server-side ordering is arrival-order LWW.
18
19
  (0, operators_1.switchMap)(([, state]) => {
19
- const overrides = (0, notificationPreferencesViewSelector_1.getNotificationLocalOverrides)(state);
20
- const preferencesPatch = (0, notificationPreferencesViewPayload_1.mapPreferencesToPayload)(overrides);
21
- if (Object.keys(preferencesPatch).length === 0) {
20
+ // A clean domain is omitted entirely: an empty patch is still a write against it.
21
+ const overridesByDomain = {};
22
+ const body = {};
23
+ notificationRegistryState_1.NOTIFICATION_DOMAINS.forEach((domain) => {
24
+ const overrides = (0, notificationPreferencesViewSelector_1.getNotificationLocalOverridesForDomain)(state, domain);
25
+ const preferencesPatch = (0, notificationPreferencesViewPayload_1.mapPreferencesToPayload)(overrides);
26
+ if (Object.keys(preferencesPatch).length === 0) {
27
+ return;
28
+ }
29
+ overridesByDomain[domain] = overrides;
30
+ body[`${domain}_preferences`] = preferencesPatch;
31
+ });
32
+ if (Object.keys(body).length === 0) {
22
33
  // No diff → do not dispatch a synthetic success (that would wipe
23
34
  // stored preferences via the reducer). Simply no-op.
24
35
  return rxjs_1.EMPTY;
@@ -31,7 +42,6 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
31
42
  // in-flight save's response (Success/Failure never dispatched, so
32
43
  // the counter never drained and future saves got their merge dropped).
33
44
  const cancelEpochAtDispatch = state.notificationPreferencesViewState.cancelEpoch;
34
- const body = { task_checklist_preferences: preferencesPatch };
35
45
  // Suppress the error snackbar when the response lands AFTER the user
36
46
  // pressed Cancel — the Failure reducer already swallows the state
37
47
  // update, so the toast would be a stale error for a save the user
@@ -60,8 +70,11 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
60
70
  if ((0, responsePayload_1.isSuccessResponse)(response)) {
61
71
  return (0, rxjs_1.of)((0, notificationPreferencesViewReducer_1.saveNotificationPreferencesSuccess)({
62
72
  cancelEpochAtDispatch,
63
- savedOverrides: overrides,
64
- serverEcho: response.data?.task_checklist_preferences,
73
+ savedOverridesByDomain: overridesByDomain,
74
+ serverEchoByDomain: {
75
+ task_checklist: response.data?.task_checklist_preferences,
76
+ commenting: response.data?.commenting_preferences,
77
+ },
65
78
  }));
66
79
  }
67
80
  const actions = [
@@ -1,30 +1,33 @@
1
- import { NotificationChannel, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
1
+ import { NotificationChannel, NotificationDomain, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
2
2
  import { ZeniAPIStatus } from '../../responsePayload';
3
3
  import { NotificationPreferencesPayload } from './notificationPreferencesViewPayload';
4
- import { NotificationPreferences, NotificationPreferencesViewState } from './notificationPreferencesViewState';
4
+ import { NotificationPreferencesByDomain, NotificationPreferencesViewState } from './notificationPreferencesViewState';
5
5
  export declare const initialState: NotificationPreferencesViewState;
6
6
  export interface SetFrequencyPayload {
7
7
  frequency: NotificationFrequency;
8
8
  groupId: string;
9
+ domain?: NotificationDomain;
9
10
  }
10
11
  export interface TogglePreferencePayload {
11
12
  channel: NotificationChannel;
12
13
  enabled: boolean;
13
14
  eventId: string;
15
+ domain?: NotificationDomain;
14
16
  }
15
17
  export interface SetGroupChannelMasterPayload {
16
18
  channel: NotificationChannel;
17
19
  enabled: boolean;
18
20
  groupId: string;
21
+ domain?: NotificationDomain;
19
22
  }
20
23
  export interface FetchNotificationPreferencesSuccessPayload {
21
- preferences: NotificationPreferencesPayload | undefined;
24
+ preferencesByDomain: Partial<Record<NotificationDomain, NotificationPreferencesPayload | undefined>>;
22
25
  saveEpochAtDispatch: number;
23
26
  }
24
27
  export interface SaveNotificationPreferencesSuccessPayload {
25
28
  cancelEpochAtDispatch: number;
26
- savedOverrides: NotificationPreferences;
27
- serverEcho: NotificationPreferencesPayload | undefined;
29
+ savedOverridesByDomain: NotificationPreferencesByDomain;
30
+ serverEchoByDomain: Partial<Record<NotificationDomain, NotificationPreferencesPayload | undefined>>;
28
31
  }
29
32
  export interface SaveNotificationPreferencesFailurePayload {
30
33
  cancelEpochAtDispatch: number;
@@ -3,16 +3,27 @@ var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.toggleEventChannel = exports.setGroupFrequency = exports.setGroupChannelMaster = exports.saveNotificationPreferencesSuccess = exports.saveNotificationPreferencesFailure = exports.saveNotificationPreferences = exports.fetchNotificationPreferencesSuccess = exports.clearNotificationPreferencesLocalOverrides = exports.clearAllNotificationPreferencesView = exports.initialState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
+ const notificationRegistryState_1 = require("../../entity/notificationRegistry/notificationRegistryState");
6
7
  const rootActions_1 = require("../../rootActions");
7
8
  const notificationPreferencesViewPayload_1 = require("./notificationPreferencesViewPayload");
8
9
  const notificationPreferencesViewState_1 = require("./notificationPreferencesViewState");
9
10
  exports.initialState = {
10
11
  cancelEpoch: 0,
11
- localOverrides: (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(),
12
- preferences: (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(),
12
+ localOverridesByDomain: {},
13
+ preferencesByDomain: {},
13
14
  saveEpoch: 0,
14
15
  savePreferencesState: { fetchState: 'Not-Started' },
15
16
  };
17
+ // Lazy: a gate-off tenant never receives that domain's key at all.
18
+ const overridesFor = (byDomain, domain) => {
19
+ const existing = byDomain[domain];
20
+ if (existing != null) {
21
+ return existing;
22
+ }
23
+ const created = (0, notificationPreferencesViewState_1.emptyNotificationPreferences)();
24
+ byDomain[domain] = created;
25
+ return created;
26
+ };
16
27
  const mergePreferences = (base, patch) => {
17
28
  const eventEnabledByChannel = {
18
29
  ...base.eventEnabledByChannel,
@@ -150,7 +161,8 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
150
161
  // Success/Failure never dispatches, subsequent legitimate saves
151
162
  // still work because their captured epoch matches the current one.
152
163
  draft.cancelEpoch += 1;
153
- draft.localOverrides = (0, notificationPreferencesViewState_1.emptyNotificationPreferences)();
164
+ // One shared Save/Cancel footer, so Cancel discards every domain.
165
+ draft.localOverridesByDomain = {};
154
166
  // Also drop `savePreferencesState` back to Not-Started so any
155
167
  // "Saving…" affordance bound to `getNotificationPreferencesSaveState`
156
168
  // clears on Cancel. Without this, a Cancel-during-save whose stale
@@ -167,9 +179,6 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
167
179
  // them; the save-success path is the only clearer.
168
180
  // A `null`/`undefined` payload isn't a snapshot at all (e.g. the
169
181
  // envelope key was omitted); no-op instead of wiping preferences.
170
- if (action.payload.preferences == null) {
171
- return;
172
- }
173
182
  // Stale-GET guard (Cursor Bugbot 3632510237): if the fetch epic
174
183
  // captured a `saveEpoch` lower than the current one, a save has
175
184
  // succeeded since this GET dispatched — the response is carrying
@@ -179,7 +188,13 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
179
188
  if (action.payload.saveEpochAtDispatch < draft.saveEpoch) {
180
189
  return;
181
190
  }
182
- draft.preferences = (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(action.payload.preferences);
191
+ Object.entries(action.payload.preferencesByDomain).forEach(([domain, payload]) => {
192
+ if (payload == null) {
193
+ return;
194
+ }
195
+ draft.preferencesByDomain[domain] =
196
+ (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(payload);
197
+ });
183
198
  },
184
199
  saveNotificationPreferences(draft) {
185
200
  draft.savePreferencesState = { fetchState: 'In-Progress' };
@@ -238,33 +253,44 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
238
253
  // skip the `preferences` replace. Cursor Bugbot 3632510237.
239
254
  draft.saveEpoch += 1;
240
255
  draft.savePreferencesState = { fetchState: 'Completed' };
241
- const patch = action.payload.serverEcho == null
242
- ? action.payload.savedOverrides
243
- : (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(action.payload.serverEcho);
244
- draft.preferences = mergePreferences(draft.preferences, patch);
245
- draft.localOverrides = subtractSavedOverrides(draft.localOverrides, action.payload.savedOverrides);
256
+ Object.entries(action.payload.savedOverridesByDomain).forEach(([rawDomain, savedOverrides]) => {
257
+ if (savedOverrides == null) {
258
+ return;
259
+ }
260
+ const domain = rawDomain;
261
+ const serverEcho = action.payload.serverEchoByDomain[domain];
262
+ const patch = serverEcho == null
263
+ ? savedOverrides
264
+ : (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(serverEcho);
265
+ draft.preferencesByDomain[domain] = mergePreferences(draft.preferencesByDomain[domain] ?? (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(), patch);
266
+ draft.localOverridesByDomain[domain] = subtractSavedOverrides(draft.localOverridesByDomain[domain] ??
267
+ (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(), savedOverrides);
268
+ });
246
269
  },
247
270
  setGroupChannelMaster(draft, action) {
248
271
  // Mirror of `toggleEventChannel` but scoped to the group-channel master
249
272
  // held under `groupChannelEnabledByGroupId[groupId][channel]`.
250
273
  const { channel, enabled, groupId } = action.payload;
274
+ const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
251
275
  const groupMap = {
252
- ...(draft.localOverrides.groupChannelEnabledByGroupId[groupId] ?? {}),
276
+ ...(overrides.groupChannelEnabledByGroupId[groupId] ?? {}),
253
277
  };
254
278
  groupMap[channel] = enabled;
255
- draft.localOverrides.groupChannelEnabledByGroupId[groupId] = groupMap;
279
+ overrides.groupChannelEnabledByGroupId[groupId] = groupMap;
256
280
  },
257
281
  setGroupFrequency(draft, action) {
258
- draft.localOverrides.groupFrequencyByGroupId[action.payload.groupId] =
282
+ const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
283
+ overrides.groupFrequencyByGroupId[action.payload.groupId] =
259
284
  action.payload.frequency;
260
285
  },
261
286
  toggleEventChannel(draft, action) {
262
287
  const { channel, enabled, eventId } = action.payload;
288
+ const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
263
289
  const eventMap = {
264
- ...(draft.localOverrides.eventEnabledByChannel[eventId] ?? {}),
290
+ ...(overrides.eventEnabledByChannel[eventId] ?? {}),
265
291
  };
266
292
  eventMap[channel] = enabled;
267
- draft.localOverrides.eventEnabledByChannel[eventId] = eventMap;
293
+ overrides.eventEnabledByChannel[eventId] = eventMap;
268
294
  },
269
295
  },
270
296
  extraReducers: (builder) => {
@@ -1,7 +1,11 @@
1
1
  import { FetchStateAndError } from '../../commonStateTypes/common';
2
+ import { NotificationDomain } from '../../entity/notificationRegistry/notificationRegistryState';
2
3
  import { RootState } from '../../rootStateTypes';
3
4
  import { NotificationPreferences } from './notificationPreferencesViewState';
4
5
  export declare const getNotificationPreferencesSaveState: (state: RootState) => FetchStateAndError;
6
+ export declare const getEffectiveNotificationPreferencesForDomain: (state: RootState, domain: NotificationDomain) => NotificationPreferences;
5
7
  export declare const getEffectiveNotificationPreferences: (state: RootState) => NotificationPreferences;
8
+ export declare const getNotificationLocalOverridesForDomain: (state: RootState, domain: NotificationDomain) => NotificationPreferences;
6
9
  export declare const getNotificationLocalOverrides: (state: RootState) => NotificationPreferences;
10
+ export declare const hasUnsavedNotificationPreferencesForDomain: (state: RootState, domain: NotificationDomain) => boolean;
7
11
  export declare const hasUnsavedNotificationPreferences: (state: RootState) => boolean;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasUnsavedNotificationPreferences = exports.getNotificationLocalOverrides = exports.getEffectiveNotificationPreferences = exports.getNotificationPreferencesSaveState = void 0;
3
+ exports.hasUnsavedNotificationPreferences = exports.hasUnsavedNotificationPreferencesForDomain = exports.getNotificationLocalOverrides = exports.getNotificationLocalOverridesForDomain = exports.getEffectiveNotificationPreferences = exports.getEffectiveNotificationPreferencesForDomain = exports.getNotificationPreferencesSaveState = void 0;
4
4
  const toolkit_1 = require("@reduxjs/toolkit");
5
+ const notificationRegistryState_1 = require("../../entity/notificationRegistry/notificationRegistryState");
6
+ const notificationPreferencesViewState_1 = require("./notificationPreferencesViewState");
5
7
  const getView = (state) => state.notificationPreferencesViewState;
6
8
  const getNotificationPreferencesSaveState = (state) => getView(state).savePreferencesState;
7
9
  exports.getNotificationPreferencesSaveState = getNotificationPreferencesSaveState;
@@ -9,7 +11,12 @@ exports.getNotificationPreferencesSaveState = getNotificationPreferencesSaveStat
9
11
  // stable reference while neither input changes — a plain function would
10
12
  // allocate a fresh object on every call and re-render every `useSelector`
11
13
  // consumer on any unrelated dispatch.
12
- exports.getEffectiveNotificationPreferences = (0, toolkit_1.createSelector)((state) => getView(state).preferences, (state) => getView(state).localOverrides, (preferences, localOverrides) => {
14
+ const EMPTY_PREFERENCES = (0, notificationPreferencesViewState_1.emptyNotificationPreferences)();
15
+ // One per domain: a shared createSelector has a cache of one and would thrash across two.
16
+ const effectivePreferencesSelectorByDomain = new Map();
17
+ const createEffectivePreferencesSelector = (domain) => (0, toolkit_1.createSelector)((state) => getView(state).preferencesByDomain[domain], (state) => getView(state).localOverridesByDomain[domain], (storedPreferences, storedOverrides) => {
18
+ const preferences = storedPreferences ?? EMPTY_PREFERENCES;
19
+ const localOverrides = storedOverrides ?? EMPTY_PREFERENCES;
13
20
  const mergedEventEnabledByChannel = { ...preferences.eventEnabledByChannel };
14
21
  Object.entries(localOverrides.eventEnabledByChannel).forEach(([eventId, channels]) => {
15
22
  mergedEventEnabledByChannel[eventId] = {
@@ -33,8 +40,26 @@ exports.getEffectiveNotificationPreferences = (0, toolkit_1.createSelector)((sta
33
40
  },
34
41
  };
35
42
  });
36
- const getNotificationLocalOverrides = (state) => getView(state).localOverrides;
43
+ const getEffectiveNotificationPreferencesForDomain = (state, domain) => {
44
+ let selector = effectivePreferencesSelectorByDomain.get(domain);
45
+ if (selector == null) {
46
+ selector = createEffectivePreferencesSelector(domain);
47
+ effectivePreferencesSelectorByDomain.set(domain, selector);
48
+ }
49
+ return selector(state);
50
+ };
51
+ exports.getEffectiveNotificationPreferencesForDomain = getEffectiveNotificationPreferencesForDomain;
52
+ const getEffectiveNotificationPreferences = (state) => (0, exports.getEffectiveNotificationPreferencesForDomain)(state, notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
53
+ exports.getEffectiveNotificationPreferences = getEffectiveNotificationPreferences;
54
+ const getNotificationLocalOverridesForDomain = (state, domain) => getView(state).localOverridesByDomain[domain] ?? EMPTY_PREFERENCES;
55
+ exports.getNotificationLocalOverridesForDomain = getNotificationLocalOverridesForDomain;
56
+ const getNotificationLocalOverrides = (state) => (0, exports.getNotificationLocalOverridesForDomain)(state, notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
37
57
  exports.getNotificationLocalOverrides = getNotificationLocalOverrides;
38
- exports.hasUnsavedNotificationPreferences = (0, toolkit_1.createSelector)((state) => getView(state).localOverrides, (localOverrides) => Object.keys(localOverrides.eventEnabledByChannel).length > 0 ||
58
+ const isDirty = (localOverrides) => Object.keys(localOverrides.eventEnabledByChannel).length > 0 ||
39
59
  Object.keys(localOverrides.groupChannelEnabledByGroupId).length > 0 ||
40
- Object.keys(localOverrides.groupFrequencyByGroupId).length > 0);
60
+ Object.keys(localOverrides.groupFrequencyByGroupId).length > 0;
61
+ const hasUnsavedNotificationPreferencesForDomain = (state, domain) => isDirty((0, exports.getNotificationLocalOverridesForDomain)(state, domain));
62
+ exports.hasUnsavedNotificationPreferencesForDomain = hasUnsavedNotificationPreferencesForDomain;
63
+ // Drives the shared page-level Save/Cancel footer.
64
+ const hasUnsavedNotificationPreferences = (state) => Object.values(getView(state).localOverridesByDomain).some((localOverrides) => localOverrides != null && isDirty(localOverrides));
65
+ exports.hasUnsavedNotificationPreferences = hasUnsavedNotificationPreferences;
@@ -1,5 +1,5 @@
1
1
  import { FetchStateAndError } from '../../commonStateTypes/common';
2
- import { NotificationChannel, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
2
+ import { NotificationChannel, NotificationDomain, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
3
3
  export interface NotificationPreferences {
4
4
  eventEnabledByChannel: Record<string, Partial<Record<NotificationChannel, boolean>>>;
5
5
  groupChannelEnabledByGroupId: Record<string, Partial<Record<NotificationChannel, boolean>>>;
@@ -7,9 +7,10 @@ export interface NotificationPreferences {
7
7
  }
8
8
  export interface NotificationPreferencesViewState {
9
9
  cancelEpoch: number;
10
- localOverrides: NotificationPreferences;
11
- preferences: NotificationPreferences;
10
+ localOverridesByDomain: NotificationPreferencesByDomain;
11
+ preferencesByDomain: NotificationPreferencesByDomain;
12
12
  saveEpoch: number;
13
13
  savePreferencesState: FetchStateAndError;
14
14
  }
15
+ export type NotificationPreferencesByDomain = Partial<Record<NotificationDomain, NotificationPreferences>>;
15
16
  export declare const emptyNotificationPreferences: () => NotificationPreferences;
@@ -41,11 +41,21 @@ const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => actions$.pi
41
41
  // 67d6918e.
42
42
  updateActions.push((0, notificationRegistryReducer_1.updateNotificationRegistry)({
43
43
  registry: response.data.task_checklist_registry ?? undefined,
44
+ // An absent `<domain>_registry` means that domain's gate is off.
45
+ registriesByDomain: {
46
+ task_checklist: response.data.task_checklist_registry ?? undefined,
47
+ commenting: response.data.commenting_registry ?? undefined,
48
+ },
44
49
  registryEpochAtDispatch,
45
50
  }));
46
- if (response.data.task_checklist_preferences != null) {
51
+ // One action so the domains cannot land on different store epochs.
52
+ const preferencesByDomain = {
53
+ task_checklist: response.data.task_checklist_preferences ?? undefined,
54
+ commenting: response.data.commenting_preferences ?? undefined,
55
+ };
56
+ if (Object.values(preferencesByDomain).some((preferences) => preferences != null)) {
47
57
  updateActions.push((0, notificationPreferencesViewReducer_1.fetchNotificationPreferencesSuccess)({
48
- preferences: response.data.task_checklist_preferences,
58
+ preferencesByDomain,
49
59
  saveEpochAtDispatch,
50
60
  }));
51
61
  }
@@ -37,6 +37,8 @@ export interface NotificationSettingsPayload {
37
37
  pending_bill_approval_notification: RemindersPayload | null;
38
38
  pending_reimbursement_approval_notification: RemindersPayload | null;
39
39
  uncategorized_transaction_notification: RemindersPayload | null;
40
+ commenting_preferences?: NotificationPreferencesPayload;
41
+ commenting_registry?: NotificationRegistryPayload;
40
42
  month_end_email_recipient_email_ids?: string[];
41
43
  task_checklist_preferences?: NotificationPreferencesPayload;
42
44
  task_checklist_registry?: NotificationRegistryPayload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.47",
3
+ "version": "5.2.49-beta0ND",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",