@mx-cartographer/experiences 9.4.64 → 9.4.66

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 (49) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/core/stores/InsightsV2Store.d.ts +19 -0
  3. package/dist/core/stores/InsightsV2Store.es.js +31 -14
  4. package/dist/core/stores/InsightsV2Store.es.js.map +1 -1
  5. package/dist/core/types/Beat.d.ts +2 -0
  6. package/dist/core/types/Beat.es.js.map +1 -1
  7. package/dist/core/types/Insights.d.ts +16 -0
  8. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/RecurringTransactionBlock.d.ts +17 -0
  9. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/RecurringTransactionBlock.es.js +39 -0
  10. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/RecurringTransactionBlock.es.js.map +1 -0
  11. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/index.d.ts +2 -0
  12. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/subscriptionBeats.d.ts +19 -0
  13. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/subscriptionBeats.es.js +12 -0
  14. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/subscriptionBeats.es.js.map +1 -0
  15. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionBlock.d.ts +23 -0
  16. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionBlock.es.js +21 -0
  17. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionBlock.es.js.map +1 -0
  18. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionPrepare.d.ts +25 -0
  19. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionPrepare.es.js +24 -0
  20. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionPrepare.es.js.map +1 -0
  21. package/dist/insights-v2/beatActions.d.ts +11 -0
  22. package/dist/insights-v2/beatActions.es.js.map +1 -1
  23. package/dist/insights-v2/components/BaseLayout.es.js +84 -80
  24. package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
  25. package/dist/insights-v2/ctaActions/ctaConfig.d.ts +7 -0
  26. package/dist/insights-v2/ctaActions/ctaConfig.es.js +10 -0
  27. package/dist/insights-v2/ctaActions/ctaConfig.es.js.map +1 -0
  28. package/dist/insights-v2/ctaActions/ctaHandlers.d.ts +44 -0
  29. package/dist/insights-v2/ctaActions/ctaHandlers.es.js +107 -0
  30. package/dist/insights-v2/ctaActions/ctaHandlers.es.js.map +1 -0
  31. package/dist/insights-v2/ctaActions/hostActions.d.ts +17 -0
  32. package/dist/insights-v2/ctaActions/hostActions.es.js +16 -0
  33. package/dist/insights-v2/ctaActions/hostActions.es.js.map +1 -0
  34. package/dist/insights-v2/ctaActions/index.d.ts +6 -0
  35. package/dist/insights-v2/ctaActions/runCtaAction.d.ts +12 -0
  36. package/dist/insights-v2/ctaActions/runCtaAction.es.js +79 -0
  37. package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -0
  38. package/dist/insights-v2/ctaActions/types.d.ts +57 -0
  39. package/dist/insights-v2/ctaActions/useCtaAction.d.ts +19 -0
  40. package/dist/insights-v2/ctaActions/useCtaAction.es.js +44 -0
  41. package/dist/insights-v2/ctaActions/useCtaAction.es.js.map +1 -0
  42. package/dist/insights-v2/index.d.ts +3 -1
  43. package/dist/insights-v2/index.es.js +59 -54
  44. package/dist/insights-v2/mappings/actionBlocksKey.d.ts +1 -0
  45. package/dist/insights-v2/mappings/actionBlocksKey.es.js +2 -1
  46. package/dist/insights-v2/mappings/actionBlocksKey.es.js.map +1 -1
  47. package/dist/insights-v2/mappings/blocks.es.js +52 -45
  48. package/dist/insights-v2/mappings/blocks.es.js.map +1 -1
  49. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [9.4.66] - 10-08-2026
2
+
3
+ - **ADDED** - `recurring_transaction_block` (Recurring Transaction) action block: opens the Expense Details drawer for one recurring subscription, shared by the Upcoming Subscription and Subscription Price Increase beats. The drawer content is the recurring module's `RecurringTransactionDetails` (merchant, amount, next due date, schedule, account/category, past activity, mark as paid/unpaid, delete), so the block only resolves *which* subscription and hands off, as `TransactionDetailsBlock` does. The subscription is `primary_repeating_transaction_guid` (V2 payload, then the legacy beat envelope), else the beat's primary transaction is walked to the repeating transaction it is linked to. `usePrepare` loads recurring data on open through `loadRepeatingTransactionData`; when nothing resolves the drawer shows `recurring.no_activities`, never the generic error; when the open-time load fails it shows the feed's `ErrorCard` with a Try Again retry instead of an endless loader. The shared transaction filter is restored on close only if the details view actually narrowed it. (WEC-1428)
4
+ - **ADDED** - `InsightsPayload.primary_repeating_transaction_guid` and `Beat.primary_repeating_transaction_guid` (legacy pulse envelope). (WEC-1428)
5
+ - **ADDED** - Mock subscription beats (`mock/BlockBeatsData/RecurringTransactionBeats.ts`, pointing at the mock Netflix subscription), `InsightsV2/ActionBlock/RecurringTransactionBlock` stories, and `docs/action-blocks/` (index + block doc). (WEC-1428)
6
+
7
+ ## [9.4.65] - 10-08-2026
8
+
9
+ - **ADDED** - Insights V2 CTA action layer (`src/insights-v2/ctaActions`): a card CTA whose backend-named `action_key` (ditto `*_cta_action`, passed through by tupac on `primary_cta` / `secondary_cta`) has a registered step runs that step first: a typed store handler, `onSuccess` effects (`refreshBeat`), a client-only disable (`disabledWhen`), then its drawer or message. No key, or an unknown key, keeps the default (drawer if the CTA has a block, else post). First key: `dismiss_beat` (ConnectDiscoveredAccounts). Per-beat `ctaStatus` and `beatContext` on `InsightsV2Store`; `useBeatCtaActions` for the card and `useCtaHandler` for saves inside a drawer (WEC-1852)
10
+ - **ADDED** - `BeatActions.onCtaAction` so host-owned effects (`HOST_ACTIONS`: `markCtaClicked`, `dismissBeat`, `updateBeat`, `readBeatSummary`, `sendBeatFeedback`, `disableBeatTemplate`, spending plan items) are delegated to the embedding app (WEC-1852)
11
+ - **CHANGED** - `BaseLayout` delegates CTA clicks to the runner, disables CTAs while a handler is pending, and records the first primary click (`cta_clicked_at`) through the host (WEC-1852)
12
+ - **CHANGED** - `BlockType` gains `account_switch_block` and `save_extra_block`, which were already registered blocks (WEC-1852)
13
+
1
14
  ## [9.4.64] - 10-08-2026
2
15
 
3
16
  - **ADDED** - `SupportPrompt` help component: a "Need more assistance?" text button that reveals an "Open a Case" button on click, used on every clickable Request Support category card and at the end of every help article. Only the reveal callers supply the prompt copy, so the prompt never renders empty. Clicking "Open a Case" opens the contact form with the category or article title as the ticket title. `RequestSupport` accepts a `caseTopic` prop; from an article the panel is not rendered at all and the contact form is the whole surface (WEC-1783)
@@ -26,6 +26,8 @@ interface OpenActionDrawerArgs {
26
26
  trigger: ActionDrawerTrigger;
27
27
  context?: unknown;
28
28
  }
29
+ /** Lifecycle of a beat's CTA action (see insights-v2/ctaActions), keyed by beat guid. */
30
+ export type CtaStatus = 'pending' | 'done' | 'error';
29
31
  /**
30
32
  * UI state shared across the insights-v2 widget tree. Owns the action drawer so
31
33
  * cards, charts, and action blocks coordinate through the store instead of prop
@@ -35,7 +37,24 @@ interface OpenActionDrawerArgs {
35
37
  export declare class InsightsV2Store {
36
38
  globalStore: GlobalStore;
37
39
  actionDrawer: ActionDrawerState;
40
+ /**
41
+ * CTA action status per beat guid. Keyed by guid (not block) so a block shared by many
42
+ * insights never reflects another card's pending or error state.
43
+ */
44
+ ctaStatus: Record<string, CtaStatus>;
45
+ /**
46
+ * Per-beat scratch input collected on the card or in its blocks (e.g. the account a user
47
+ * picked before pressing Save), keyed by beat guid. Unlike `actionDrawer.context`, which is
48
+ * replaced whenever any drawer session starts, this survives other cards' drawers opening.
49
+ * CTA steps read it via `disabledWhen` and their `params` callback (`beatContextOf`). Ships
50
+ * ahead of its first block writer (Designate's account picker) on purpose: it is the layer's
51
+ * only way to keep a pick alive across other cards' drawer sessions.
52
+ */
53
+ beatContext: Record<string, Record<string, unknown>>;
38
54
  constructor(globalStore: GlobalStore);
