@zeniai/client-epic-state 5.1.55 → 5.1.57-betaDI0

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 (66) hide show
  1. package/lib/coreEpics.js +3 -2
  2. package/lib/entity/accountRecon/accountReconPayload.d.ts +1 -1
  3. package/lib/entity/accountRecon/accountReconReducer.js +15 -1
  4. package/lib/entity/chargeCard/chargeCard.d.ts +1 -1
  5. package/lib/entity/notificationRegistry/notificationRegistryPayload.d.ts +18 -0
  6. package/lib/entity/notificationRegistry/notificationRegistryPayload.js +28 -0
  7. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +6 -0
  8. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +26 -0
  9. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -0
  10. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +10 -0
  11. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +31 -0
  12. package/lib/entity/notificationRegistry/notificationRegistryState.js +10 -0
  13. package/lib/entity/tenant/clearAllEpic.d.ts +3 -1
  14. package/lib/entity/tenant/clearAllEpic.js +4 -0
  15. package/lib/epic.d.ts +2 -1
  16. package/lib/epic.js +2 -1
  17. package/lib/esm/coreEpics.js +3 -2
  18. package/lib/esm/entity/accountRecon/accountReconReducer.js +15 -1
  19. package/lib/esm/entity/notificationRegistry/notificationRegistryPayload.js +24 -0
  20. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +22 -0
  21. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +6 -0
  22. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
  23. package/lib/esm/entity/tenant/clearAllEpic.js +4 -0
  24. package/lib/esm/epic.js +2 -1
  25. package/lib/esm/index.js +6 -2
  26. package/lib/esm/reducer.js +6 -0
  27. package/lib/esm/view/expenseAutomationView/selectors/statementParseViewSelector.js +0 -224
  28. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +103 -0
  29. package/lib/esm/view/notificationPreferencesView/notificationPreferencesEndpoint.js +5 -0
  30. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewPayload.js +36 -0
  31. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +222 -0
  32. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +25 -0
  33. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewState.js +4 -0
  34. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +21 -2
  35. package/lib/esm/view/settingsView/epic/saveNotificationSettingsEpic.js +2 -1
  36. package/lib/esm/view/settingsView/settingsViewHelpers.js +3 -2
  37. package/lib/esm/view/transactionDetail/transactionDetailLocalDataHelper.js +28 -0
  38. package/lib/index.d.ts +9 -4
  39. package/lib/index.js +52 -42
  40. package/lib/reducer.d.ts +6 -0
  41. package/lib/reducer.js +6 -0
  42. package/lib/view/common/recurringViewHelper.d.ts +2 -2
  43. package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.d.ts +1 -15
  44. package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.js +1 -233
  45. package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +7 -44
  46. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.d.ts +34 -0
  47. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +107 -0
  48. package/lib/view/notificationPreferencesView/notificationPreferencesEndpoint.d.ts +2 -0
  49. package/lib/view/notificationPreferencesView/notificationPreferencesEndpoint.js +9 -0
  50. package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.d.ts +7 -0
  51. package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.js +41 -0
  52. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +30 -0
  53. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +226 -0
  54. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +7 -0
  55. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +32 -0
  56. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +14 -0
  57. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.js +8 -0
  58. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.d.ts +9 -1
  59. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +21 -2
  60. package/lib/view/settingsView/epic/saveNotificationSettingsEpic.js +2 -1
  61. package/lib/view/settingsView/settingsViewHelpers.d.ts +1 -1
  62. package/lib/view/settingsView/settingsViewHelpers.js +3 -2
  63. package/lib/view/settingsView/settingsViewPayload.d.ts +4 -0
  64. package/lib/view/settingsView/settingsViewState.d.ts +1 -1
  65. package/lib/view/transactionDetail/transactionDetailLocalDataHelper.js +28 -0
  66. package/package.json +1 -1
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var _a;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.toggleEventChannel = exports.setGroupFrequency = exports.saveNotificationPreferencesSuccess = exports.saveNotificationPreferencesFailure = exports.saveNotificationPreferences = exports.fetchNotificationPreferencesSuccess = exports.clearNotificationPreferencesLocalOverrides = exports.clearAllNotificationPreferencesView = exports.initialState = void 0;
5
+ const toolkit_1 = require("@reduxjs/toolkit");
6
+ const notificationPreferencesViewPayload_1 = require("./notificationPreferencesViewPayload");
7
+ const notificationPreferencesViewState_1 = require("./notificationPreferencesViewState");
8
+ exports.initialState = {
9
+ cancelEpoch: 0,
10
+ localOverrides: (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(),
11
+ preferences: (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(),
12
+ saveEpoch: 0,
13
+ savePreferencesState: { fetchState: 'Not-Started' },
14
+ };
15
+ const mergePreferences = (base, patch) => {
16
+ const eventEnabledByChannel = {
17
+ ...base.eventEnabledByChannel,
18
+ };
19
+ Object.entries(patch.eventEnabledByChannel).forEach(([eventId, channels]) => {
20
+ eventEnabledByChannel[eventId] = {
21
+ ...(base.eventEnabledByChannel[eventId] ?? {}),
22
+ ...channels,
23
+ };
24
+ });
25
+ return {
26
+ eventEnabledByChannel,
27
+ groupFrequencyByGroupId: {
28
+ ...base.groupFrequencyByGroupId,
29
+ ...patch.groupFrequencyByGroupId,
30
+ },
31
+ };
32
+ };
33
+ // Remove only the (eventId, channel) and groupId keys that were actually saved
34
+ // to the server from `localOverrides` — AND whose current value still equals
35
+ // what was saved. Any new edits the user added while the PUT was in flight
36
+ // (either new keys not present in `saved`, OR same keys the user flipped back
37
+ // to a different value) are preserved so the next debounce cycle picks them up.
38
+ //
39
+ // Presence-only subtraction (the prior implementation) silently dropped a
40
+ // same-key re-edit that landed AFTER debounce fired but BEFORE the response
41
+ // returned. Concrete case:
42
+ // 1. toggle evt1.email = false → localOverrides = {evt1:{email:false}}
43
+ // 2. debounce PUT #1 fires with savedOverrides = {evt1:{email:false}}
44
+ // 3. user re-toggles evt1.email = true → localOverrides = {evt1:{email:true}}
45
+ // 4. PUT #1 success → subtract dropped `email` on presence, so the true
46
+ // re-edit disappeared and next-debounce found nothing to send.
47
+ const subtractSavedOverrides = (current, saved) => {
48
+ const eventEnabledByChannel = {};
49
+ Object.entries(current.eventEnabledByChannel).forEach(([eventId, channels]) => {
50
+ const savedChannels = saved.eventEnabledByChannel[eventId];
51
+ if (savedChannels == null) {
52
+ eventEnabledByChannel[eventId] = channels;
53
+ return;
54
+ }
55
+ const remainingChannels = {};
56
+ Object.keys(channels).forEach((channel) => {
57
+ // Preserve the key when it's a new channel OR when the user's
58
+ // current value differs from what was saved (same-key re-edit).
59
+ if (!(channel in savedChannels) ||
60
+ channels[channel] !== savedChannels[channel]) {
61
+ remainingChannels[channel] = channels[channel];
62
+ }
63
+ });
64
+ if (Object.keys(remainingChannels).length > 0) {
65
+ eventEnabledByChannel[eventId] = remainingChannels;
66
+ }
67
+ });
68
+ const groupFrequencyByGroupId = {};
69
+ Object.entries(current.groupFrequencyByGroupId).forEach(([groupId, frequency]) => {
70
+ if (saved.groupFrequencyByGroupId[groupId] !== frequency) {
71
+ groupFrequencyByGroupId[groupId] = frequency;
72
+ }
73
+ });
74
+ return { eventEnabledByChannel, groupFrequencyByGroupId };
75
+ };
76
+ const notificationPreferencesView = (0, toolkit_1.createSlice)({
77
+ name: 'notificationPreferencesView',
78
+ initialState: exports.initialState,
79
+ reducers: {
80
+ clearAllNotificationPreferencesView(draft) {
81
+ // Reset everything EXCEPT `cancelEpoch`, which must stay monotonic
82
+ // across the lifetime of the app instance. `initialState.cancelEpoch`
83
+ // is `0`; returning `initialState` verbatim would rewind the counter
84
+ // to zero, and any in-flight save whose `cancelEpochAtDispatch` was
85
+ // captured pre-clearAll (>0) would then satisfy the "captured >=
86
+ // current" epoch guard and merge stale edits into fresh preferences.
87
+ // Bumping instead of resetting also invalidates the abandoned save
88
+ // for free — Cursor Bugbot bc7876f1 followup.
89
+ const nextCancelEpoch = draft.cancelEpoch + 1;
90
+ // saveEpoch must also stay monotonic across clearAll for the same
91
+ // reason (Cursor Bugbot 3632510237). Rewinding to 0 would let a
92
+ // stale-in-flight GET whose `saveEpochAtDispatch` was captured >0
93
+ // satisfy the "captured >= current" epoch guard and clobber fresh
94
+ // preferences on any subsequent save.
95
+ const nextSaveEpoch = draft.saveEpoch + 1;
96
+ return {
97
+ ...exports.initialState,
98
+ cancelEpoch: nextCancelEpoch,
99
+ saveEpoch: nextSaveEpoch,
100
+ };
101
+ },
102
+ clearNotificationPreferencesLocalOverrides(draft) {
103
+ // Discards in-flight per-event/per-group edits held in `localOverrides`
104
+ // without touching the fetched `preferences` snapshot. Used by the
105
+ // settings-page Cancel affordance so the effective preferences revert
106
+ // to the last server-known state without triggering a refetch race.
107
+ //
108
+ // Cancel/in-flight-save race: every Cancel bumps `cancelEpoch`
109
+ // monotonically. Any save that captured a lower epoch value at
110
+ // dispatch time is treated as stale by the Success/Failure reducer
111
+ // — its merge is skipped. Unconditional bump (vs the earlier
112
+ // counter model) survives switchMap abandonment and double-Cancel
113
+ // (Cursor Bugbot 4aa021f7): even if the abandoned save's
114
+ // Success/Failure never dispatches, subsequent legitimate saves
115
+ // still work because their captured epoch matches the current one.
116
+ draft.cancelEpoch += 1;
117
+ draft.localOverrides = (0, notificationPreferencesViewState_1.emptyNotificationPreferences)();
118
+ // Also drop `savePreferencesState` back to Not-Started so any
119
+ // "Saving…" affordance bound to `getNotificationPreferencesSaveState`
120
+ // clears on Cancel. Without this, a Cancel-during-save whose stale
121
+ // Success/Failure never fires (switchMap abandonment) leaves the
122
+ // selector reporting In-Progress forever — Cursor Bugbot bc7876f1.
123
+ draft.savePreferencesState = { fetchState: 'Not-Started' };
124
+ },
125
+ fetchNotificationPreferencesSuccess(draft, action) {
126
+ // Piggybacks on the settings-envelope fetch. The GET response is a full
127
+ // snapshot of the server-known preferences (empty maps included), so
128
+ // REPLACE `preferences` rather than merging — merging would leak keys
129
+ // the server has since removed. DO NOT touch `localOverrides`: a refetch
130
+ // that fires while the user has in-flight edits (pre-debounce) must not
131
+ // silently drop them; the save-success path is the only clearer.
132
+ // A `null`/`undefined` payload isn't a snapshot at all (e.g. the
133
+ // envelope key was omitted); no-op instead of wiping preferences.
134
+ if (action.payload.preferences == null) {
135
+ return;
136
+ }
137
+ // Stale-GET guard (Cursor Bugbot 3632510237): if the fetch epic
138
+ // captured a `saveEpoch` lower than the current one, a save has
139
+ // succeeded since this GET dispatched — the response is carrying
140
+ // the pre-PUT snapshot and would clobber the just-saved server
141
+ // truth back to the pre-edit state until the next refetch. Skip
142
+ // the replace; the store already reflects the fresher save.
143
+ if (action.payload.saveEpochAtDispatch < draft.saveEpoch) {
144
+ return;
145
+ }
146
+ draft.preferences = (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(action.payload.preferences);
147
+ },
148
+ saveNotificationPreferences(draft) {
149
+ draft.savePreferencesState = { fetchState: 'In-Progress' };
150
+ },
151
+ saveNotificationPreferencesFailure(draft, action) {
152
+ // Stale failure from a cancelled-since save — swallow silently so
153
+ // the UI doesn't surface an error banner for a save the user
154
+ // already abandoned.
155
+ if (action.payload.cancelEpochAtDispatch < draft.cancelEpoch) {
156
+ return;
157
+ }
158
+ // Preserve `localOverrides` so the user's in-flight edits survive a
159
+ // transient network blip. UI should surface an error affordance and
160
+ // allow retry.
161
+ draft.savePreferencesState = {
162
+ fetchState: 'Error',
163
+ error: action.payload.error,
164
+ };
165
+ },
166
+ saveNotificationPreferencesSuccess(draft, action) {
167
+ // Preserve-on-partial: PUT /preferences is patch-semantic. The response
168
+ // echoes only the fields that changed, so merge into `preferences`
169
+ // instead of replacing — otherwise a single-channel toggle would wipe
170
+ // every other stored preference.
171
+ //
172
+ // Rollback invariant (F5): `serverEcho` may be `undefined` if the server
173
+ // rolls back to an older shape that omits the echo entirely. In that
174
+ // case fall back to merging `savedOverrides` into `preferences` — the
175
+ // PUT succeeded (2xx), so the values we sent are now server truth. If
176
+ // we merged an empty patch instead, `subtractSavedOverrides` would
177
+ // still strip the just-saved keys from `localOverrides`, leaving the
178
+ // effective preferences (preferences ∪ localOverrides) reverted to the
179
+ // pre-edit server snapshot until the next refetch.
180
+ //
181
+ // In-flight-edit invariant: `savedOverrides` is the exact snapshot the
182
+ // epic PUT to the server. Subtracting those keys from `localOverrides`
183
+ // (instead of clearing it wholesale) preserves any edits the user
184
+ // added AFTER the debounce fired but BEFORE the response landed. The
185
+ // next debounce cycle will pick them up. Clearing wholesale drops
186
+ // in-flight edits from both UI (once server echo lands) and server.
187
+ // Cancel/in-flight-save race: `cancelEpoch` monotonically increases
188
+ // on every Cancel. If the save captured a lower epoch at dispatch,
189
+ // the user has pressed Cancel since — skip the `preferences` merge
190
+ // and the `localOverrides` subtract (Cancel already emptied it).
191
+ // Do NOT flip fetchState so a subsequent legit save can still show
192
+ // its own In-Progress → Completed lifecycle. The server itself has
193
+ // accepted the write — reverting server-side is out of FE scope; UI
194
+ // reflects the cancelled intention until the next refetch merges
195
+ // whatever the server persisted.
196
+ if (action.payload.cancelEpochAtDispatch < draft.cancelEpoch) {
197
+ return;
198
+ }
199
+ // Bump `saveEpoch` so any settings-envelope GET that was in flight
200
+ // BEFORE this PUT resolved is treated as stale. Its captured epoch
201
+ // will be lower than the new value, so its reducer branch will
202
+ // skip the `preferences` replace. Cursor Bugbot 3632510237.
203
+ draft.saveEpoch += 1;
204
+ draft.savePreferencesState = { fetchState: 'Completed' };
205
+ const patch = action.payload.serverEcho == null
206
+ ? action.payload.savedOverrides
207
+ : (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(action.payload.serverEcho);
208
+ draft.preferences = mergePreferences(draft.preferences, patch);
209
+ draft.localOverrides = subtractSavedOverrides(draft.localOverrides, action.payload.savedOverrides);
210
+ },
211
+ setGroupFrequency(draft, action) {
212
+ draft.localOverrides.groupFrequencyByGroupId[action.payload.groupId] =
213
+ action.payload.frequency;
214
+ },
215
+ toggleEventChannel(draft, action) {
216
+ const { channel, enabled, eventId } = action.payload;
217
+ const eventMap = {
218
+ ...(draft.localOverrides.eventEnabledByChannel[eventId] ?? {}),
219
+ };
220
+ eventMap[channel] = enabled;
221
+ draft.localOverrides.eventEnabledByChannel[eventId] = eventMap;
222
+ },
223
+ },
224
+ });
225
+ _a = notificationPreferencesView.actions, exports.clearAllNotificationPreferencesView = _a.clearAllNotificationPreferencesView, exports.clearNotificationPreferencesLocalOverrides = _a.clearNotificationPreferencesLocalOverrides, exports.fetchNotificationPreferencesSuccess = _a.fetchNotificationPreferencesSuccess, exports.saveNotificationPreferences = _a.saveNotificationPreferences, exports.saveNotificationPreferencesFailure = _a.saveNotificationPreferencesFailure, exports.saveNotificationPreferencesSuccess = _a.saveNotificationPreferencesSuccess, exports.setGroupFrequency = _a.setGroupFrequency, exports.toggleEventChannel = _a.toggleEventChannel;
226
+ exports.default = notificationPreferencesView.reducer;
@@ -0,0 +1,7 @@
1
+ import { FetchStateAndError } from '../../commonStateTypes/common';
2
+ import { RootState } from '../../reducer';
3
+ import { NotificationPreferences } from './notificationPreferencesViewState';
4
+ export declare const getNotificationPreferencesSaveState: (state: RootState) => FetchStateAndError;
5
+ export declare const getEffectiveNotificationPreferences: (state: RootState) => NotificationPreferences;
6
+ export declare const getNotificationLocalOverrides: (state: RootState) => NotificationPreferences;
7
+ export declare const hasUnsavedNotificationPreferences: (state: RootState) => boolean;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasUnsavedNotificationPreferences = exports.getNotificationLocalOverrides = exports.getEffectiveNotificationPreferences = exports.getNotificationPreferencesSaveState = void 0;
4
+ const getView = (state) => state.notificationPreferencesViewState;
5
+ const getNotificationPreferencesSaveState = (state) => getView(state).savePreferencesState;
6
+ exports.getNotificationPreferencesSaveState = getNotificationPreferencesSaveState;
7
+ const getEffectiveNotificationPreferences = (state) => {
8
+ const view = getView(state);
9
+ const mergedEventEnabledByChannel = { ...view.preferences.eventEnabledByChannel };
10
+ Object.entries(view.localOverrides.eventEnabledByChannel).forEach(([eventId, channels]) => {
11
+ mergedEventEnabledByChannel[eventId] = {
12
+ ...(view.preferences.eventEnabledByChannel[eventId] ?? {}),
13
+ ...channels,
14
+ };
15
+ });
16
+ return {
17
+ eventEnabledByChannel: mergedEventEnabledByChannel,
18
+ groupFrequencyByGroupId: {
19
+ ...view.preferences.groupFrequencyByGroupId,
20
+ ...view.localOverrides.groupFrequencyByGroupId,
21
+ },
22
+ };
23
+ };
24
+ exports.getEffectiveNotificationPreferences = getEffectiveNotificationPreferences;
25
+ const getNotificationLocalOverrides = (state) => getView(state).localOverrides;
26
+ exports.getNotificationLocalOverrides = getNotificationLocalOverrides;
27
+ const hasUnsavedNotificationPreferences = (state) => {
28
+ const overrides = getView(state).localOverrides;
29
+ return (Object.keys(overrides.eventEnabledByChannel).length > 0 ||
30
+ Object.keys(overrides.groupFrequencyByGroupId).length > 0);
31
+ };
32
+ exports.hasUnsavedNotificationPreferences = hasUnsavedNotificationPreferences;
@@ -0,0 +1,14 @@
1
+ import { FetchStateAndError } from '../../commonStateTypes/common';
2
+ import { NotificationChannel, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
3
+ export interface NotificationPreferences {
4
+ eventEnabledByChannel: Record<string, Partial<Record<NotificationChannel, boolean>>>;
5
+ groupFrequencyByGroupId: Record<string, NotificationFrequency>;
6
+ }
7
+ export interface NotificationPreferencesViewState {
8
+ cancelEpoch: number;
9
+ localOverrides: NotificationPreferences;
10
+ preferences: NotificationPreferences;
11
+ saveEpoch: number;
12
+ savePreferencesState: FetchStateAndError;
13
+ }
14
+ export declare const emptyNotificationPreferences: () => NotificationPreferences;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emptyNotificationPreferences = void 0;
4
+ const emptyNotificationPreferences = () => ({
5
+ eventEnabledByChannel: {},
6
+ groupFrequencyByGroupId: {},
7
+ });
8
+ exports.emptyNotificationPreferences = emptyNotificationPreferences;
@@ -1,8 +1,10 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
+ import { updateNotificationRegistry } from '../../../entity/notificationRegistry/notificationRegistryReducer';
2
3
  import { RootState } from '../../../reducer';
3
4
  import { ZeniAPI } from '../../../zeniAPI';
5
+ import { fetchNotificationPreferencesSuccess } from '../../notificationPreferencesView/notificationPreferencesViewReducer';
4
6
  import { fetchNotificationSettings, updateNotificationSettingsOnFailure, updateNotificationSettingsOnSuccess } from '../settingsViewReducer';
5
- export type ActionType = ReturnType<typeof fetchNotificationSettings> | ReturnType<typeof updateNotificationSettingsOnSuccess> | ReturnType<typeof updateNotificationSettingsOnFailure>;
7
+ export type ActionType = ReturnType<typeof fetchNotificationSettings> | ReturnType<typeof updateNotificationSettingsOnSuccess> | ReturnType<typeof updateNotificationSettingsOnFailure> | ReturnType<typeof updateNotificationRegistry> | ReturnType<typeof fetchNotificationPreferencesSuccess>;
6
8
  export declare const fetchNotificationSettingsEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => import("rxjs").Observable<{
7
9
  payload: undefined;
8
10
  type: "settings/fetchNotificationSettings";
@@ -15,4 +17,10 @@ export declare const fetchNotificationSettingsEpic: (actions$: ActionsObservable
15
17
  } | {
16
18
  payload: import("../../../responsePayload").ZeniAPIStatus<Record<string, unknown>>;
17
19
  type: "settings/updateNotificationSettingsOnFailure";
20
+ } | {
21
+ payload: import("../../../entity/notificationRegistry/notificationRegistryPayload").NotificationRegistryPayload;
22
+ type: "notificationRegistry/updateNotificationRegistry";
23
+ } | {
24
+ payload: import("../../notificationPreferencesView/notificationPreferencesViewReducer").FetchNotificationPreferencesSuccessPayload;
25
+ type: "notificationPreferencesView/fetchNotificationPreferencesSuccess";
18
26
  }>;
@@ -3,20 +3,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchNotificationSettingsEpic = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
+ const notificationRegistryReducer_1 = require("../../../entity/notificationRegistry/notificationRegistryReducer");
6
7
  const responsePayload_1 = require("../../../responsePayload");
8
+ const notificationPreferencesEndpoint_1 = require("../../notificationPreferencesView/notificationPreferencesEndpoint");
9
+ const notificationPreferencesViewReducer_1 = require("../../notificationPreferencesView/notificationPreferencesViewReducer");
7
10
  const settingsViewReducer_1 = require("../settingsViewReducer");
8
11
  const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(settingsViewReducer_1.fetchNotificationSettings.match), (0, operators_1.switchMap)(() => {
9
12
  const state = state$.value;
10
- const URL = `${zeniAPI.apiEndPoints.notificationMicroServiceBaseUrl}/1.0/notification-preferences`;
11
13
  const userEmail = state.tenantState.loggedInUser?.userEmail;
12
14
  if (userEmail == null) {
13
15
  return (0, rxjs_1.from)([]);
14
16
  }
15
- return zeniAPI.getJSON(URL).pipe((0, operators_1.mergeMap)((response) => {
17
+ // Snapshot the save epoch at GET-dispatch time. The reducer compares
18
+ // the captured value against the current `saveEpoch` on response and
19
+ // skips the `preferences` replace when a save has landed in the
20
+ // meantime — the GET is carrying the pre-PUT snapshot and would
21
+ // otherwise clobber the fresh save. Cursor Bugbot 3632510237.
22
+ const saveEpochAtDispatch = state.notificationPreferencesViewState.saveEpoch;
23
+ return zeniAPI
24
+ .getJSON((0, notificationPreferencesEndpoint_1.notificationPreferencesUrl)(zeniAPI))
25
+ .pipe((0, operators_1.mergeMap)((response) => {
16
26
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
17
27
  const updateActions = [
18
28
  (0, settingsViewReducer_1.updateNotificationSettingsOnSuccess)(response.data, userEmail),
19
29
  ];
30
+ if (response.data.task_checklist_registry != null) {
31
+ updateActions.push((0, notificationRegistryReducer_1.updateNotificationRegistry)(response.data.task_checklist_registry));
32
+ }
33
+ if (response.data.task_checklist_preferences != null) {
34
+ updateActions.push((0, notificationPreferencesViewReducer_1.fetchNotificationPreferencesSuccess)({
35
+ preferences: response.data.task_checklist_preferences,
36
+ saveEpochAtDispatch,
37
+ }));
38
+ }
20
39
  return (0, rxjs_1.of)(...updateActions);
21
40
  }
22
41
  else {
@@ -5,9 +5,10 @@ const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
6
  const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
7
7
  const responsePayload_1 = require("../../../responsePayload");
8
+ const notificationPreferencesEndpoint_1 = require("../../notificationPreferencesView/notificationPreferencesEndpoint");
8
9
  const settingsViewReducer_1 = require("../settingsViewReducer");
9
10
  const saveNotificationSettingsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(settingsViewReducer_1.saveNotificationSettings.match), (0, operators_1.switchMap)(() => {
10
- const URL = `${zeniAPI.apiEndPoints.notificationMicroServiceBaseUrl}/1.0/notification-preferences`;
11
+ const URL = (0, notificationPreferencesEndpoint_1.notificationPreferencesUrl)(zeniAPI);
11
12
  const { notificationSettings, notificationSettingsLocalData } = state$.value.settingsViewState.notification;
12
13
  const payload = toSaveNotificationSettingsPayload(notificationSettingsLocalData, notificationSettings);
13
14
  return zeniAPI
@@ -14,4 +14,4 @@ export declare const isMyBankConnectionsTabVisible: (currentTenant: CurrentTenan
14
14
  export declare const isPerformanceTabVisible: (userRole: UserRoleType[], loggedInUser?: LoggedInUser) => boolean;
15
15
  export declare const isRemindersSectionVisibleOnSettingsPage: (reminderNotificationSettings: ReminderNotificationSettingsData) => boolean;
16
16
  export declare const isMonthEndNotificationsSectionVisibleOnSettingsPage: (monthEndInsightsNotificationSettings: MonthEndInsightsNotificationSettingsData, signedInUseEmail: string) => boolean;
17
- export declare const isNotificationsTabVisible: (notificationSettings: NotificationSettingsData, signedInUseEmail: string, isDebitCardFlagEnabled: boolean, currentTenant: CurrentTenant | undefined) => boolean;
17
+ export declare const isNotificationsTabVisible: (notificationSettings: NotificationSettingsData, signedInUseEmail: string, isDebitCardFlagEnabled: boolean, currentTenant: CurrentTenant | undefined, hasTaskChecklistAccess: boolean) => boolean;
@@ -160,13 +160,14 @@ const isMonthEndNotificationsSectionVisibleOnSettingsPage = (monthEndInsightsNot
160
160
  monthEndInsightsNotificationSettings.recipientEmailIds.includes(signedInUseEmail));
161
161
  };
162
162
  exports.isMonthEndNotificationsSectionVisibleOnSettingsPage = isMonthEndNotificationsSectionVisibleOnSettingsPage;
163
- const isNotificationsTabVisible = (notificationSettings, signedInUseEmail, isDebitCardFlagEnabled, currentTenant) => {
163
+ const isNotificationsTabVisible = (notificationSettings, signedInUseEmail, isDebitCardFlagEnabled, currentTenant, hasTaskChecklistAccess) => {
164
164
  if (currentTenant == null) {
165
165
  return false;
166
166
  }
167
167
  const { reminderNotificationSettings, monthEndInsightsNotificationSettings } = notificationSettings;
168
168
  return (((0, exports.isRemindersSectionVisibleOnSettingsPage)(reminderNotificationSettings) ||
169
- (0, exports.isMonthEndNotificationsSectionVisibleOnSettingsPage)(monthEndInsightsNotificationSettings, signedInUseEmail)) &&
169
+ (0, exports.isMonthEndNotificationsSectionVisibleOnSettingsPage)(monthEndInsightsNotificationSettings, signedInUseEmail) ||
170
+ hasTaskChecklistAccess) &&
170
171
  ((0, tenantSelector_1.isTenantAccountingServicesEnabled)(currentTenant) ||
171
172
  ((0, tenantSelector_1.isTenantBankingOnly)(isDebitCardFlagEnabled, currentTenant) === false &&
172
173
  (0, tenantSelector_1.isTenantCardsOnly)(currentTenant) === false)));
@@ -1,9 +1,11 @@
1
1
  import { AddressPayload } from '../../entity/address/addressPayload';
2
2
  import { CompanyPayload } from '../../entity/company/companyPayload';
3
+ import { NotificationRegistryPayload } from '../../entity/notificationRegistry/notificationRegistryPayload';
3
4
  import { UserPayload } from '../../entity/user/userPayload';
4
5
  import { UserRolePayload } from '../../entity/userRole/userRolePayload';
5
6
  import { ZeniAPIResponse } from '../../responsePayload';
6
7
  import { AddressUpdatableInfoPayload } from '../addressView/addressViewPayload';
8
+ import { NotificationPreferencesPayload } from '../notificationPreferencesView/notificationPreferencesViewPayload';
7
9
  interface MyProfilePeoplePayload {
8
10
  user_roles: UserRolePayload[];
9
11
  users: UserPayload[];
@@ -36,6 +38,8 @@ export interface NotificationSettingsPayload {
36
38
  pending_reimbursement_approval_notification: RemindersPayload | null;
37
39
  uncategorized_transaction_notification: RemindersPayload | null;
38
40
  month_end_email_recipient_email_ids?: string[];
41
+ task_checklist_preferences?: NotificationPreferencesPayload;
42
+ task_checklist_registry?: NotificationRegistryPayload;
39
43
  }
40
44
  export type NotificationSettingsResponse = ZeniAPIResponse<NotificationSettingsPayload>;
41
45
  interface RefreshQBOPayload {
@@ -12,7 +12,7 @@ export interface IntegrationsSettings {
12
12
  export declare const REMINDER_NOTIFICATION_TYPES: readonly ["missingReceiptNotification", "uncategorizedTransactionNotification", "pendingBillApprovalNotification", "pendingReimbursementApprovalNotification"];
13
13
  declare const toReminderTypeKey: (v: string) => "missingReceiptNotification" | "uncategorizedTransactionNotification" | "pendingBillApprovalNotification" | "pendingReimbursementApprovalNotification";
14
14
  export type ReminderTypeKey = ReturnType<typeof toReminderTypeKey>;
15
- export declare const toRemindersFrequencyType: (v: string) => "weekly" | "monthly" | "daily" | "bi-weekly";
15
+ export declare const toRemindersFrequencyType: (v: string) => "daily" | "weekly" | "monthly" | "bi-weekly";
16
16
  export type FrequencyTypeKey = ReturnType<typeof toRemindersFrequencyType>;
17
17
  export declare const NOTIFICATION_MODES: readonly ["email", "dashboard"];
18
18
  export declare const toNotificationMode: (v: string) => "dashboard" | "email";
@@ -301,11 +301,13 @@ const toUpdateLineItemsOriginalCategoryClass = (transaction, transactionDetailLo
301
301
  const isLineIdInReplaceList = (findLineID) => lineIdsForReplace.find((lineId) => findLineID === lineId);
302
302
  const isLineIdInUpdateList = (findLineID) => lineIdsForUpdateAll.find((lineId) => findLineID === lineId);
303
303
  const newLineItems = {};
304
+ const persistedLineIds = new Set();
304
305
  const checkIfNotJEAndDeposit = (0, vendorTransaction_1.isVendorTransaction)(transaction) === true ||
305
306
  (0, customerTransaction_1.isCustomerTransaction)(transaction) === true;
306
307
  if (transaction.lines != null) {
307
308
  // always preserving the order of the line items. hence iterating over this
308
309
  transaction.lines.forEach((line) => {
310
+ persistedLineIds.add(line.id);
309
311
  if (line.type === 'transaction_with_account_and_class_line' ||
310
312
  line.type === 'transaction_with_product_or_service_line' ||
311
313
  line.type === 'journal_entry_transaction_line') {
@@ -436,6 +438,7 @@ const toUpdateLineItemsOriginalCategoryClass = (transaction, transactionDetailLo
436
438
  }
437
439
  });
438
440
  }
441
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
439
442
  const newLocalData = {
440
443
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
441
444
  memo: transactionDetailLocalData.memo,
@@ -457,11 +460,13 @@ const toUpdateLineItemsOriginalCategoryClass = (transaction, transactionDetailLo
457
460
  exports.toUpdateLineItemsOriginalCategoryClass = toUpdateLineItemsOriginalCategoryClass;
458
461
  const toSetAllLineItemsToCategoryClass = (transaction, transactionDetailLocalData, isUpdateAll, lineEntity, chosenAccount, chosenClass) => {
459
462
  const newLineItems = {};
463
+ const persistedLineIds = new Set();
460
464
  const updateAllLines = (0, vendorTransaction_1.isVendorTransaction)(transaction) === true ||
461
465
  (0, customerTransaction_1.isCustomerTransaction)(transaction) === true;
462
466
  if (transaction.lines != null) {
463
467
  // always preserving the order of the line items. hence iterating over this
464
468
  transaction.lines.forEach((line) => {
469
+ persistedLineIds.add(line.id);
465
470
  if (line.type === 'transaction_with_account_and_class_line' ||
466
471
  line.type === 'transaction_with_product_or_service_line' ||
467
472
  line.type === 'journal_entry_transaction_line') {
@@ -542,6 +547,7 @@ const toSetAllLineItemsToCategoryClass = (transaction, transactionDetailLocalDat
542
547
  }
543
548
  });
544
549
  }
550
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
545
551
  const newLocalData = {
546
552
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
547
553
  memo: transactionDetailLocalData.memo,
@@ -563,11 +569,13 @@ const toSetAllLineItemsToCategoryClass = (transaction, transactionDetailLocalDat
563
569
  exports.toSetAllLineItemsToCategoryClass = toSetAllLineItemsToCategoryClass;
564
570
  const toSetAllLineItemsLocalDataIsUpdateAllChecked = (transaction, transactionDetailLocalData, isUpdateAll, entity) => {
565
571
  const newLineItems = {};
572
+ const persistedLineIds = new Set();
566
573
  const updateAllLines = (0, vendorTransaction_1.isVendorTransaction)(transaction) === true ||
567
574
  (0, customerTransaction_1.isCustomerTransaction)(transaction) === true;
568
575
  if (transaction.lines != null) {
569
576
  // always preserving the order of the line items. hence iterating over this
570
577
  transaction.lines.forEach((line) => {
578
+ persistedLineIds.add(line.id);
571
579
  const lineId = line.id;
572
580
  if (line.type === 'transaction_with_account_and_class_line' ||
573
581
  line.type === 'transaction_with_product_or_service_line' ||
@@ -589,6 +597,7 @@ const toSetAllLineItemsLocalDataIsUpdateAllChecked = (transaction, transactionDe
589
597
  }
590
598
  });
591
599
  }
600
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
592
601
  const newLocalData = {
593
602
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
594
603
  memo: transactionDetailLocalData.memo,
@@ -608,14 +617,23 @@ const toSetAllLineItemsLocalDataIsUpdateAllChecked = (transaction, transactionDe
608
617
  return newLocalData;
609
618
  };
610
619
  exports.toSetAllLineItemsLocalDataIsUpdateAllChecked = toSetAllLineItemsLocalDataIsUpdateAllChecked;
620
+ const preserveLocalLines = (target, source, persistedLineIds) => {
621
+ Object.keys(source).forEach((lineId) => {
622
+ if (!persistedLineIds.has(lineId)) {
623
+ target[lineId] = { ...source[lineId] };
624
+ }
625
+ });
626
+ };
611
627
  const setEntityRecommendationForLineIdInLocalData = (transaction, transactionDetailLocalData, entity, recommendationLineIds, uncategorizedIncomeExpense, recommendationWithCOTByLineId) => {
612
628
  const newLineItems = {};
613
629
  let matchingEntityLineId = undefined;
614
630
  const checkIfNotJEAndDeposit = (0, vendorTransaction_1.isVendorTransaction)(transaction) === true ||
615
631
  (0, customerTransaction_1.isCustomerTransaction)(transaction) === true;
632
+ const persistedLineIds = new Set();
616
633
  if (transaction.lines != null) {
617
634
  // always preserving the order of the line items. hence iterating over this
618
635
  transaction.lines.forEach((line) => {
636
+ persistedLineIds.add(line.id);
619
637
  const lineId = line.id;
620
638
  const oldRecord = transactionDetailLocalData.lineItemById[lineId];
621
639
  // check if any line already has this entity
@@ -727,6 +745,7 @@ const setEntityRecommendationForLineIdInLocalData = (transaction, transactionDet
727
745
  }
728
746
  });
729
747
  }
748
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
730
749
  // if we have found a matching entity
731
750
  if (matchingEntityLineId != null) {
732
751
  const matchingRecord = transactionDetailLocalData.lineItemById[matchingEntityLineId];
@@ -771,9 +790,11 @@ const setEntityRecommendationForLineIdInLocalData = (transaction, transactionDet
771
790
  exports.setEntityRecommendationForLineIdInLocalData = setEntityRecommendationForLineIdInLocalData;
772
791
  const removeEntityRecommendationForLineIdInLocalData = (transaction, transactionDetailLocalData, recommendationLineIds) => {
773
792
  const newLineItems = {};
793
+ const persistedLineIds = new Set();
774
794
  if (transaction.lines != null) {
775
795
  // always preserving the order of the line items. hence iterating over this
776
796
  transaction.lines.forEach((line) => {
797
+ persistedLineIds.add(line.id);
777
798
  const lineId = line.id;
778
799
  const oldRecord = transactionDetailLocalData.lineItemById[lineId];
779
800
  // check if any line already has this entity
@@ -804,6 +825,7 @@ const removeEntityRecommendationForLineIdInLocalData = (transaction, transaction
804
825
  }
805
826
  });
806
827
  }
828
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
807
829
  const newLocalData = {
808
830
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
809
831
  memo: transactionDetailLocalData.memo,
@@ -871,9 +893,11 @@ const toLineCustomerOrVendor = (entity) => {
871
893
  };
872
894
  const toSetAllLineItemsToVendor = (transaction, transactionDetailLocalData, entity) => {
873
895
  const newLineItems = {};
896
+ const persistedLineIds = new Set();
874
897
  if (transaction.lines != null) {
875
898
  // always preserving the order of the line items. hence iterating over this
876
899
  transaction.lines.forEach((line) => {
900
+ persistedLineIds.add(line.id);
877
901
  if (line.type === 'transaction_with_account_and_class_line' ||
878
902
  line.type === 'transaction_with_product_or_service_line' ||
879
903
  line.type === 'journal_entry_transaction_line') {
@@ -918,6 +942,7 @@ const toSetAllLineItemsToVendor = (transaction, transactionDetailLocalData, enti
918
942
  }
919
943
  });
920
944
  }
945
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
921
946
  const newLocalData = {
922
947
  ...transactionDetailLocalData,
923
948
  lineItemById: newLineItems,
@@ -929,9 +954,11 @@ exports.toSetAllLineItemsToVendor = toSetAllLineItemsToVendor;
929
954
  const toUpdateLineItemsToOriginalVendor = (transaction, transactionDetailLocalData, lineIdToRetainVendorSelection) => {
930
955
  const newLineItems = {};
931
956
  const transactionWithCOT = (0, transactionHelper_1.getTransactionWithCOT)(transaction);
957
+ const persistedLineIds = new Set();
932
958
  if (transaction.lines != null) {
933
959
  // always preserving the order of the line items. hence iterating over this
934
960
  transaction.lines.forEach((line) => {
961
+ persistedLineIds.add(line.id);
935
962
  if (line.type === 'transaction_with_account_and_class_line' ||
936
963
  line.type === 'transaction_with_product_or_service_line' ||
937
964
  line.type === 'journal_entry_transaction_line') {
@@ -1032,6 +1059,7 @@ const toUpdateLineItemsToOriginalVendor = (transaction, transactionDetailLocalDa
1032
1059
  }
1033
1060
  });
1034
1061
  }
1062
+ preserveLocalLines(newLineItems, transactionDetailLocalData.lineItemById, persistedLineIds);
1035
1063
  const newLocalData = {
1036
1064
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
1037
1065
  memo: transactionDetailLocalData.memo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.55",
3
+ "version": "5.1.57-betaDI0",
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",