55
+ setCtaStatus: (guid: string, status: CtaStatus) => void;
56
+ /** Merges `values` into the beat's scratch input. */
57
+ setBeatContext: (guid: string, values: Record<string, unknown>) => void;
39
58
  /**
40
59
  * Merges keys into the current drawer session's context bag — it does NOT
41
60
  * replace it. A nested drawer (e.g. a row click inside TransactionHistoryBlock
@@ -1,36 +1,53 @@
1
- import { makeAutoObservable as a } from "mobx";
1
+ import { makeAutoObservable as r } from "mobx";
2
2
  var o = {
3
3
  isDrawerOpen: !1,
4
4
  trigger: "primaryCta",
5
5
  guid: "",
6
6
  context: void 0
7
- }, n = class {
7
+ }, s = class {
8
8
  globalStore;
9
9
  actionDrawer = o;
10
- constructor(r) {
11
- this.globalStore = r, a(this);
10
+ ctaStatus = {};
11
+ beatContext = {};
12
+ constructor(t) {
13
+ this.globalStore = t, r(this);
12
14
  }
13
- setActionContext = (r) => {
15
+ setCtaStatus = (t, e) => {
16
+ this.ctaStatus = {
17
+ ...this.ctaStatus,
18
+ [t]: e
19
+ };
20
+ };
21
+ setBeatContext = (t, e) => {
22
+ this.beatContext = {
23
+ ...this.beatContext,
24
+ [t]: {
25
+ ...this.beatContext[t],
26
+ ...e
27
+ }
28
+ };
29
+ };
30
+ setActionContext = (t) => {
14
31
  this.actionDrawer = {
15
32
  ...this.actionDrawer,
16
33
  context: {
17
34
  ...this.actionDrawer.context,
18
- ...r
35
+ ...t
19
36
  }
20
37
  };
21
38
  };
22
- openActionDrawer = ({ guid: r, trigger: t, context: e }) => {
39
+ openActionDrawer = ({ guid: t, trigger: e, context: a }) => {
23
40
  this.actionDrawer = {
24
41
  isDrawerOpen: !0,
25
- trigger: t,
26
- guid: r,
27
- context: e
42
+ trigger: e,
43
+ guid: t,
44
+ context: a
28
45
  };
29
46
  };
30
- openCtaDrawer = (r, t) => {
47
+ openCtaDrawer = (t, e) => {
31
48
  this.openActionDrawer({
32
- guid: r,
33
- trigger: t
49
+ guid: t,
50
+ trigger: e
34
51
  });
35
52
  };
36
53
  closeActionDrawer = () => {
@@ -43,7 +60,7 @@ var o = {
43
60
  };
44
61
  export {
45
62
  o as CLOSED_ACTION_DRAWER,
46
- n as InsightsV2Store
63
+ s as InsightsV2Store
47
64
  };
48
65
 
49
66
  //# sourceMappingURL=InsightsV2Store.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InsightsV2Store.es.js","names":[],"sources":["../../../src/core/stores/InsightsV2Store.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx'\n\nimport type { GlobalStore } from './GlobalStore'\n\nexport type ActionDrawerTrigger = 'primaryCta' | 'secondaryCta' | 'chartBar'\n\nexport interface ActionDrawerState {\n isDrawerOpen: boolean\n trigger: ActionDrawerTrigger\n guid: string\n /**\n * Block-specific drilldown data for the current drawer SESSION, opaque to the\n * store. Each action block defines and reads its own shape (via\n * useActionContext<T>()). Chart drilldowns put `{ chartBarIndex, tabIndex }`\n * here; CTAs carry none.\n *\n * This is an accumulating bag, not a single owner's value: a nested drawer\n * (e.g. TransactionDetailsBlock opened from a row inside TransactionHistory\n * Block) adds its own key via `setActionContext` ON TOP of whatever the outer\n * drawer already put here, so the outer block's own context survives while the\n * nested block is open. It resets to a clean slate only when a NEW drawer\n * session starts (`openActionDrawer`/`openCtaDrawer`, which fully replace the\n * envelope) — not on every `setActionContext` call.\n */\n context: unknown\n}\n\nexport const CLOSED_ACTION_DRAWER: ActionDrawerState = {\n isDrawerOpen: false,\n trigger: 'primaryCta',\n guid: '',\n context: undefined,\n}\n\ninterface OpenActionDrawerArgs {\n guid: string\n trigger: ActionDrawerTrigger\n context?: unknown\n}\n\n/**\n * UI state shared across the insights-v2 widget tree. Owns the action drawer so\n * cards, charts, and action blocks coordinate through the store instead of prop\n * drilling. The drawer state is a fixed envelope (isDrawerOpen/guid/trigger) plus\n * an opaque `context` bag, so per-block drilldown data never leaks into this shape.\n */\nexport class InsightsV2Store {\n globalStore: GlobalStore\n\n actionDrawer: ActionDrawerState = CLOSED_ACTION_DRAWER\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n\n makeAutoObservable(this)\n }\n\n /**\n * Merges keys into the current drawer session's context bag — it does NOT\n * replace it. A nested drawer (e.g. a row click inside TransactionHistoryBlock\n * opening TransactionDetailsBlock) calls this to layer its own key (e.g.\n * `transaction_guid`) on top of whatever context the outer drawer already set\n * (e.g. `chartBarIndex`), so the outer block keeps reading its own keys via\n * useActionContext while the nested block is open. The bag only returns to a\n * clean slate when a new drawer session starts via `openActionDrawer`/\n * `openCtaDrawer`, which fully replace the envelope.\n */\n setActionContext = (context: Record<string, unknown>) => {\n this.actionDrawer = {\n ...this.actionDrawer,\n context: {\n ...(this.actionDrawer.context as Record<string, unknown> | undefined),\n ...context,\n },\n }\n }\n\n /**\n * Generic drawer opener — the only opener a new action block needs. Pass the\n * beat guid, the CTA/source that triggered it, and (optionally) a block-specific\n * context object the block reads back via useActionContext.\n */\n openActionDrawer = ({ guid, trigger, context }: OpenActionDrawerArgs) => {\n this.actionDrawer = { isDrawerOpen: true, trigger, guid, context }\n }\n\n /** Convenience opener for CTA drawers (no drilldown context). */\n openCtaDrawer = (guid: string, trigger: 'primaryCta' | 'secondaryCta') => {\n this.openActionDrawer({ guid, trigger })\n }\n\n closeActionDrawer = () => {\n // Preserve `trigger` + `context` so the drawer keeps its variant-specific\n // title/content through the slide-out animation (ActionBlockLayer stays\n // mounted while closing). Only flip isDrawerOpen off and clear the beat guid.\n this.actionDrawer = { ...this.actionDrawer, isDrawerOpen: false, guid: '' }\n }\n}\n"],"mappings":";AA2BA,IAAa,IAA0C;AAAA,EACrD,cAAc;AAAA,EACd,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AACX,GAca,IAAb,MAA6B;AAAA,EAC3B;AAAA,EAEA,eAAkC;AAAA,EAElC,YAAY,GAA0B;AACpC,SAAK,cAAc,GAEnB,EAAmB,IAAI;AAAA,EACzB;AAAA,EAYA,mBAAA,CAAoB,MAAqC;AACvD,SAAK,eAAe;AAAA,MAClB,GAAG,KAAK;AAAA,MACR,SAAS;AAAA,QACP,GAAI,KAAK,aAAa;AAAA,QACtB,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAAA,EAOA,mBAAA,CAAoB,EAAE,MAAA,GAAM,SAAA,GAAS,SAAA,EAAA,MAAoC;AACvE,SAAK,eAAe;AAAA,MAAE,cAAc;AAAA,MAAM,SAAA;AAAA,MAAS,MAAA;AAAA,MAAM,SAAA;AAAA,IAAQ;AAAA,EACnE;AAAA,EAGA,gBAAA,CAAiB,GAAc,MAA2C;AACxE,SAAK,iBAAiB;AAAA,MAAE,MAAA;AAAA,MAAM,SAAA;AAAA,IAAQ,CAAC;AAAA,EACzC;AAAA,EAEA,oBAAA,MAA0B;AAIxB,SAAK,eAAe;AAAA,MAAE,GAAG,KAAK;AAAA,MAAc,cAAc;AAAA,MAAO,MAAM;AAAA,IAAG;AAAA,EAC5E;AACF"}
1
+ {"version":3,"file":"InsightsV2Store.es.js","names":[],"sources":["../../../src/core/stores/InsightsV2Store.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx'\n\nimport type { GlobalStore } from './GlobalStore'\n\nexport type ActionDrawerTrigger = 'primaryCta' | 'secondaryCta' | 'chartBar'\n\nexport interface ActionDrawerState {\n isDrawerOpen: boolean\n trigger: ActionDrawerTrigger\n guid: string\n /**\n * Block-specific drilldown data for the current drawer SESSION, opaque to the\n * store. Each action block defines and reads its own shape (via\n * useActionContext<T>()). Chart drilldowns put `{ chartBarIndex, tabIndex }`\n * here; CTAs carry none.\n *\n * This is an accumulating bag, not a single owner's value: a nested drawer\n * (e.g. TransactionDetailsBlock opened from a row inside TransactionHistory\n * Block) adds its own key via `setActionContext` ON TOP of whatever the outer\n * drawer already put here, so the outer block's own context survives while the\n * nested block is open. It resets to a clean slate only when a NEW drawer\n * session starts (`openActionDrawer`/`openCtaDrawer`, which fully replace the\n * envelope) — not on every `setActionContext` call.\n */\n context: unknown\n}\n\nexport const CLOSED_ACTION_DRAWER: ActionDrawerState = {\n isDrawerOpen: false,\n trigger: 'primaryCta',\n guid: '',\n context: undefined,\n}\n\ninterface OpenActionDrawerArgs {\n guid: string\n trigger: ActionDrawerTrigger\n context?: unknown\n}\n\n/** Lifecycle of a beat's CTA action (see insights-v2/ctaActions), keyed by beat guid. */\nexport type CtaStatus = 'pending' | 'done' | 'error'\n\n/**\n * UI state shared across the insights-v2 widget tree. Owns the action drawer so\n * cards, charts, and action blocks coordinate through the store instead of prop\n * drilling. The drawer state is a fixed envelope (isDrawerOpen/guid/trigger) plus\n * an opaque `context` bag, so per-block drilldown data never leaks into this shape.\n */\nexport class InsightsV2Store {\n globalStore: GlobalStore\n\n actionDrawer: ActionDrawerState = CLOSED_ACTION_DRAWER\n\n /**\n * CTA action status per beat guid. Keyed by guid (not block) so a block shared by many\n * insights never reflects another card's pending or error state.\n */\n ctaStatus: Record<string, CtaStatus> = {}\n\n /**\n * Per-beat scratch input collected on the card or in its blocks (e.g. the account a user\n * picked before pressing Save), keyed by beat guid. Unlike `actionDrawer.context`, which is\n * replaced whenever any drawer session starts, this survives other cards' drawers opening.\n * CTA steps read it via `disabledWhen` and their `params` callback (`beatContextOf`). Ships\n * ahead of its first block writer (Designate's account picker) on purpose: it is the layer's\n * only way to keep a pick alive across other cards' drawer sessions.\n */\n beatContext: Record<string, Record<string, unknown>> = {}\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n\n makeAutoObservable(this)\n }\n\n setCtaStatus = (guid: string, status: CtaStatus) => {\n this.ctaStatus = { ...this.ctaStatus, [guid]: status }\n }\n\n /** Merges `values` into the beat's scratch input. */\n setBeatContext = (guid: string, values: Record<string, unknown>) => {\n this.beatContext = { ...this.beatContext, [guid]: { ...this.beatContext[guid], ...values } }\n }\n\n /**\n * Merges keys into the current drawer session's context bag — it does NOT\n * replace it. A nested drawer (e.g. a row click inside TransactionHistoryBlock\n * opening TransactionDetailsBlock) calls this to layer its own key (e.g.\n * `transaction_guid`) on top of whatever context the outer drawer already set\n * (e.g. `chartBarIndex`), so the outer block keeps reading its own keys via\n * useActionContext while the nested block is open. The bag only returns to a\n * clean slate when a new drawer session starts via `openActionDrawer`/\n * `openCtaDrawer`, which fully replace the envelope.\n */\n setActionContext = (context: Record<string, unknown>) => {\n this.actionDrawer = {\n ...this.actionDrawer,\n context: {\n ...(this.actionDrawer.context as Record<string, unknown> | undefined),\n ...context,\n },\n }\n }\n\n /**\n * Generic drawer opener — the only opener a new action block needs. Pass the\n * beat guid, the CTA/source that triggered it, and (optionally) a block-specific\n * context object the block reads back via useActionContext.\n */\n openActionDrawer = ({ guid, trigger, context }: OpenActionDrawerArgs) => {\n this.actionDrawer = { isDrawerOpen: true, trigger, guid, context }\n }\n\n /** Convenience opener for CTA drawers (no drilldown context). */\n openCtaDrawer = (guid: string, trigger: 'primaryCta' | 'secondaryCta') => {\n this.openActionDrawer({ guid, trigger })\n }\n\n closeActionDrawer = () => {\n // Preserve `trigger` + `context` so the drawer keeps its variant-specific\n // title/content through the slide-out animation (ActionBlockLayer stays\n // mounted while closing). Only flip isDrawerOpen off and clear the beat guid.\n this.actionDrawer = { ...this.actionDrawer, isDrawerOpen: false, guid: '' }\n }\n}\n"],"mappings":";AA2BA,IAAa,IAA0C;AAAA,EACrD,cAAc;AAAA,EACd,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AACX,GAiBa,IAAb,MAA6B;AAAA,EAC3B;AAAA,EAEA,eAAkC;AAAA,EAMlC,YAAuC,CAAC;AAAA,EAUxC,cAAuD,CAAC;AAAA,EAExD,YAAY,GAA0B;AACpC,SAAK,cAAc,GAEnB,EAAmB,IAAI;AAAA,EACzB;AAAA,EAEA,eAAA,CAAgB,GAAc,MAAsB;AAClD,SAAK,YAAY;AAAA,MAAE,GAAG,KAAK;AAAA,MAAY,CAAA,CAAA,GAAO;AAAA,IAAO;AAAA,EACvD;AAAA,EAGA,iBAAA,CAAkB,GAAc,MAAoC;AAClE,SAAK,cAAc;AAAA,MAAE,GAAG,KAAK;AAAA,MAAc,CAAA,CAAA,GAAO;AAAA,QAAE,GAAG,KAAK,YAAY,CAAA;AAAA,QAAO,GAAG;AAAA,MAAO;AAAA,IAAE;AAAA,EAC7F;AAAA,EAYA,mBAAA,CAAoB,MAAqC;AACvD,SAAK,eAAe;AAAA,MAClB,GAAG,KAAK;AAAA,MACR,SAAS;AAAA,QACP,GAAI,KAAK,aAAa;AAAA,QACtB,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAAA,EAOA,mBAAA,CAAoB,EAAE,MAAA,GAAM,SAAA,GAAS,SAAA,EAAA,MAAoC;AACvE,SAAK,eAAe;AAAA,MAAE,cAAc;AAAA,MAAM,SAAA;AAAA,MAAS,MAAA;AAAA,MAAM,SAAA;AAAA,IAAQ;AAAA,EACnE;AAAA,EAGA,gBAAA,CAAiB,GAAc,MAA2C;AACxE,SAAK,iBAAiB;AAAA,MAAE,MAAA;AAAA,MAAM,SAAA;AAAA,IAAQ,CAAC;AAAA,EACzC;AAAA,EAEA,oBAAA,MAA0B;AAIxB,SAAK,eAAe;AAAA,MAAE,GAAG,KAAK;AAAA,MAAc,cAAc;AAAA,MAAO,MAAM;AAAA,IAAG;AAAA,EAC5E;AACF"}
@@ -42,6 +42,8 @@ export interface Beat {
42
42
  primary_category_guid?: string | null;
43
43
  primary_goal_guid?: string | null;
44
44
  primary_merchant_guid?: string | null;
45
+ /** Legacy pulse envelope: the subscription an Upcoming Subscription beat is about. */
46
+ primary_repeating_transaction_guid?: string | null;
45
47
  primary_scheduled_payment_guid?: string | null;
46
48
  primary_transaction?: Transaction;
47
49
  primary_transaction_guid?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"Beat.es.js","names":[],"sources":["../../../src/core/types/Beat.ts"],"sourcesContent":["import { Account } from './Account'\nimport { InsightsPayload } from './Insights'\nimport { Transaction } from './Transaction'\n\nexport interface Beat {\n account_guids: string[] | []\n active_at?: string | number\n associated_transaction_guid?: string | null\n call_to_action?: string | null\n category_guids?: string[] | []\n created_at?: string | number\n cta_clicked_at?: number | null\n data_series: DataSeries\n description?: string\n digest?: string\n dismissed_at?: number | null\n displayed_at?: string | null\n errors?: any\n guid: string\n has_been_displayed: boolean\n html_description?: string\n html_in_description_cta?: string\n html_micro_call_to_action?: string\n html_micro_description?: string\n html_micro_title?: string\n html_short_title?: string\n html_title: string\n icon_color?: string | null\n icon_type?: number | null\n in_description_cta?: string | null\n is_dismissed: boolean\n is_obsolete: boolean\n is_refreshable: boolean\n is_sampled: boolean\n merchant_guids: string[] | []\n metadata?: string | null\n micro_call_to_action?: string | null\n micro_description?: string | null\n micro_title?: string | null\n payload: any\n primary_account?: Account\n primary_account_guid?: string | null\n primary_category_guid?: string | null\n primary_goal_guid?: string | null\n primary_merchant_guid?: string | null\n primary_scheduled_payment_guid?: string | null\n primary_transaction?: Transaction\n primary_transaction_guid?: string | null\n power_action_completed_at?: string | null\n power_action_has_been_completed?: boolean\n scheduled_payment_guids: string[] | []\n short_title?: string | null\n supporting_action?: string | null\n template: string\n title: string\n transaction_guids: string[] | []\n transactions?: Transaction[]\n user_guid: string\n user_verification: UserVerification\n\n // Added for UI: optional institution info for displaying logos\n institutionGuid?: string | null\n institutionName?: string | null\n\n insights_payload?: InsightsPayload\n /**\n * Written by firefly, never by a template: true when the insight's Insights 2.0 flag is on (or\n * the envelope is present). It gates which renderer draws the beat — see `resolveInsightRender`.\n * Absent on an older firefly or on cortex, which renders the legacy path unchanged.\n */\n is_insights_v2?: boolean\n}\n\nexport interface DataSeries {\n names?: string[]\n types?: string[]\n data?: any[]\n}\n\nexport enum UserVerification {\n UNKNOWN = 0,\n CORRECT = 1,\n INCORRECT = 2,\n}\n"],"mappings":"AA+EA,IAAY,IAAL,0BAAA,GAAA;AACL,SAAA,EAAA,EAAA,UAAA,CAAA,IAAA,WACA,EAAA,EAAA,UAAA,CAAA,IAAA,WACA,EAAA,EAAA,YAAA,CAAA,IAAA;AACF,GAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"Beat.es.js","names":[],"sources":["../../../src/core/types/Beat.ts"],"sourcesContent":["import { Account } from './Account'\nimport { InsightsPayload } from './Insights'\nimport { Transaction } from './Transaction'\n\nexport interface Beat {\n account_guids: string[] | []\n active_at?: string | number\n associated_transaction_guid?: string | null\n call_to_action?: string | null\n category_guids?: string[] | []\n created_at?: string | number\n cta_clicked_at?: number | null\n data_series: DataSeries\n description?: string\n digest?: string\n dismissed_at?: number | null\n displayed_at?: string | null\n errors?: any\n guid: string\n has_been_displayed: boolean\n html_description?: string\n html_in_description_cta?: string\n html_micro_call_to_action?: string\n html_micro_description?: string\n html_micro_title?: string\n html_short_title?: string\n html_title: string\n icon_color?: string | null\n icon_type?: number | null\n in_description_cta?: string | null\n is_dismissed: boolean\n is_obsolete: boolean\n is_refreshable: boolean\n is_sampled: boolean\n merchant_guids: string[] | []\n metadata?: string | null\n micro_call_to_action?: string | null\n micro_description?: string | null\n micro_title?: string | null\n payload: any\n primary_account?: Account\n primary_account_guid?: string | null\n primary_category_guid?: string | null\n primary_goal_guid?: string | null\n primary_merchant_guid?: string | null\n /** Legacy pulse envelope: the subscription an Upcoming Subscription beat is about. */\n primary_repeating_transaction_guid?: string | null\n primary_scheduled_payment_guid?: string | null\n primary_transaction?: Transaction\n primary_transaction_guid?: string | null\n power_action_completed_at?: string | null\n power_action_has_been_completed?: boolean\n scheduled_payment_guids: string[] | []\n short_title?: string | null\n supporting_action?: string | null\n template: string\n title: string\n transaction_guids: string[] | []\n transactions?: Transaction[]\n user_guid: string\n user_verification: UserVerification\n\n // Added for UI: optional institution info for displaying logos\n institutionGuid?: string | null\n institutionName?: string | null\n\n insights_payload?: InsightsPayload\n /**\n * Written by firefly, never by a template: true when the insight's Insights 2.0 flag is on (or\n * the envelope is present). It gates which renderer draws the beat — see `resolveInsightRender`.\n * Absent on an older firefly or on cortex, which renders the legacy path unchanged.\n */\n is_insights_v2?: boolean\n}\n\nexport interface DataSeries {\n names?: string[]\n types?: string[]\n data?: any[]\n}\n\nexport enum UserVerification {\n UNKNOWN = 0,\n CORRECT = 1,\n INCORRECT = 2,\n}\n"],"mappings":"AAiFA,IAAY,IAAL,0BAAA,GAAA;AACL,SAAA,EAAA,EAAA,UAAA,CAAA,IAAA,WACA,EAAA,EAAA,UAAA,CAAA,IAAA,WACA,EAAA,EAAA,YAAA,CAAA,IAAA;AACF,GAAA,CAAA,CAAA"}
@@ -95,6 +95,12 @@ export interface InsightsPayload {
95
95
  primary_transaction_guid?: string | null;
96
96
  primary_category_guid?: string | null;
97
97
  primary_merchant_guid?: string | null;
98
+ /**
99
+ * V2: the repeating transaction a subscription beat is about (Upcoming Subscription, Subscription
100
+ * Price Increase). Read by `recurring_transaction_block`, see
101
+ * `actionBlocks/RecurringTransactionBlock/subscriptionBeats.ts`.
102
+ */
103
+ primary_repeating_transaction_guid?: string | null;
98
104
  }
99
105
  export interface Legends {
100
106
  label: string;
@@ -159,11 +165,19 @@ export interface PrimaryCTA {
159
165
  label: string;
160
166
  block: string;
161
167
  variant?: string | null;
168
+ /**
169
+ * What pressing the CTA does beyond opening its block, named by the backend (ditto
170
+ * `primary_cta_action`, passed through by tupac). Looked up in `insights-v2/ctaActions`;
171
+ * null / absent / unknown means the CTA only opens its block or posts its message.
172
+ */
173
+ action_key?: string | null;
162
174
  }
163
175
  export interface SecondaryCTA {
164
176
  label: string;
165
177
  block: string;
166
178
  variant?: string | null;
179
+ /** See `PrimaryCTA.action_key` (ditto `secondary_cta_action`). */
180
+ action_key?: string | null;
167
181
  }
168
182
  export interface Footer {
169
183
  /** V1: a pre-formatted date string. */
@@ -182,6 +196,7 @@ export interface Footer {
182
196
  }
183
197
  export declare const BlockType: {
184
198
  readonly AccountBlock: "account_block";
199
+ readonly AccountSwitchBlock: "account_switch_block";
185
200
  readonly BarChartBlock: "bar_chart_block";
186
201
  readonly BudgetBlock: "budget_block";
187
202
  readonly ImageBlock: "image_block";
@@ -189,6 +204,7 @@ export declare const BlockType: {
189
204
  readonly DonutChartBlock: "donut_chart_block";
190
205
  readonly DoubleBarChartBlock: "double_bar_chart_block";
191
206
  readonly DoubleProgressBarBlock: "double_progress_bar_block";
207
+ readonly SaveExtraBlock: "save_extra_block";
192
208
  readonly StepChartBlock: "step_chart_block";
193
209
  readonly SVGImageBlock: "svg_image_block";
194
210
  readonly WithdrawalBlock: "withdrawal_block";
@@ -0,0 +1,17 @@
1
+ import { ActionBlockProps } from '../../mappings/blocks';
2
+ /**
3
+ * Expense Details drawer content for one recurring subscription (WEC-1428), shared by the Upcoming
4
+ * Subscription (WEC-1469) and Subscription Price Increase (WEC-1470) beats.
5
+ *
6
+ * The detail rendering (merchant header, next-due card, account/category/schedule rows, past
7
+ * activity, mark-as-paid/unpaid, delete) all lives in the recurring module's
8
+ * `RecurringTransactionDetails`, the same component behind the recurring widget's Expense Details
9
+ * drawer. This block's job is only to resolve *which* subscription (`subscriptionBeats.ts`) and hand
10
+ * off, exactly as `TransactionDetailsBlock` does with `TransactionDetails`.
11
+ *
12
+ * `observer` is required: the resolved subscription comes from RecurringTransactionsStore, and
13
+ * `usePrepare` may still be loading it when this first renders.
14
+ */
15
+ export declare const RecurringTransactionBlock: (({ beat, }: ActionBlockProps) => import("react/jsx-runtime").JSX.Element) & {
16
+ displayName: string;
17
+ };
@@ -0,0 +1,39 @@
1
+ import { useGlobalCopyStore as R, useInsightsV2Store as g, useTransactionStore as T } from "../../../common/context/hooks.es.js";
2
+ import v from "../../../common/components/Loader.es.js";
3
+ import { RecurringTransactionDetails as x } from "../../../recurringtransactions/components/RecurringTransactionDetails.es.js";
4
+ import { ErrorCard as y } from "../../components/ErrorCard.es.js";
5
+ import { useRecurringTransactionBlock as D } from "./useRecurringTransactionBlock.es.js";
6
+ import { useRecurringTransactionLoad as L } from "./useRecurringTransactionPrepare.es.js";
7
+ import r from "react";
8
+ import { observer as B } from "mobx-react-lite";
9
+ import { Text as E } from "@mxenabled/mxui";
10
+ import { jsx as t } from "react/jsx-runtime";
11
+ var O = B(function({ beat: f }) {
12
+ const { closeActionDrawer: l } = g(), { recurring: m } = R(), { filter: d, setFilter: n } = T(), { hasLoadError: i, isLoading: c, repeatingTransaction: o } = D(f), p = L(), e = r.useRef(null);
13
+ e.current === null && (e.current = { ...d });
14
+ const s = r.useRef(n), a = r.useRef(!1), u = !c && !i && o !== void 0;
15
+ return s.current = n, r.useEffect(() => {
16
+ u && (a.current = !0);
17
+ }, [u]), r.useEffect(() => () => {
18
+ a.current && e.current && s.current(e.current);
19
+ }, []), c ? /* @__PURE__ */ t(v, {}) : i ? /* @__PURE__ */ t(y, { onRetry: () => {
20
+ p();
21
+ } }) : o ? /* @__PURE__ */ t(x, {
22
+ onDeleted: l,
23
+ recurringTransaction: o,
24
+ selectedRecurrence: null
25
+ }) : /* @__PURE__ */ t(E, {
26
+ color: "text.secondary",
27
+ sx: {
28
+ m: 24,
29
+ textAlign: "center"
30
+ },
31
+ variant: "body1",
32
+ children: m.no_activities
33
+ });
34
+ });
35
+ export {
36
+ O as RecurringTransactionBlock
37
+ };
38
+
39
+ //# sourceMappingURL=RecurringTransactionBlock.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecurringTransactionBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/RecurringTransactionBlock/RecurringTransactionBlock.tsx"],"sourcesContent":["import React from 'react'\n\nimport { Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n Loader,\n useGlobalCopyStore,\n useInsightsV2Store,\n useTransactionStore,\n} from '../../../common'\nimport type { TransactionFilter } from '../../../core/stores/TransactionStore'\nimport { RecurringTransactionDetails } from '../../../recurringtransactions/components/RecurringTransactionDetails'\nimport { ErrorCard } from '../../components/ErrorCard'\nimport type { ActionBlockProps } from '../../mappings/blocks'\n\nimport { useRecurringTransactionBlock } from './useRecurringTransactionBlock'\nimport { useRecurringTransactionLoad } from './useRecurringTransactionPrepare'\n\n/**\n * Expense Details drawer content for one recurring subscription (WEC-1428), shared by the Upcoming\n * Subscription (WEC-1469) and Subscription Price Increase (WEC-1470) beats.\n *\n * The detail rendering (merchant header, next-due card, account/category/schedule rows, past\n * activity, mark-as-paid/unpaid, delete) all lives in the recurring module's\n * `RecurringTransactionDetails`, the same component behind the recurring widget's Expense Details\n * drawer. This block's job is only to resolve *which* subscription (`subscriptionBeats.ts`) and hand\n * off, exactly as `TransactionDetailsBlock` does with `TransactionDetails`.\n *\n * `observer` is required: the resolved subscription comes from RecurringTransactionsStore, and\n * `usePrepare` may still be loading it when this first renders.\n */\nexport const RecurringTransactionBlock = observer(function RecurringTransactionBlock({\n beat,\n}: ActionBlockProps) {\n const { closeActionDrawer } = useInsightsV2Store()\n const { recurring: copy } = useGlobalCopyStore()\n const { filter, setFilter } = useTransactionStore()\n const { hasLoadError, isLoading, repeatingTransaction } = useRecurringTransactionBlock(beat)\n const retryLoad = useRecurringTransactionLoad()\n\n // `RecurringTransactionDetails` narrows the shared `TransactionStore.filter` to this one\n // subscription (90-day window + guid predicate) and never restores it. Inside the recurring widget\n // that is fine, but a Transactions widget on the same page would be left showing only this\n // subscription's charges after the drawer closes. The runner mounts this block on open and\n // unmounts it on close, so the filter is put back on unmount, with two guards:\n // - the snapshot is taken BY VALUE on the first render (the store's `filter` is a live observable\n // object, so holding the reference would hold whatever it was later changed to);\n // - the restore only runs if the details view actually mounted. While loading, on a failed load\n // or when nothing resolved, this block changed nothing and must not overwrite a filter some\n // other widget may have set in the meantime.\n const filterBeforeOpen = React.useRef<TransactionFilter | null>(null)\n\n if (filterBeforeOpen.current === null) filterBeforeOpen.current = { ...filter }\n\n const setFilterRef = React.useRef(setFilter)\n const didRenderDetails = React.useRef(false)\n const isDetailsRendered = !isLoading && !hasLoadError && repeatingTransaction !== undefined\n\n setFilterRef.current = setFilter\n\n React.useEffect(() => {\n if (isDetailsRendered) didRenderDetails.current = true\n }, [isDetailsRendered])\n\n React.useEffect(\n () => () => {\n if (didRenderDetails.current && filterBeforeOpen.current) {\n setFilterRef.current(filterBeforeOpen.current)\n }\n },\n [],\n )\n\n if (isLoading) return <Loader />\n\n // The open-time load finished without data (API failure, reported through the store). Offer the\n // feed's standard error card with a retry rather than leaving the loader up forever.\n if (hasLoadError) return <ErrorCard onRetry={() => void retryLoad()} />\n\n // Loading finished and the beat still resolves to nothing (deleted, never detected, or a beat that\n // names none). Say so in the recurring module's own words rather than the generic error drawer.\n if (!repeatingTransaction) {\n return (\n <Text color=\"text.secondary\" sx={{ m: 24, textAlign: 'center' }} variant=\"body1\">\n {copy.no_activities}\n </Text>\n )\n }\n\n return (\n <RecurringTransactionDetails\n // Deleting the subscription from inside the drawer leaves nothing to show; close it, as the\n // recurring widget's own drawer does.\n onDeleted={closeActionDrawer}\n recurringTransaction={repeatingTransaction}\n // No specific occurrence was tapped (the CTA opens the subscription itself), so the next-due\n // card falls back to the subscription's next upcoming date.\n selectedRecurrence={null}\n />\n )\n})\n"],"mappings":";;;;;;;;;;AAgCA,IAAa,IAA4B,EAAS,SAAmC,EACnF,MAAA,EAAA,GACmB;AACnB,QAAM,EAAE,mBAAA,EAAA,IAAsB,EAAmB,GAC3C,EAAE,WAAW,EAAA,IAAS,EAAmB,GACzC,EAAE,QAAA,GAAQ,WAAA,EAAA,IAAc,EAAoB,GAC5C,EAAE,cAAA,GAAc,WAAA,GAAW,sBAAA,EAAA,IAAyB,EAA6B,CAAI,GACrF,IAAY,EAA4B,GAYxC,IAAmB,EAAM,OAAiC,IAAI;AAEpE,EAAI,EAAiB,YAAY,SAAM,EAAiB,UAAU,EAAE,GAAG,EAAO;AAE9E,QAAM,IAAe,EAAM,OAAO,CAAS,GACrC,IAAmB,EAAM,OAAO,EAAK,GACrC,IAAoB,CAAC,KAAa,CAAC,KAAgB,MAAyB;AAiBlF,SAfA,EAAa,UAAU,GAEvB,EAAM,UAAA,MAAgB;AACpB,IAAI,MAAmB,EAAiB,UAAU;AAAA,EACpD,GAAG,CAAC,CAAiB,CAAC,GAEtB,EAAM,UAAA,MAAA,MACQ;AACV,IAAI,EAAiB,WAAW,EAAiB,WAC/C,EAAa,QAAQ,EAAiB,OAAO;AAAA,EAEjD,GACA,CAAC,CACH,GAEI,IAAkB,gBAAA,EAAC,GAAD,CAAS,CAAA,IAI3B,IAAqB,gBAAA,EAAC,GAAD,EAAW,SAAA,MAAA;AAAe,IAAK,EAAU;AAAA,IAAI,CAAA,IAIjE,IASH,gBAAA,EAAC,GAAD;AAAA,IAGE,WAAW;AAAA,IACX,sBAAsB;AAAA,IAGtB,oBAAoB;AAAA,EACrB,CAAA,IAfC,gBAAA,EAAC,GAAD;AAAA,IAAM,OAAM;AAAA,IAAiB,IAAI;AAAA,MAAE,GAAG;AAAA,MAAI,WAAW;AAAA,IAAS;AAAA,IAAG,SAAQ;AAAA,IACtE,UAAA,EAAK;AAAA,EACF,CAAA;AAeZ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { RecurringTransactionBlock } from './RecurringTransactionBlock';
2
+ export { useRecurringTransactionPrepare } from './useRecurringTransactionPrepare';
@@ -0,0 +1,19 @@
1
+ import { Beat, RepeatingTransaction, Transaction } from '../../../core';
2
+ interface SubscriptionContext {
3
+ /** `recurringTransactionsStore.repeatingTransactions`, with linked transactions when augmented. */
4
+ repeatingTransactions: RepeatingTransaction[];
5
+ /** `transactionStore.transactions`, consulted to walk a transaction to its repeating transaction. */
6
+ transactions: Transaction[];
7
+ }
8
+ /**
9
+ * Which repeating transaction the beat is about, or `undefined` if it cannot tell.
10
+ *
11
+ * The direct pointer wins: V2 carries `primary_repeating_transaction_guid` on the payload beside the
12
+ * other `primary_*` guids, and the legacy pulse envelope carries it on the beat (Upcoming
13
+ * Subscription, WEC-1469). Failing that, the beat's primary transaction is walked to the repeating
14
+ * transaction it is linked to (Subscription Price Increase, WEC-1470, whose pulse fixture names only
15
+ * `primary_transaction`): the hydrated `beat.primary_transaction` first, then the transaction store,
16
+ * then each repeating transaction's own linked `transactions`.
17
+ */
18
+ export declare const resolveSubscriptionGuid: (beat: Beat, { repeatingTransactions, transactions }: SubscriptionContext) => string | undefined;
19
+ export {};
@@ -0,0 +1,12 @@
1
+ var s = (i, { repeatingTransactions: t, transactions: o }) => {
2
+ const a = i.insights_payload?.primary_repeating_transaction_guid || i.primary_repeating_transaction_guid;
3
+ if (a) return a;
4
+ const r = i.insights_payload?.primary_transaction_guid || i.primary_transaction?.guid || i.primary_transaction_guid;
5
+ if (r)
6
+ return (i.primary_transaction?.guid === r ? i.primary_transaction : void 0)?.repeating_transaction_guid || o.find((n) => n.guid === r)?.repeating_transaction_guid || t.find((n) => n.transactions?.some((d) => d.guid === r))?.guid || void 0;
7
+ };
8
+ export {
9
+ s as resolveSubscriptionGuid
10
+ };
11
+
12
+ //# sourceMappingURL=subscriptionBeats.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptionBeats.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/RecurringTransactionBlock/subscriptionBeats.ts"],"sourcesContent":["import type { Beat, RepeatingTransaction, Transaction } from '../../../core'\n\ninterface SubscriptionContext {\n /** `recurringTransactionsStore.repeatingTransactions`, with linked transactions when augmented. */\n repeatingTransactions: RepeatingTransaction[]\n /** `transactionStore.transactions`, consulted to walk a transaction to its repeating transaction. */\n transactions: Transaction[]\n}\n\n/**\n * Which repeating transaction the beat is about, or `undefined` if it cannot tell.\n *\n * The direct pointer wins: V2 carries `primary_repeating_transaction_guid` on the payload beside the\n * other `primary_*` guids, and the legacy pulse envelope carries it on the beat (Upcoming\n * Subscription, WEC-1469). Failing that, the beat's primary transaction is walked to the repeating\n * transaction it is linked to (Subscription Price Increase, WEC-1470, whose pulse fixture names only\n * `primary_transaction`): the hydrated `beat.primary_transaction` first, then the transaction store,\n * then each repeating transaction's own linked `transactions`.\n */\nexport const resolveSubscriptionGuid = (\n beat: Beat,\n { repeatingTransactions, transactions }: SubscriptionContext,\n): string | undefined => {\n const direct =\n beat.insights_payload?.primary_repeating_transaction_guid ||\n beat.primary_repeating_transaction_guid\n\n if (direct) return direct\n\n const transactionGuid =\n beat.insights_payload?.primary_transaction_guid ||\n beat.primary_transaction?.guid ||\n beat.primary_transaction_guid\n\n if (!transactionGuid) return undefined\n\n const snapshot =\n beat.primary_transaction?.guid === transactionGuid ? beat.primary_transaction : undefined\n\n return (\n snapshot?.repeating_transaction_guid ||\n transactions.find((transaction) => transaction.guid === transactionGuid)\n ?.repeating_transaction_guid ||\n repeatingTransactions.find((repeating) =>\n repeating.transactions?.some((transaction) => transaction.guid === transactionGuid),\n )?.guid ||\n undefined\n )\n}\n"],"mappings":"AAmBA,IAAa,IAAA,CACX,GACA,EAAE,uBAAA,GAAuB,cAAA,EAAA,MACF;AACvB,QAAM,IACJ,EAAK,kBAAkB,sCACvB,EAAK;AAEP,MAAI,EAAQ,QAAO;AAEnB,QAAM,IACJ,EAAK,kBAAkB,4BACvB,EAAK,qBAAqB,QAC1B,EAAK;AAEP,MAAK;AAKL,YAFE,EAAK,qBAAqB,SAAS,IAAkB,EAAK,sBAAsB,SAGtE,8BACV,EAAa,KAAA,CAAM,MAAgB,EAAY,SAAS,CAAe,GACnE,8BACJ,EAAsB,KAAA,CAAM,MAC1B,EAAU,cAAc,KAAA,CAAM,MAAgB,EAAY,SAAS,CAAe,CACpF,GAAG,QACH;AAEJ"}
@@ -0,0 +1,23 @@
1
+ import { Beat, RepeatingTransaction } from '../../../core';
2
+ export interface RecurringTransactionBlockState {
3
+ /** True when the open-time load finished without leaving the store loaded; the block offers a retry. */
4
+ hasLoadError: boolean;
5
+ /** True until the recurring store has loaded; the block shows a loader instead of an empty state. */
6
+ isLoading: boolean;
7
+ /** `undefined` once loading finishes means the subscription could not be resolved. */
8
+ repeatingTransaction?: RepeatingTransaction;
9
+ }
10
+ /**
11
+ * Resolves which repeating transaction this drawer is about.
12
+ *
13
+ * The guid comes from the beat (`resolveSubscriptionGuid`), then is looked up in
14
+ * `RecurringTransactionsStore` rather than rebuilt from the beat, because the Expense Details view
15
+ * edits (payment schedule, mark as paid, delete) must write through to the same store object the
16
+ * recurring widget renders.
17
+ *
18
+ * `detailedRepeatingTransactions` joins the account and linked transactions in, but it is empty until
19
+ * the transaction store has loaded (see `augmentRepeatingTransactions`). The raw record is the
20
+ * fallback so a subscription the store has is never reported missing just because transactions are
21
+ * still on their way.
22
+ */
23
+ export declare const useRecurringTransactionBlock: (beat: Beat) => RecurringTransactionBlockState;
@@ -0,0 +1,21 @@
1
+ import { useActionContext as p, useRecurringTransactionsStore as T, useTransactionStore as f } from "../../../common/context/hooks.es.js";
2
+ import { resolveSubscriptionGuid as l } from "./subscriptionBeats.es.js";
3
+ var L = (s) => {
4
+ const { detailedRepeatingTransactions: e, isDataLoaded: o, repeatingTransactions: r } = T(), { transactions: c } = f(), t = p()?.recurringLoadFailed === !0 && !o, i = l(s, {
5
+ repeatingTransactions: r,
6
+ transactions: c
7
+ }), a = (u) => u.find((g) => g.guid === i), n = i ? a(r) : void 0, d = n ? a(e) ?? {
8
+ ...n,
9
+ transactions: n.transactions ?? []
10
+ } : void 0;
11
+ return {
12
+ hasLoadError: t,
13
+ isLoading: !o && !t,
14
+ repeatingTransaction: d
15
+ };
16
+ };
17
+ export {
18
+ L as useRecurringTransactionBlock
19
+ };
20
+
21
+ //# sourceMappingURL=useRecurringTransactionBlock.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRecurringTransactionBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionBlock.ts"],"sourcesContent":["import {\n useActionContext,\n useRecurringTransactionsStore,\n useTransactionStore,\n} from '../../../common'\nimport type { Beat, RepeatingTransaction } from '../../../core'\n\nimport { resolveSubscriptionGuid } from './subscriptionBeats'\nimport type { RecurringTransactionActionContext } from './useRecurringTransactionPrepare'\n\nexport interface RecurringTransactionBlockState {\n /** True when the open-time load finished without leaving the store loaded; the block offers a retry. */\n hasLoadError: boolean\n /** True until the recurring store has loaded; the block shows a loader instead of an empty state. */\n isLoading: boolean\n /** `undefined` once loading finishes means the subscription could not be resolved. */\n repeatingTransaction?: RepeatingTransaction\n}\n\n/**\n * Resolves which repeating transaction this drawer is about.\n *\n * The guid comes from the beat (`resolveSubscriptionGuid`), then is looked up in\n * `RecurringTransactionsStore` rather than rebuilt from the beat, because the Expense Details view\n * edits (payment schedule, mark as paid, delete) must write through to the same store object the\n * recurring widget renders.\n *\n * `detailedRepeatingTransactions` joins the account and linked transactions in, but it is empty until\n * the transaction store has loaded (see `augmentRepeatingTransactions`). The raw record is the\n * fallback so a subscription the store has is never reported missing just because transactions are\n * still on their way.\n */\nexport const useRecurringTransactionBlock = (beat: Beat): RecurringTransactionBlockState => {\n const { detailedRepeatingTransactions, isDataLoaded, repeatingTransactions } =\n useRecurringTransactionsStore()\n const { transactions } = useTransactionStore()\n // The failure flag is per drawer session and only cleared by an explicit retry, but the store is\n // shared: another widget on the page (e.g. Recurring Transactions on a dashboard) may load it\n // successfully while this drawer is still open. Data that has since arrived wins over the flag.\n const loadFailed =\n useActionContext<RecurringTransactionActionContext>()?.recurringLoadFailed === true\n const hasLoadError = loadFailed && !isDataLoaded\n\n const guid = resolveSubscriptionGuid(beat, { repeatingTransactions, transactions })\n\n const findByGuid = (list: RepeatingTransaction[]) =>\n list.find((repeating) => repeating.guid === guid)\n\n const raw = guid ? findByGuid(repeatingTransactions) : undefined\n const repeatingTransaction = raw\n ? (findByGuid(detailedRepeatingTransactions) ?? {\n ...raw,\n transactions: raw.transactions ?? [],\n })\n : undefined\n\n return { hasLoadError, isLoading: !isDataLoaded && !hasLoadError, repeatingTransaction }\n}\n"],"mappings":";;AAgCA,IAAa,IAAA,CAAgC,MAA+C;AAC1F,QAAM,EAAE,+BAAA,GAA+B,cAAA,GAAc,uBAAA,EAAA,IACnD,EAA8B,GAC1B,EAAE,cAAA,EAAA,IAAiB,EAAoB,GAMvC,IADJ,EAAoD,GAAG,wBAAwB,MAC9C,CAAC,GAE9B,IAAO,EAAwB,GAAM;AAAA,IAAE,uBAAA;AAAA,IAAuB,cAAA;AAAA,EAAa,CAAC,GAE5E,IAAA,CAAc,MAClB,EAAK,KAAA,CAAM,MAAc,EAAU,SAAS,CAAI,GAE5C,IAAM,IAAO,EAAW,CAAqB,IAAI,QACjD,IAAuB,IACxB,EAAW,CAA6B,KAAK;AAAA,IAC5C,GAAG;AAAA,IACH,cAAc,EAAI,gBAAgB,CAAC;AAAA,EACrC,IACA;AAEJ,SAAO;AAAA,IAAE,cAAA;AAAA,IAAc,WAAW,CAAC,KAAgB,CAAC;AAAA,IAAc,sBAAA;AAAA,EAAqB;AACzF"}
@@ -0,0 +1,25 @@
1
+ import { ActionBlockProps } from '../../mappings/blocks';
2
+ /** The key this block layers onto the drawer's action context when its open-time load fails. */
3
+ export interface RecurringTransactionActionContext {
4
+ recurringLoadFailed?: boolean;
5
+ }
6
+ /**
7
+ * Runs the recurring widget's loader and records whether it left the store loaded.
8
+ *
9
+ * `RecurringTransactionsStore` swallows API failures into `captureError`, so on a failed request
10
+ * `isDataLoaded` simply stays false and nothing else changes. Left alone, that would keep the block
11
+ * on its loader forever. This is the one place that turns the outcome into a flag the block can
12
+ * render an error state (with retry) from. The flag lives on the drawer's action context, which is
13
+ * per drawer session: a new open starts clean.
14
+ */
15
+ export declare const useRecurringTransactionLoad: () => () => Promise<void>;
16
+ /**
17
+ * Open-time data guarantee for the Expense Details drawer. Wired as the block's `usePrepare`, so
18
+ * `ActionBlockLayer` runs it once each time a CTA opens the drawer.
19
+ *
20
+ * A beat is rendered in the feed long before the recurring module has loaded, so the store is
21
+ * usually cold at open time. The loader is the recurring widget's own (accounts, categories and
22
+ * transactions first, then repeating transactions). Guarded on `isDataLoaded` so re-opening does
23
+ * not refetch, and on a ref so a failed load does not refire on every render (retry is explicit).
24
+ */
25
+ export declare const useRecurringTransactionPrepare: (_props: ActionBlockProps) => void;
@@ -0,0 +1,24 @@
1
+ import { useInsightsV2Store as o, useRecurringTransactionsStore as s } from "../../../common/context/hooks.es.js";
2
+ import e from "react";
3
+ var c = () => {
4
+ const a = s(), { setActionContext: r } = o();
5
+ return e.useCallback(async () => {
6
+ r({ recurringLoadFailed: !1 });
7
+ try {
8
+ await a.loadRepeatingTransactionData();
9
+ } catch {
10
+ }
11
+ a.isDataLoaded || r({ recurringLoadFailed: !0 });
12
+ }, [a, r]);
13
+ }, d = (a) => {
14
+ const { isDataLoaded: r } = s(), t = c(), n = e.useRef(!1);
15
+ e.useEffect(() => {
16
+ n.current || r || (n.current = !0, t());
17
+ }, [r, t]);
18
+ };
19
+ export {
20
+ c as useRecurringTransactionLoad,
21
+ d as useRecurringTransactionPrepare
22
+ };
23
+
24
+ //# sourceMappingURL=useRecurringTransactionPrepare.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRecurringTransactionPrepare.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/RecurringTransactionBlock/useRecurringTransactionPrepare.ts"],"sourcesContent":["import React from 'react'\n\nimport { useInsightsV2Store, useRecurringTransactionsStore } from '../../../common'\nimport type { ActionBlockProps } from '../../mappings/blocks'\n\n/** The key this block layers onto the drawer's action context when its open-time load fails. */\nexport interface RecurringTransactionActionContext {\n recurringLoadFailed?: boolean\n}\n\n/**\n * Runs the recurring widget's loader and records whether it left the store loaded.\n *\n * `RecurringTransactionsStore` swallows API failures into `captureError`, so on a failed request\n * `isDataLoaded` simply stays false and nothing else changes. Left alone, that would keep the block\n * on its loader forever. This is the one place that turns the outcome into a flag the block can\n * render an error state (with retry) from. The flag lives on the drawer's action context, which is\n * per drawer session: a new open starts clean.\n */\nexport const useRecurringTransactionLoad = () => {\n const recurringTransactionsStore = useRecurringTransactionsStore()\n const { setActionContext } = useInsightsV2Store()\n\n return React.useCallback(async () => {\n setActionContext({ recurringLoadFailed: false })\n\n try {\n await recurringTransactionsStore.loadRepeatingTransactionData()\n } catch {\n // Already reported through the store's captureError; the flag below is the UI signal.\n }\n\n if (!recurringTransactionsStore.isDataLoaded) setActionContext({ recurringLoadFailed: true })\n }, [recurringTransactionsStore, setActionContext])\n}\n\n/**\n * Open-time data guarantee for the Expense Details drawer. Wired as the block's `usePrepare`, so\n * `ActionBlockLayer` runs it once each time a CTA opens the drawer.\n *\n * A beat is rendered in the feed long before the recurring module has loaded, so the store is\n * usually cold at open time. The loader is the recurring widget's own (accounts, categories and\n * transactions first, then repeating transactions). Guarded on `isDataLoaded` so re-opening does\n * not refetch, and on a ref so a failed load does not refire on every render (retry is explicit).\n */\nexport const useRecurringTransactionPrepare = (_props: ActionBlockProps) => {\n const { isDataLoaded } = useRecurringTransactionsStore()\n const load = useRecurringTransactionLoad()\n const hasStarted = React.useRef(false)\n\n React.useEffect(() => {\n if (hasStarted.current || isDataLoaded) return\n\n hasStarted.current = true\n void load()\n }, [isDataLoaded, load])\n}\n"],"mappings":";;AAmBA,IAAa,IAAA,MAAoC;AAC/C,QAAM,IAA6B,EAA8B,GAC3D,EAAE,kBAAA,EAAA,IAAqB,EAAmB;AAEhD,SAAO,EAAM,YAAY,YAAY;AACnC,IAAA,EAAiB,EAAE,qBAAqB,GAAM,CAAC;AAE/C,QAAI;AACF,YAAM,EAA2B,6BAA6B;AAAA,IAChE,QAAQ;AAAA,IAER;AAEA,IAAK,EAA2B,gBAAc,EAAiB,EAAE,qBAAqB,GAAK,CAAC;AAAA,EAC9F,GAAG,CAAC,GAA4B,CAAgB,CAAC;AACnD,GAWa,IAAA,CAAkC,MAA6B;AAC1E,QAAM,EAAE,cAAA,EAAA,IAAiB,EAA8B,GACjD,IAAO,EAA4B,GACnC,IAAa,EAAM,OAAO,EAAK;AAErC,EAAA,EAAM,UAAA,MAAgB;AACpB,IAAI,EAAW,WAAW,MAE1B,EAAW,UAAU,IACrB,EAAU;AAAA,EACZ,GAAG,CAAC,GAAc,CAAI,CAAC;AACzB"}
@@ -21,6 +21,16 @@ export interface DismissParams {
21
21
  beatGuid: string;
22
22
  type: DismissType;
23
23
  }
24
+ /**
25
+ * A CTA side effect the host must perform because experiences cannot (beat-owned endpoints,
26
+ * spending plan items, third-party SDKs). `name` is agreed between the ctaActions table and
27
+ * the host; see insights-v2/ctaActions/README.md for the list.
28
+ */
29
+ export interface CtaActionParams {
30
+ beatGuid: string;
31
+ name: string;
32
+ params?: Record<string, unknown>;
33
+ }
24
34
  /** Host callbacks for a beat card. */
25
35
  export interface BeatActions {
26
36
  onMenuAction?: MenuActionHandlers;
@@ -40,6 +50,7 @@ export interface BeatActions {
40
50
  * renders disabled (see `isContactSupportCtaDisabled`).
41
51
  */
42
52
  onContactSupport?: () => void;
53
+ onCtaAction?: (params: CtaActionParams) => Promise<void> | void;
43
54
  }
44
55
  export declare const BeatActionsProvider: ({ value, children, }: {
45
56
  value: BeatActions;
@@ -1 +1 @@
1
- {"version":3,"file":"beatActions.es.js","names":[],"sources":["../../src/insights-v2/beatActions.tsx"],"sourcesContent":["import { createContext, useContext, type ReactNode } from 'react'\n\nimport type { DismissType } from './components/DismissInsightDialog'\nimport type { FeedbackDialogCloseReason } from './components/FeedbackDialog'\n\nexport type InsightMenuAction = 'helpful' | 'not_helpful' | 'hide'\nexport type InsightRating = 'helpful' | 'not_helpful'\n\nexport interface MenuActionHandlers {\n onAction: (action: InsightMenuAction) => void\n onOpen?: () => void\n disabled?: boolean\n}\n\nexport interface UpdateParams {\n beatGuid: string\n}\n\nexport interface FeedbackParams {\n beatGuid: string\n source: InsightRating | 'hide'\n option: string\n detail?: string\n}\n\nexport interface DismissParams {\n beatGuid: string\n type: DismissType\n}\n\n/** Host callbacks for a beat card. */\nexport interface BeatActions {\n onMenuAction?: MenuActionHandlers\n onUpdate?: (params: UpdateParams) => void\n onSubmitFeedback?: (params: FeedbackParams) => void\n onDismissInsight?: (params: DismissParams) => void\n onUndoDismiss?: (params: { beatGuid: string }) => void\n onFeedbackDialogClose?: (reason: FeedbackDialogCloseReason) => void\n onFeedbackDialogBack?: () => void\n onFeedbackTextFocus?: () => void\n /**\n * Opens the host's contact-support flow for a `contact_support_block` CTA. insights-v2 cannot\n * import the help module (hosts that don't ship it stub the import), so the host supplies it:\n * the experiences Help widget wires `RequestSupport`; pulse wires its own. When absent, the CTA\n * renders disabled (see `isContactSupportCtaDisabled`).\n */\n onContactSupport?: () => void\n}\n\nconst BeatActionsContext = createContext<BeatActions>({})\n\nexport const BeatActionsProvider = ({\n value,\n children,\n}: {\n value: BeatActions\n children: ReactNode\n}) => <BeatActionsContext.Provider value={value}>{children}</BeatActionsContext.Provider>\n\nexport const useBeatActions = () => useContext(BeatActionsContext)\n"],"mappings":";;AAiDA,IAAM,IAAqB,EAA2B,CAAC,CAAC,GAE3C,IAAA,CAAuB,EAClC,OAAA,GACA,UAAA,EAAA,MAII,gBAAA,EAAC,EAAmB,UAApB;AAAA,EAAoC,OAAA;AAAA,EAAQ,UAAA;AAAsC,CAAA,GAE3E,IAAA,MAAuB,EAAW,CAAkB"}
1
+ {"version":3,"file":"beatActions.es.js","names":[],"sources":["../../src/insights-v2/beatActions.tsx"],"sourcesContent":["import { createContext, useContext, type ReactNode } from 'react'\n\nimport type { DismissType } from './components/DismissInsightDialog'\nimport type { FeedbackDialogCloseReason } from './components/FeedbackDialog'\n\nexport type InsightMenuAction = 'helpful' | 'not_helpful' | 'hide'\nexport type InsightRating = 'helpful' | 'not_helpful'\n\nexport interface MenuActionHandlers {\n onAction: (action: InsightMenuAction) => void\n onOpen?: () => void\n disabled?: boolean\n}\n\nexport interface UpdateParams {\n beatGuid: string\n}\n\nexport interface FeedbackParams {\n beatGuid: string\n source: InsightRating | 'hide'\n option: string\n detail?: string\n}\n\nexport interface DismissParams {\n beatGuid: string\n type: DismissType\n}\n\n/**\n * A CTA side effect the host must perform because experiences cannot (beat-owned endpoints,\n * spending plan items, third-party SDKs). `name` is agreed between the ctaActions table and\n * the host; see insights-v2/ctaActions/README.md for the list.\n */\nexport interface CtaActionParams {\n beatGuid: string\n name: string\n params?: Record<string, unknown>\n}\n\n/** Host callbacks for a beat card. */\nexport interface BeatActions {\n onMenuAction?: MenuActionHandlers\n onUpdate?: (params: UpdateParams) => void\n onSubmitFeedback?: (params: FeedbackParams) => void\n onDismissInsight?: (params: DismissParams) => void\n onUndoDismiss?: (params: { beatGuid: string }) => void\n onFeedbackDialogClose?: (reason: FeedbackDialogCloseReason) => void\n onFeedbackDialogBack?: () => void\n onFeedbackTextFocus?: () => void\n /**\n * Opens the host's contact-support flow for a `contact_support_block` CTA. insights-v2 cannot\n * import the help module (hosts that don't ship it stub the import), so the host supplies it:\n * the experiences Help widget wires `RequestSupport`; pulse wires its own. When absent, the CTA\n * renders disabled (see `isContactSupportCtaDisabled`).\n */\n onContactSupport?: () => void\n onCtaAction?: (params: CtaActionParams) => Promise<void> | void\n}\n\nconst BeatActionsContext = createContext<BeatActions>({})\n\nexport const BeatActionsProvider = ({\n value,\n children,\n}: {\n value: BeatActions\n children: ReactNode\n}) => <BeatActionsContext.Provider value={value}>{children}</BeatActionsContext.Provider>\n\nexport const useBeatActions = () => useContext(BeatActionsContext)\n"],"mappings":";;AA6DA,IAAM,IAAqB,EAA2B,CAAC,CAAC,GAE3C,IAAA,CAAuB,EAClC,OAAA,GACA,UAAA,EAAA,MAII,gBAAA,EAAC,EAAmB,UAApB;AAAA,EAAoC,OAAA;AAAA,EAAQ,UAAA;AAAsC,CAAA,GAE3E,IAAA,MAAuB,EAAW,CAAkB"}