@mx-cartographer/experiences 9.4.69 → 9.4.70
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.
- package/CHANGELOG.md +7 -0
- package/dist/common/utils/PostMessage.d.ts +5 -1
- package/dist/common/utils/PostMessage.es.js +26 -24
- package/dist/common/utils/PostMessage.es.js.map +1 -1
- package/dist/core/types/Insights.d.ts +17 -0
- package/dist/core/types/index.d.ts +1 -1
- package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/ctaConfig.d.ts +3 -2
- package/dist/insights-v2/ctaActions/ctaConfig.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/ctaPostMessages.d.ts +16 -0
- package/dist/insights-v2/ctaActions/ctaPostMessages.es.js +51 -0
- package/dist/insights-v2/ctaActions/ctaPostMessages.es.js.map +1 -0
- package/dist/insights-v2/ctaActions/index.d.ts +1 -0
- package/dist/insights-v2/ctaActions/runCtaAction.d.ts +5 -1
- package/dist/insights-v2/ctaActions/runCtaAction.es.js +54 -51
- package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/types.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [9.4.70] - 10-09-2026
|
|
2
|
+
|
|
3
|
+
- **ADDED** - `PrimaryCTA` / `SecondaryCTA` accept a backend-described `metadata` object (`event`, `action`, `payload`); on press the CTA runner posts that message to the host after the step's handler and effects succeed, so per-insight post messages no longer need frontend code (WEC-1852)
|
|
4
|
+
- **CHANGED** - A CTA press posts a message only when one resolves (`cta.metadata`, else `ctaPostMessages`); the previous fallback that posted a base `beat/cta` `op_1` for every block-less CTA is removed (WEC-1852)
|
|
5
|
+
- **CHANGED** - `sendBeatCta` accepts an `event` override and `extras` merged into the message metadata (WEC-1852)
|
|
6
|
+
- **ADDED** - `ctaPostMessages`: interim per-template CTA post messages (SaveAnExtra100Dollars, LowAccountBalance, MonthlyObligationsStatus, DesignateEmergencySavingsAccount, ReplenishSavings, SavingsOpportunityV2) carried over from pulse, used only when the backend sends no `cta.metadata` (WEC-1852)
|
|
7
|
+
|
|
1
8
|
## [9.4.69] - 10-09-2026
|
|
2
9
|
|
|
3
10
|
- **ADDED** - `remaining_cash_block` (Remaining Cash) action block for the Payday Cash Left Over Status beat: summary card (remaining until payday, days, cash on hand, upcoming expenses) plus cash accounts and recurring expenses before payday, each capped at two rows with an "Other (#)" row. Store-driven from `CashflowStore`; loads through `useRecurringTransactionPrepare` with an `ErrorCard` retry on failure (WEC-1880)
|
|
@@ -52,10 +52,14 @@ export interface SendBeatCtaOptions {
|
|
|
52
52
|
userGuid?: string;
|
|
53
53
|
/** Operation string sent with the CTA (defaults to op_1). */
|
|
54
54
|
op?: string;
|
|
55
|
+
/** Post message event name (defaults to `beat/cta`). */
|
|
56
|
+
event?: string;
|
|
57
|
+
/** Beat-specific fields merged into the metadata after the base payload and `user_guid`. */
|
|
58
|
+
extras?: Record<string, unknown>;
|
|
55
59
|
}
|
|
56
60
|
/**
|
|
57
61
|
* Send a beat CTA to the host: builds the payload, adds `user_guid`, then either
|
|
58
62
|
* posts a message to the host or navigates the webview. Mirrors the behaviour of
|
|
59
63
|
* Pulse's `postMessages` epic (minus the RxJS buffering).
|
|
60
64
|
*/
|
|
61
|
-
export declare function sendBeatCta(beat: BeatCtaSource, { isMobileWebview, webviewScheme, userGuid, op }?: SendBeatCtaOptions): void;
|
|
65
|
+
export declare function sendBeatCta(beat: BeatCtaSource, { isMobileWebview, webviewScheme, userGuid, op, event, extras }?: SendBeatCtaOptions): void;
|
|
@@ -1,62 +1,64 @@
|
|
|
1
|
-
function
|
|
1
|
+
function f() {
|
|
2
2
|
return window.opener && window.opener.location ? window.opener.location.toString() : document.referrer;
|
|
3
3
|
}
|
|
4
4
|
function a(e) {
|
|
5
|
-
const n =
|
|
5
|
+
const n = f().replace(/([^:]+:\/\/[^\/]+).*/, "$1"), t = window.parent || window.opener || !1;
|
|
6
6
|
return t && t.postMessage && n ? (t.postMessage(e, n), !0) : !1;
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function T(e) {
|
|
9
9
|
return a({
|
|
10
10
|
metadata: { beat_guid: e },
|
|
11
11
|
mx: !0,
|
|
12
12
|
type: "mx/moneyDashboard/insightOnTransaction"
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function y(e) {
|
|
16
16
|
return a({
|
|
17
17
|
metadata: { template: e },
|
|
18
18
|
mx: !0,
|
|
19
19
|
type: "mx/pulse/client_configured_cta"
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
var
|
|
23
|
-
function
|
|
22
|
+
var l = "beat/cta", g = "op_1", x = "op_2";
|
|
23
|
+
function w(e, n = g) {
|
|
24
24
|
const t = {
|
|
25
25
|
action: n,
|
|
26
26
|
beat_guid: e.guid,
|
|
27
27
|
beat_template: e.template
|
|
28
28
|
};
|
|
29
|
-
return e?.payload?.action_string && (t.action_string = e.payload.action_string), [
|
|
29
|
+
return e?.payload?.action_string && (t.action_string = e.payload.action_string), [l, t];
|
|
30
30
|
}
|
|
31
|
-
function
|
|
31
|
+
function m(e, n, t = "mx/pulse") {
|
|
32
32
|
return window.self === window.top ? !1 : a({
|
|
33
33
|
metadata: n,
|
|
34
34
|
mx: !0,
|
|
35
35
|
type: `${t}/${e}`
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
function
|
|
38
|
+
function _(e, n, t = "mx") {
|
|
39
39
|
let o = `${t}://pulse/${e}`;
|
|
40
40
|
n && (o += "?metadata=" + encodeURIComponent(JSON.stringify(n))), window.location.href = o;
|
|
41
41
|
}
|
|
42
|
-
function
|
|
43
|
-
const [
|
|
44
|
-
...
|
|
45
|
-
...o ? { user_guid: o } : {}
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
function E(e, { isMobileWebview: n, webviewScheme: t, userGuid: o, op: s, event: u, extras: c } = {}) {
|
|
43
|
+
const [d, p] = w(e, s), r = {
|
|
44
|
+
...p,
|
|
45
|
+
...o ? { user_guid: o } : {},
|
|
46
|
+
...c
|
|
47
|
+
}, i = u || d;
|
|
48
|
+
n ? _(i, r, t) : m(i, r);
|
|
48
49
|
}
|
|
49
50
|
export {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
l as BEAT_CTA_EVENT,
|
|
52
|
+
g as BEAT_OPERATION_1,
|
|
53
|
+
x as BEAT_OPERATION_2,
|
|
54
|
+
w as getBaseCtaPostMessage,
|
|
55
|
+
f as getReferrer,
|
|
56
|
+
y as postClientConfiguredCta,
|
|
57
|
+
T as postInsightOnTransaction,
|
|
56
58
|
a as postMessageToParent,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
E as sendBeatCta,
|
|
60
|
+
m as sendBeatPostMessage,
|
|
61
|
+
_ as setWebviewURL
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
//# sourceMappingURL=PostMessage.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostMessage.es.js","names":[],"sources":["../../../src/common/utils/PostMessage.ts"],"sourcesContent":["export function getReferrer(): string {\n if (window.opener && window.opener.location) {\n return window.opener.location.toString()\n } else {\n return document.referrer\n }\n}\n\nexport interface PostMessageData {\n metadata?: Record<string, any>\n mx: boolean\n type: string\n [key: string]: any\n}\n\nexport function postMessageToParent(message: PostMessageData): boolean {\n const postUrl = getReferrer().replace(/([^:]+:\\/\\/[^\\/]+).*/, '$1') // eslint-disable-line\n const otherWindow = window.parent || window.opener || false\n\n if (otherWindow && otherWindow.postMessage && postUrl) {\n otherWindow.postMessage(message, postUrl)\n return true\n }\n\n return false\n}\n\nexport function postInsightOnTransaction(beatGuid: string): boolean {\n const message: PostMessageData = {\n metadata: { beat_guid: beatGuid },\n mx: true,\n type: 'mx/moneyDashboard/insightOnTransaction',\n }\n\n return postMessageToParent(message)\n}\n\nexport function postClientConfiguredCta(template: string): boolean {\n const message: PostMessageData = {\n metadata: { template: template },\n mx: true,\n type: 'mx/pulse/client_configured_cta',\n }\n\n return postMessageToParent(message)\n}\n\n// ============================================================================\n// Beat CTA post messages (ported from Pulse: src/utils/PostMessage.js +\n// src/constants/PostMessage.js + src/epics/PostMessage.js).\n//\n// Scope: currently only the Save an extra $100 beat uses this. When a beat CTA\n// has no drilldown drawer, clicking it notifies the host app via a beat/cta\n// post message instead of opening a drawer. Native (mobile webview) hosts can't\n// receive post messages, so we navigate the webview to a custom-scheme URL.\n// ============================================================================\n\nexport const BEAT_CTA_EVENT = 'beat/cta'\nexport const BEAT_OPERATION_1 = 'op_1'\nexport const BEAT_OPERATION_2 = 'op_2'\nexport const BEAT_OPERATION_3 = 'op_3'\n\n/** Minimal beat shape needed to build a CTA post message. */\nexport interface BeatCtaSource {\n guid: string\n template: string\n payload?: { action_string?: string } | null\n}\n\nexport interface BeatCtaPayload {\n action: string\n beat_guid: string\n beat_template: string\n action_string?: string\n [key: string]: unknown\n}\n\n/**\n * Build the `[event, payload]` tuple for a beat CTA post message.\n * Mirrors Pulse's `getBaseCtaPostMessage`.\n */\nexport function getBaseCtaPostMessage(\n beat: BeatCtaSource,\n op: string = BEAT_OPERATION_1,\n): [string, BeatCtaPayload] {\n const payload: BeatCtaPayload = {\n action: op,\n beat_guid: beat.guid,\n beat_template: beat.template,\n }\n\n if (beat?.payload?.action_string) {\n payload.action_string = beat.payload.action_string\n }\n\n return [BEAT_CTA_EVENT, payload]\n}\n\n/**\n * Send a beat post message to the parent window. Only posts when embedded in an\n * iframe (`window.self !== window.top`), mirroring Pulse's `sendPostMessage`\n * (default scheme `mx/pulse`). Returns false when not embedded or no target.\n */\nexport function sendBeatPostMessage(\n event: string,\n data: Record<string, unknown>,\n scheme = 'mx/pulse',\n): boolean {\n if (window.self === window.top) {\n return false\n }\n\n const message: PostMessageData = {\n metadata: data,\n mx: true,\n type: `${scheme}/${event}`,\n }\n\n return postMessageToParent(message)\n}\n\n/**\n * Navigate a mobile webview to a custom-scheme URL instead of posting a message.\n * Mirrors Pulse's `setWebviewURL` (default scheme `mx`).\n */\nexport function setWebviewURL(event: string, data?: Record<string, unknown>, scheme = 'mx'): void {\n let webviewUrl = `${scheme}://pulse/${event}`\n\n if (data) {\n webviewUrl += '?metadata=' + encodeURIComponent(JSON.stringify(data))\n }\n\n window.location.href = webviewUrl\n}\n\nexport interface SendBeatCtaOptions {\n /** True when running inside a native mobile webview. */\n isMobileWebview?: boolean\n /** Custom URL scheme for webview navigation (from client config). */\n webviewScheme?: string\n /** Current user guid, added to the metadata like the Pulse epic does. */\n userGuid?: string\n /** Operation string sent with the CTA (defaults to op_1). */\n op?: string\n}\n\n/**\n * Send a beat CTA to the host: builds the payload, adds `user_guid`, then either\n * posts a message to the host or navigates the webview. Mirrors the behaviour of\n * Pulse's `postMessages` epic (minus the RxJS buffering).\n */\nexport function sendBeatCta(\n beat: BeatCtaSource,\n { isMobileWebview, webviewScheme, userGuid, op }: SendBeatCtaOptions = {},\n): void {\n const [
|
|
1
|
+
{"version":3,"file":"PostMessage.es.js","names":[],"sources":["../../../src/common/utils/PostMessage.ts"],"sourcesContent":["export function getReferrer(): string {\n if (window.opener && window.opener.location) {\n return window.opener.location.toString()\n } else {\n return document.referrer\n }\n}\n\nexport interface PostMessageData {\n metadata?: Record<string, any>\n mx: boolean\n type: string\n [key: string]: any\n}\n\nexport function postMessageToParent(message: PostMessageData): boolean {\n const postUrl = getReferrer().replace(/([^:]+:\\/\\/[^\\/]+).*/, '$1') // eslint-disable-line\n const otherWindow = window.parent || window.opener || false\n\n if (otherWindow && otherWindow.postMessage && postUrl) {\n otherWindow.postMessage(message, postUrl)\n return true\n }\n\n return false\n}\n\nexport function postInsightOnTransaction(beatGuid: string): boolean {\n const message: PostMessageData = {\n metadata: { beat_guid: beatGuid },\n mx: true,\n type: 'mx/moneyDashboard/insightOnTransaction',\n }\n\n return postMessageToParent(message)\n}\n\nexport function postClientConfiguredCta(template: string): boolean {\n const message: PostMessageData = {\n metadata: { template: template },\n mx: true,\n type: 'mx/pulse/client_configured_cta',\n }\n\n return postMessageToParent(message)\n}\n\n// ============================================================================\n// Beat CTA post messages (ported from Pulse: src/utils/PostMessage.js +\n// src/constants/PostMessage.js + src/epics/PostMessage.js).\n//\n// Scope: currently only the Save an extra $100 beat uses this. When a beat CTA\n// has no drilldown drawer, clicking it notifies the host app via a beat/cta\n// post message instead of opening a drawer. Native (mobile webview) hosts can't\n// receive post messages, so we navigate the webview to a custom-scheme URL.\n// ============================================================================\n\nexport const BEAT_CTA_EVENT = 'beat/cta'\nexport const BEAT_OPERATION_1 = 'op_1'\nexport const BEAT_OPERATION_2 = 'op_2'\nexport const BEAT_OPERATION_3 = 'op_3'\n\n/** Minimal beat shape needed to build a CTA post message. */\nexport interface BeatCtaSource {\n guid: string\n template: string\n payload?: { action_string?: string } | null\n}\n\nexport interface BeatCtaPayload {\n action: string\n beat_guid: string\n beat_template: string\n action_string?: string\n [key: string]: unknown\n}\n\n/**\n * Build the `[event, payload]` tuple for a beat CTA post message.\n * Mirrors Pulse's `getBaseCtaPostMessage`.\n */\nexport function getBaseCtaPostMessage(\n beat: BeatCtaSource,\n op: string = BEAT_OPERATION_1,\n): [string, BeatCtaPayload] {\n const payload: BeatCtaPayload = {\n action: op,\n beat_guid: beat.guid,\n beat_template: beat.template,\n }\n\n if (beat?.payload?.action_string) {\n payload.action_string = beat.payload.action_string\n }\n\n return [BEAT_CTA_EVENT, payload]\n}\n\n/**\n * Send a beat post message to the parent window. Only posts when embedded in an\n * iframe (`window.self !== window.top`), mirroring Pulse's `sendPostMessage`\n * (default scheme `mx/pulse`). Returns false when not embedded or no target.\n */\nexport function sendBeatPostMessage(\n event: string,\n data: Record<string, unknown>,\n scheme = 'mx/pulse',\n): boolean {\n if (window.self === window.top) {\n return false\n }\n\n const message: PostMessageData = {\n metadata: data,\n mx: true,\n type: `${scheme}/${event}`,\n }\n\n return postMessageToParent(message)\n}\n\n/**\n * Navigate a mobile webview to a custom-scheme URL instead of posting a message.\n * Mirrors Pulse's `setWebviewURL` (default scheme `mx`).\n */\nexport function setWebviewURL(event: string, data?: Record<string, unknown>, scheme = 'mx'): void {\n let webviewUrl = `${scheme}://pulse/${event}`\n\n if (data) {\n webviewUrl += '?metadata=' + encodeURIComponent(JSON.stringify(data))\n }\n\n window.location.href = webviewUrl\n}\n\nexport interface SendBeatCtaOptions {\n /** True when running inside a native mobile webview. */\n isMobileWebview?: boolean\n /** Custom URL scheme for webview navigation (from client config). */\n webviewScheme?: string\n /** Current user guid, added to the metadata like the Pulse epic does. */\n userGuid?: string\n /** Operation string sent with the CTA (defaults to op_1). */\n op?: string\n /** Post message event name (defaults to `beat/cta`). */\n event?: string\n /** Beat-specific fields merged into the metadata after the base payload and `user_guid`. */\n extras?: Record<string, unknown>\n}\n\n/**\n * Send a beat CTA to the host: builds the payload, adds `user_guid`, then either\n * posts a message to the host or navigates the webview. Mirrors the behaviour of\n * Pulse's `postMessages` epic (minus the RxJS buffering).\n */\nexport function sendBeatCta(\n beat: BeatCtaSource,\n { isMobileWebview, webviewScheme, userGuid, op, event, extras }: SendBeatCtaOptions = {},\n): void {\n const [defaultEvent, basePayload] = getBaseCtaPostMessage(beat, op)\n const metadata: BeatCtaPayload = {\n ...basePayload,\n ...(userGuid ? { user_guid: userGuid } : {}),\n ...extras,\n }\n const eventName = event || defaultEvent\n\n if (isMobileWebview) {\n setWebviewURL(eventName, metadata, webviewScheme)\n } else {\n sendBeatPostMessage(eventName, metadata)\n }\n}\n"],"mappings":"AAAA,SAAgB,IAAsB;AACpC,SAAI,OAAO,UAAU,OAAO,OAAO,WAC1B,OAAO,OAAO,SAAS,SAAS,IAEhC,SAAS;AAEpB;AASA,SAAgB,EAAoB,GAAmC;AACrE,QAAM,IAAU,EAAY,EAAE,QAAQ,wBAAwB,IAAI,GAC5D,IAAc,OAAO,UAAU,OAAO,UAAU;AAEtD,SAAI,KAAe,EAAY,eAAe,KAC5C,EAAY,YAAY,GAAS,CAAO,GACjC,MAGF;AACT;AAEA,SAAgB,EAAyB,GAA2B;AAOlE,SAAO,EAAoB;AAAA,IALzB,UAAU,EAAE,WAAW,EAAS;AAAA,IAChC,IAAI;AAAA,IACJ,MAAM;AAAA,EAGmB,CAAO;AACpC;AAEA,SAAgB,EAAwB,GAA2B;AAOjE,SAAO,EAAoB;AAAA,IALzB,UAAU,EAAY,UAAA,EAAS;AAAA,IAC/B,IAAI;AAAA,IACJ,MAAM;AAAA,EAGmB,CAAO;AACpC;AAYA,IAAa,IAAiB,YACjB,IAAmB,QACnB,IAAmB;AAsBhC,SAAgB,EACd,GACA,IAAa,GACa;AAC1B,QAAM,IAA0B;AAAA,IAC9B,QAAQ;AAAA,IACR,WAAW,EAAK;AAAA,IAChB,eAAe,EAAK;AAAA,EACtB;AAEA,SAAI,GAAM,SAAS,kBACjB,EAAQ,gBAAgB,EAAK,QAAQ,gBAGhC,CAAC,GAAgB,CAAO;AACjC;AAOA,SAAgB,EACd,GACA,GACA,IAAS,YACA;AACT,SAAI,OAAO,SAAS,OAAO,MAClB,KASF,EAAoB;AAAA,IALzB,UAAU;AAAA,IACV,IAAI;AAAA,IACJ,MAAM,GAAG,CAAA,IAAU,CAAA;AAAA,EAGM,CAAO;AACpC;AAMA,SAAgB,EAAc,GAAe,GAAgC,IAAS,MAAY;AAChG,MAAI,IAAa,GAAG,CAAA,YAAkB,CAAA;AAEtC,EAAI,MACF,KAAc,eAAe,mBAAmB,KAAK,UAAU,CAAI,CAAC,IAGtE,OAAO,SAAS,OAAO;AACzB;AAsBA,SAAgB,EACd,GACA,EAAE,iBAAA,GAAiB,eAAA,GAAe,UAAA,GAAU,IAAA,GAAI,OAAA,GAAO,QAAA,EAAA,IAA+B,CAAC,GACjF;AACN,QAAM,CAAC,GAAc,CAAA,IAAe,EAAsB,GAAM,CAAE,GAC5D,IAA2B;AAAA,IAC/B,GAAG;AAAA,IACH,GAAI,IAAW,EAAE,WAAW,EAAS,IAAI,CAAC;AAAA,IAC1C,GAAG;AAAA,EACL,GACM,IAAY,KAAS;AAE3B,EAAI,IACF,EAAc,GAAW,GAAU,CAAa,IAEhD,EAAoB,GAAW,CAAQ;AAE3C"}
|
|
@@ -161,6 +161,21 @@ export interface Description {
|
|
|
161
161
|
text: string;
|
|
162
162
|
html_text: string;
|
|
163
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Post message a CTA press sends to the host, resolved by the backend for the beat's current
|
|
166
|
+
* state. Absent/null = the backend has not described one: the frontend uses the template's
|
|
167
|
+
* interim `ctaPostMessages` entry if there is one, else sends no message. `payload` carries only
|
|
168
|
+
* beat-specific extras; the frontend always adds `beat_guid`, `beat_template`, `user_guid` and
|
|
169
|
+
* `action_string`.
|
|
170
|
+
*/
|
|
171
|
+
export interface CtaPostMessageMetadata {
|
|
172
|
+
/** Post message event; defaults to `beat/cta`. */
|
|
173
|
+
event?: string | null;
|
|
174
|
+
/** Operation the host should perform, e.g. `op_1`, `op_2`, `op_3`. Defaults to `op_1`. */
|
|
175
|
+
action?: string | null;
|
|
176
|
+
/** Beat-specific fields merged into the message metadata (account ids, amounts, ...). */
|
|
177
|
+
payload?: Record<string, unknown> | null;
|
|
178
|
+
}
|
|
164
179
|
export interface PrimaryCTA {
|
|
165
180
|
label: string;
|
|
166
181
|
block: string;
|
|
@@ -171,6 +186,7 @@ export interface PrimaryCTA {
|
|
|
171
186
|
* null / absent / unknown means the CTA only opens its block or posts its message.
|
|
172
187
|
*/
|
|
173
188
|
action_key?: string | null;
|
|
189
|
+
metadata?: CtaPostMessageMetadata | null;
|
|
174
190
|
}
|
|
175
191
|
export interface SecondaryCTA {
|
|
176
192
|
label: string;
|
|
@@ -178,6 +194,7 @@ export interface SecondaryCTA {
|
|
|
178
194
|
variant?: string | null;
|
|
179
195
|
/** See `PrimaryCTA.action_key` (ditto `secondary_cta_action`). */
|
|
180
196
|
action_key?: string | null;
|
|
197
|
+
metadata?: CtaPostMessageMetadata | null;
|
|
181
198
|
}
|
|
182
199
|
export interface Footer {
|
|
183
200
|
/** V1: a pre-formatted date string. */
|
|
@@ -13,7 +13,7 @@ export type { Expense } from './Expense';
|
|
|
13
13
|
export { ExpenseStatus, ScheduledPaymentTypes } from './Expense';
|
|
14
14
|
export type { DynamicCopyElement, GlobalCopy } from './GlobalCopy';
|
|
15
15
|
export { GoalType, MetaType, TrackType } from './Goal';
|
|
16
|
-
export type { ThemedSVGImageProps, DrilldownData, DrilldownPayload, InsightsPayload, InstanceSlot, Pill, WidgetType, InstanceSlotRow, BlockType, Description, PrimaryCTA, SecondaryCTA, Subtitle, Footer, Logo, LogoRef, CategoryLogo, MerchantLogo, InstitutionLogo, Legends, } from './Insights';
|
|
16
|
+
export type { ThemedSVGImageProps, DrilldownData, DrilldownPayload, InsightsPayload, InstanceSlot, Pill, WidgetType, InstanceSlotRow, BlockType, Description, PrimaryCTA, CtaPostMessageMetadata, SecondaryCTA, Subtitle, Footer, Logo, LogoRef, CategoryLogo, MerchantLogo, InstitutionLogo, Legends, } from './Insights';
|
|
17
17
|
export type { Goal, MonthlyCashFlowProfile, RetirementGoalAccount } from './Goal';
|
|
18
18
|
export type { Debt } from './Debt';
|
|
19
19
|
export type { GlobalCaptureException, GlobalErrorHandler, GlobalEventHandler } from './Handlers';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseLayout.es.js","names":[],"sources":["../../../src/insights-v2/components/BaseLayout.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite'\n\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport Stack from '@mui/material/Stack'\n\nimport { ChevronRight } from '@mxenabled/mx-icons'\nimport { P, Text } from '@mxenabled/mxui'\n\nimport { blocks } from '../mappings/blocks'\nimport { actionBlocksKey } from '../mappings/actionBlocksKey'\nimport { isContactSupportCtaDisabled } from '../actionBlocks/ContactSupportActionBlock'\nimport { logoFromRef } from './shared/EntityLogo'\n\nimport Footer from './Footer'\nimport Header from './Header'\nimport FeedbackBar from './FeedbackBar'\nimport FeedbackDialog from './FeedbackDialog'\nimport DismissInsightDialog from './DismissInsightDialog'\nimport HiddenConfirmationCard from './HiddenConfirmationCard'\nimport ErrorCard from './ErrorCard'\nimport LoadingCard from './LoadingCard'\nimport { ObsolesceBanner, type ObsolesceBannerState } from './ObsolesceBanner'\nimport { useAppConfig, useGlobalCopyStore } from '../../common'\nimport type { Beat, PrimaryCTA, SecondaryCTA } from '../../core'\nimport { BeatActionsProvider, type BeatActions } from '../beatActions'\nimport { ActionBlockLayer } from '../actionBlocks/ActionBlockLayer'\nimport { useBeatCtaActions } from '../ctaActions'\nimport { useBeatFeedbackFlow } from '../hooks/useBeatFeedbackFlow'\n\nexport interface ObsoleteState {\n state: ObsolesceBannerState\n onRefresh: () => void\n onDismiss: () => void\n}\n\ninterface BaseLayoutProps {\n beat: Beat\n beatActions?: BeatActions\n obsolete?: ObsoleteState\n /** Swap only the block slot for a LoadingCard (e.g. while a sampled beat loads). */\n isLoading?: boolean\n /** Replace the entire card with an ErrorCard (e.g. when the sample fails to load). */\n isError?: boolean\n}\n\nconst NO_BEAT_ACTIONS: BeatActions = {}\n\nconst OBSOLETE_OVERLAY_SX = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n backgroundColor: 'background.default',\n opacity: 0.7,\n zIndex: 1,\n} as const\n\n// Figma (Mini | Large Transaction): a left-aligned link under the description. The 2px padding plus\n// the CTA row's 16 lines the label up with the description's 18. The label is not a Button, so it\n// carries the theme's MuiButton look itself: semi-bold, no uppercase (typography.button uppercases),\n// and the medium Button's 44px height with the text centred.\nconst MINI_CTA_SX = {\n alignItems: 'center',\n alignSelf: 'flex-start',\n display: 'flex',\n fontWeight: 600,\n height: 44,\n px: 2,\n textTransform: 'none',\n} as const\n\nconst BaseLayout = observer(\n ({\n beat,\n beatActions = NO_BEAT_ACTIONS,\n obsolete,\n isLoading = false,\n isError = false,\n }: BaseLayoutProps) => {\n const copy = useGlobalCopyStore()\n const { config } = useAppConfig()\n const flow = useBeatFeedbackFlow(beat, beatActions)\n const {\n runCta: handleCtaClick,\n primary: primaryCtaState,\n secondary: secondaryCtaState,\n } = useBeatCtaActions(\n beat,\n beatActions,\n beat.insights_payload?.primary_cta,\n beat.insights_payload?.secondary_cta,\n )\n\n // Error replaces the entire card; loading only swaps the block slot (below).\n if (isError) return <ErrorCard />\n\n const insights_payload = beat.insights_payload\n\n if (!insights_payload) return null\n if (flow.cardState === 'dismissed') return null\n\n const {\n instance_slot,\n primary_cta,\n secondary_cta,\n sub_title,\n title,\n widget_type,\n guid,\n dismissed_at,\n } = insights_payload\n const { block } = instance_slot\n\n // V1 sends `sub_title: { text, logo }`. The V2 envelope flattens it to a `subtitle` string\n // with the logo promoted to a payload-level `LogoRef` (an entity guid or a status icon), so\n // rebuild the shape `Header`/`SubHeader` already render. `sub_title` wins when a beat sends both.\n const subtitle =\n sub_title ??\n (insights_payload.subtitle\n ? {\n logo: logoFromRef(insights_payload.logo),\n text: insights_payload.subtitle,\n }\n : undefined)\n\n // CTA presses go through the ctaActions runner (../ctaActions): a backend `action_key` runs\n // its step, otherwise a `block` opens its drawer, else the CTA posts its message.\n // `contact_support_block` bypasses the runner: the help flow is host-supplied via\n // `beatActions.onContactSupport`, and the CTA is disabled without it.\n const isCtaDisabled = (ctaBlock: string | null | undefined) =>\n isContactSupportCtaDisabled(ctaBlock, config, beatActions.onContactSupport)\n\n const onCtaClick = async (slot: 'primary' | 'secondary', cta: PrimaryCTA | SecondaryCTA) => {\n if (isCtaDisabled(cta.block)) return\n\n if (cta.block === actionBlocksKey.CONTACT_SUPPORT_ACTION_BLOCK) {\n beatActions.onContactSupport?.()\n return\n }\n\n await handleCtaClick(slot, cta)\n }\n\n const Block = blocks[block as keyof typeof blocks]\n\n const isMini = widget_type === 'mini'\n const obsoleteCopy = copy.insights_feed?.general\n const isObsoleteDimmed =\n obsolete?.state === 'not_refreshable' && !dismissed_at && !!obsoleteCopy\n\n return (\n <BeatActionsProvider value={flow.wrappedActions}>\n <Stack sx={{ mb: 16 }}>\n {flow.cardState === 'confirming' ? (\n <HiddenConfirmationCard\n onClose={flow.collapseConfirmation}\n onLeaveFeedback={flow.openHideSurvey}\n onUndo={flow.undoDismiss}\n />\n ) : (\n <>\n {obsolete && obsoleteCopy && <ObsolesceBanner {...obsolete} copy={obsoleteCopy} />}\n <Card\n inert={isObsoleteDimmed}\n sx={{\n ...(isObsoleteDimmed && {\n position: 'relative',\n zIndex: 2,\n pointerEvents: 'none',\n }),\n }}\n >\n {isObsoleteDimmed && <Box sx={OBSOLETE_OVERLAY_SX} />}\n <Header isMini={isMini} subtitle={subtitle} title={title ?? ''} />\n {isLoading ? (\n <LoadingCard />\n ) : (\n Block && <Block beatGuid={guid} payload={insights_payload} />\n )}\n\n <Stack sx={{ gap: 6, pb: 16, pt: 12, px: 16 }}>\n <P\n dangerouslySetInnerHTML={{\n __html: insights_payload.description.html_text,\n }}\n variant=\"subtitle2\"\n />\n {/* TODO: Migrate to insights_payload.in_description_cta once backend provides it */}\n {beat?.in_description_cta && (\n <Button\n endIcon={<ChevronRight />}\n onClick={() => {}}\n sx={{ height: 24, p: 4, width: 'max-content' }}\n >\n {beat.in_description_cta}\n </Button>\n )}\n </Stack>\n\n {primary_cta?.label && (\n <Stack sx={{ gap: 8, pb: 16, px: 16 }}>\n {isMini ? (\n // Mini is a tap target, as in V1: the card, not this label, is the control. The label\n // is deliberately not a button, so a tap on it reaches the host's card handler (Pulse\n // posts `carousel/cta`, the same redirect a V1 mini card sends) instead of opening\n // the action drawer the full card's CTA opens.\n <Text color=\"primary.main\" sx={MINI_CTA_SX} variant=\"button\">\n {primary_cta.label}\n </Text>\n ) : (\n <Button\n aria-disabled={isCtaDisabled(primary_cta.block) || primaryCtaState.disabled}\n disabled={primaryCtaState.disabled}\n onClick={() => onCtaClick('primary', primary_cta)}\n sx={{\n ...(isCtaDisabled(primary_cta.block) && { color: 'text.disabled' }),\n }}\n variant=\"outlined\"\n >\n {primary_cta.label}\n </Button>\n )}\n {/* Mini has room for one CTA. */}\n {!isMini && secondary_cta?.label && (\n <Button\n aria-disabled={\n isCtaDisabled(secondary_cta.block) || secondaryCtaState.disabled\n }\n color=\"secondary\"\n disabled={secondaryCtaState.disabled}\n onClick={() => onCtaClick('secondary', secondary_cta)}\n >\n {secondary_cta.label}\n </Button>\n )}\n </Stack>\n )}\n\n <FeedbackBar onTellUsMore={flow.openRatingSurvey} show={flow.isFeedbackBarShown} />\n </Card>\n </>\n )}\n\n <ActionBlockLayer beat={beat} />\n\n <DismissInsightDialog\n isOpen={flow.isDismissOpen}\n onClose={flow.closeDismissDialog}\n onDismiss={flow.confirmDismiss}\n />\n\n {flow.isSurveyOpen && (\n <FeedbackDialog\n characterLimitLabel={flow.survey.characterLimitLabel}\n closeAriaLabel={flow.survey.closeAriaLabel}\n isOpen={true}\n onBackClick={flow.feedbackDialogCallbacks.onBackClick}\n onClose={flow.closeSurvey}\n onSubmit={flow.submitSurvey}\n onTextInputFocus={flow.feedbackDialogCallbacks.onTextInputFocus}\n options={flow.survey.options}\n primaryText={flow.survey.submitText}\n secondaryText={flow.survey.backText}\n title={flow.survey.title}\n />\n )}\n\n {flow.cardState === 'visible' && insights_payload.footer && (\n <Footer footer={insights_payload.footer} />\n )}\n </Stack>\n </BeatActionsProvider>\n )\n },\n)\n\nexport default BaseLayout\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BaseLayout.es.js","names":[],"sources":["../../../src/insights-v2/components/BaseLayout.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite'\n\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport Stack from '@mui/material/Stack'\n\nimport { ChevronRight } from '@mxenabled/mx-icons'\nimport { P, Text } from '@mxenabled/mxui'\n\nimport { blocks } from '../mappings/blocks'\nimport { actionBlocksKey } from '../mappings/actionBlocksKey'\nimport { isContactSupportCtaDisabled } from '../actionBlocks/ContactSupportActionBlock'\nimport { logoFromRef } from './shared/EntityLogo'\n\nimport Footer from './Footer'\nimport Header from './Header'\nimport FeedbackBar from './FeedbackBar'\nimport FeedbackDialog from './FeedbackDialog'\nimport DismissInsightDialog from './DismissInsightDialog'\nimport HiddenConfirmationCard from './HiddenConfirmationCard'\nimport ErrorCard from './ErrorCard'\nimport LoadingCard from './LoadingCard'\nimport { ObsolesceBanner, type ObsolesceBannerState } from './ObsolesceBanner'\nimport { useAppConfig, useGlobalCopyStore } from '../../common'\nimport type { Beat, PrimaryCTA, SecondaryCTA } from '../../core'\nimport { BeatActionsProvider, type BeatActions } from '../beatActions'\nimport { ActionBlockLayer } from '../actionBlocks/ActionBlockLayer'\nimport { useBeatCtaActions } from '../ctaActions'\nimport { useBeatFeedbackFlow } from '../hooks/useBeatFeedbackFlow'\n\nexport interface ObsoleteState {\n state: ObsolesceBannerState\n onRefresh: () => void\n onDismiss: () => void\n}\n\ninterface BaseLayoutProps {\n beat: Beat\n beatActions?: BeatActions\n obsolete?: ObsoleteState\n /** Swap only the block slot for a LoadingCard (e.g. while a sampled beat loads). */\n isLoading?: boolean\n /** Replace the entire card with an ErrorCard (e.g. when the sample fails to load). */\n isError?: boolean\n}\n\nconst NO_BEAT_ACTIONS: BeatActions = {}\n\nconst OBSOLETE_OVERLAY_SX = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n backgroundColor: 'background.default',\n opacity: 0.7,\n zIndex: 1,\n} as const\n\n// Figma (Mini | Large Transaction): a left-aligned link under the description. The 2px padding plus\n// the CTA row's 16 lines the label up with the description's 18. The label is not a Button, so it\n// carries the theme's MuiButton look itself: semi-bold, no uppercase (typography.button uppercases),\n// and the medium Button's 44px height with the text centred.\nconst MINI_CTA_SX = {\n alignItems: 'center',\n alignSelf: 'flex-start',\n display: 'flex',\n fontWeight: 600,\n height: 44,\n px: 2,\n textTransform: 'none',\n} as const\n\nconst BaseLayout = observer(\n ({\n beat,\n beatActions = NO_BEAT_ACTIONS,\n obsolete,\n isLoading = false,\n isError = false,\n }: BaseLayoutProps) => {\n const copy = useGlobalCopyStore()\n const { config } = useAppConfig()\n const flow = useBeatFeedbackFlow(beat, beatActions)\n const {\n runCta: handleCtaClick,\n primary: primaryCtaState,\n secondary: secondaryCtaState,\n } = useBeatCtaActions(\n beat,\n beatActions,\n beat.insights_payload?.primary_cta,\n beat.insights_payload?.secondary_cta,\n )\n\n // Error replaces the entire card; loading only swaps the block slot (below).\n if (isError) return <ErrorCard />\n\n const insights_payload = beat.insights_payload\n\n if (!insights_payload) return null\n if (flow.cardState === 'dismissed') return null\n\n const {\n instance_slot,\n primary_cta,\n secondary_cta,\n sub_title,\n title,\n widget_type,\n guid,\n dismissed_at,\n } = insights_payload\n const { block } = instance_slot\n\n // V1 sends `sub_title: { text, logo }`. The V2 envelope flattens it to a `subtitle` string\n // with the logo promoted to a payload-level `LogoRef` (an entity guid or a status icon), so\n // rebuild the shape `Header`/`SubHeader` already render. `sub_title` wins when a beat sends both.\n const subtitle =\n sub_title ??\n (insights_payload.subtitle\n ? {\n logo: logoFromRef(insights_payload.logo),\n text: insights_payload.subtitle,\n }\n : undefined)\n\n // CTA presses go through the ctaActions runner (../ctaActions): a backend `action_key` runs\n // its step, a `block` opens its drawer, and a resolved message (backend `metadata`, else the\n // template's `ctaPostMessages` entry) is posted. `contact_support_block` bypasses the runner:\n // the help flow is host-supplied via `beatActions.onContactSupport`, disabled without it.\n const isCtaDisabled = (ctaBlock: string | null | undefined) =>\n isContactSupportCtaDisabled(ctaBlock, config, beatActions.onContactSupport)\n\n const onCtaClick = async (slot: 'primary' | 'secondary', cta: PrimaryCTA | SecondaryCTA) => {\n if (isCtaDisabled(cta.block)) return\n\n if (cta.block === actionBlocksKey.CONTACT_SUPPORT_ACTION_BLOCK) {\n beatActions.onContactSupport?.()\n return\n }\n\n await handleCtaClick(slot, cta)\n }\n\n const Block = blocks[block as keyof typeof blocks]\n\n const isMini = widget_type === 'mini'\n const obsoleteCopy = copy.insights_feed?.general\n const isObsoleteDimmed =\n obsolete?.state === 'not_refreshable' && !dismissed_at && !!obsoleteCopy\n\n return (\n <BeatActionsProvider value={flow.wrappedActions}>\n <Stack sx={{ mb: 16 }}>\n {flow.cardState === 'confirming' ? (\n <HiddenConfirmationCard\n onClose={flow.collapseConfirmation}\n onLeaveFeedback={flow.openHideSurvey}\n onUndo={flow.undoDismiss}\n />\n ) : (\n <>\n {obsolete && obsoleteCopy && <ObsolesceBanner {...obsolete} copy={obsoleteCopy} />}\n <Card\n inert={isObsoleteDimmed}\n sx={{\n ...(isObsoleteDimmed && {\n position: 'relative',\n zIndex: 2,\n pointerEvents: 'none',\n }),\n }}\n >\n {isObsoleteDimmed && <Box sx={OBSOLETE_OVERLAY_SX} />}\n <Header isMini={isMini} subtitle={subtitle} title={title ?? ''} />\n {isLoading ? (\n <LoadingCard />\n ) : (\n Block && <Block beatGuid={guid} payload={insights_payload} />\n )}\n\n <Stack sx={{ gap: 6, pb: 16, pt: 12, px: 16 }}>\n <P\n dangerouslySetInnerHTML={{\n __html: insights_payload.description.html_text,\n }}\n variant=\"subtitle2\"\n />\n {/* TODO: Migrate to insights_payload.in_description_cta once backend provides it */}\n {beat?.in_description_cta && (\n <Button\n endIcon={<ChevronRight />}\n onClick={() => {}}\n sx={{ height: 24, p: 4, width: 'max-content' }}\n >\n {beat.in_description_cta}\n </Button>\n )}\n </Stack>\n\n {primary_cta?.label && (\n <Stack sx={{ gap: 8, pb: 16, px: 16 }}>\n {isMini ? (\n // Mini is a tap target, as in V1: the card, not this label, is the control. The label\n // is deliberately not a button, so a tap on it reaches the host's card handler (Pulse\n // posts `carousel/cta`, the same redirect a V1 mini card sends) instead of opening\n // the action drawer the full card's CTA opens.\n <Text color=\"primary.main\" sx={MINI_CTA_SX} variant=\"button\">\n {primary_cta.label}\n </Text>\n ) : (\n <Button\n aria-disabled={isCtaDisabled(primary_cta.block) || primaryCtaState.disabled}\n disabled={primaryCtaState.disabled}\n onClick={() => onCtaClick('primary', primary_cta)}\n sx={{\n ...(isCtaDisabled(primary_cta.block) && { color: 'text.disabled' }),\n }}\n variant=\"outlined\"\n >\n {primary_cta.label}\n </Button>\n )}\n {/* Mini has room for one CTA. */}\n {!isMini && secondary_cta?.label && (\n <Button\n aria-disabled={\n isCtaDisabled(secondary_cta.block) || secondaryCtaState.disabled\n }\n color=\"secondary\"\n disabled={secondaryCtaState.disabled}\n onClick={() => onCtaClick('secondary', secondary_cta)}\n >\n {secondary_cta.label}\n </Button>\n )}\n </Stack>\n )}\n\n <FeedbackBar onTellUsMore={flow.openRatingSurvey} show={flow.isFeedbackBarShown} />\n </Card>\n </>\n )}\n\n <ActionBlockLayer beat={beat} />\n\n <DismissInsightDialog\n isOpen={flow.isDismissOpen}\n onClose={flow.closeDismissDialog}\n onDismiss={flow.confirmDismiss}\n />\n\n {flow.isSurveyOpen && (\n <FeedbackDialog\n characterLimitLabel={flow.survey.characterLimitLabel}\n closeAriaLabel={flow.survey.closeAriaLabel}\n isOpen={true}\n onBackClick={flow.feedbackDialogCallbacks.onBackClick}\n onClose={flow.closeSurvey}\n onSubmit={flow.submitSurvey}\n onTextInputFocus={flow.feedbackDialogCallbacks.onTextInputFocus}\n options={flow.survey.options}\n primaryText={flow.survey.submitText}\n secondaryText={flow.survey.backText}\n title={flow.survey.title}\n />\n )}\n\n {flow.cardState === 'visible' && insights_payload.footer && (\n <Footer footer={insights_payload.footer} />\n )}\n </Stack>\n </BeatActionsProvider>\n )\n },\n)\n\nexport default BaseLayout\n"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CtaStep } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Steps keyed by the backend-named `cta.action_key` (ditto `*_cta_action`, via tupac). No key or
|
|
4
|
-
* an unknown key = default: open the CTA's block drawer,
|
|
5
|
-
*
|
|
4
|
+
* an unknown key = default: open the CTA's block drawer, if any. Its post message, when one
|
|
5
|
+
* resolves (`cta.metadata` / `ctaPostMessages`), is sent either way. A new key is registered in
|
|
6
|
+
* ditto and here in the same change; most CTAs need none. See README.md.
|
|
6
7
|
*/
|
|
7
8
|
export declare const ctaActions: Record<string, CtaStep>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ctaConfig.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/ctaConfig.ts"],"sourcesContent":["import { HOST_ACTIONS } from './hostActions'\nimport type { CtaStep } from './types'\n\n/**\n * Steps keyed by the backend-named `cta.action_key` (ditto `*_cta_action`, via tupac). No key or\n * an unknown key = default: open the CTA's block drawer,
|
|
1
|
+
{"version":3,"file":"ctaConfig.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/ctaConfig.ts"],"sourcesContent":["import { HOST_ACTIONS } from './hostActions'\nimport type { CtaStep } from './types'\n\n/**\n * Steps keyed by the backend-named `cta.action_key` (ditto `*_cta_action`, via tupac). No key or\n * an unknown key = default: open the CTA's block drawer, if any. Its post message, when one\n * resolves (`cta.metadata` / `ctaPostMessages`), is sent either way. A new key is registered in\n * ditto and here in the same change; most CTAs need none. See README.md.\n */\nexport const ctaActions: Record<string, CtaStep> = {\n /** ConnectDiscoveredAccounts \"I don't have any\": dismiss through the host, then stop. */\n dismiss_beat: {\n run: 'delegateToHost',\n params: { hostAction: HOST_ACTIONS.dismissBeat },\n },\n}\n"],"mappings":";AASA,IAAa,IAAsC,EAEjD,cAAc;AAAA,EACZ,KAAK;AAAA,EACL,QAAQ,EAAE,YAAY,EAAa,YAAY;AACjD,EACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CtaPostMessageMetadata } from '../../core';
|
|
2
|
+
import { CtaSlot, CtaStateContext } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Interim per-template post messages, carried over from the pulse beats, for CTAs the backend
|
|
5
|
+
* does not yet describe with `cta.metadata`. Each resolver returns the same shape the backend
|
|
6
|
+
* will send, or `null` for "no message" (the default then only opens the drawer for a block).
|
|
7
|
+
* Backend `metadata` always wins, so entries become dead the moment tupac ships the key for
|
|
8
|
+
* their template.
|
|
9
|
+
*
|
|
10
|
+
* Remove once every template here has `metadata` from the backend: delete this file, its test
|
|
11
|
+
* and README section, and the `resolveCtaMetadata` call in `runCtaAction.ts`.
|
|
12
|
+
*/
|
|
13
|
+
export type CtaPostMessageResolver = (ctx: CtaStateContext) => CtaPostMessageMetadata | null;
|
|
14
|
+
export declare const ctaPostMessages: Record<string, Partial<Record<CtaSlot, CtaPostMessageResolver>>>;
|
|
15
|
+
/** Backend `cta.metadata` first, else this template's interim resolver for the slot, else null. */
|
|
16
|
+
export declare const resolveCtaMetadata: (ctx: CtaStateContext) => CtaPostMessageMetadata | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AccountType as u } from "../../core/types/Account.es.js";
|
|
2
|
+
import { BEAT_OPERATION_1 as o, BEAT_OPERATION_2 as s } from "../../common/utils/PostMessage.es.js";
|
|
3
|
+
var c = (a) => !a.is_hidden && !a.is_deleted && !a.is_closed, i = ({ beat: a, stores: t }) => a.primary_account ?? t.accountStore.accounts.find((n) => n.guid === a.primary_account_guid), l = ({ beat: a, stores: t }) => {
|
|
4
|
+
const n = new Set(a.account_guids);
|
|
5
|
+
return t.accountStore.accounts.filter((e) => n.has(e.guid) && c(e));
|
|
6
|
+
}, d = ({ stores: a }) => a.accountStore.accounts.filter((t) => t.account_type === u.SAVINGS && c(t)), _ = {
|
|
7
|
+
SaveAnExtra100Dollars: { primary: () => ({ action: o }) },
|
|
8
|
+
LowAccountBalance: { primary: (a) => {
|
|
9
|
+
const t = i(a);
|
|
10
|
+
return t ? {
|
|
11
|
+
action: o,
|
|
12
|
+
payload: { account_guid: t.external_guid }
|
|
13
|
+
} : null;
|
|
14
|
+
} },
|
|
15
|
+
MonthlyObligationsStatus: { primary: ({ beat: a }) => (a.payload?.total_remaining ?? 0) < 0 ? { action: o } : null },
|
|
16
|
+
DesignateEmergencySavingsAccount: { primary: ({ beat: a }) => !a.payload?.emergency_savings_account_guid && !a.account_guids.length ? { action: o } : null },
|
|
17
|
+
ReplenishSavings: { primary: (a) => ({
|
|
18
|
+
action: o,
|
|
19
|
+
payload: {
|
|
20
|
+
total_amount_withdrawn: a.beat.payload?.total_amount,
|
|
21
|
+
accounts: l(a).map((t) => ({
|
|
22
|
+
account_guid: t.external_guid,
|
|
23
|
+
account_name: a.beat.payload?.account_name?.default
|
|
24
|
+
}))
|
|
25
|
+
}
|
|
26
|
+
}) },
|
|
27
|
+
SavingsOpportunityV2: { primary: (a) => {
|
|
28
|
+
const t = i(a);
|
|
29
|
+
if (!t) return null;
|
|
30
|
+
const n = d(a), e = {
|
|
31
|
+
amount: a.beat.payload?.suggested_savings_amount,
|
|
32
|
+
source_account_id: t.external_guid
|
|
33
|
+
};
|
|
34
|
+
return n.length ? {
|
|
35
|
+
action: o,
|
|
36
|
+
payload: {
|
|
37
|
+
...e,
|
|
38
|
+
destination_account_ids: n.map((r) => r.external_guid)
|
|
39
|
+
}
|
|
40
|
+
} : {
|
|
41
|
+
action: s,
|
|
42
|
+
payload: e
|
|
43
|
+
};
|
|
44
|
+
} }
|
|
45
|
+
}, m = (a) => a.cta.metadata ?? _[a.beat.template]?.[a.slot]?.(a) ?? null;
|
|
46
|
+
export {
|
|
47
|
+
_ as ctaPostMessages,
|
|
48
|
+
m as resolveCtaMetadata
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=ctaPostMessages.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ctaPostMessages.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/ctaPostMessages.ts"],"sourcesContent":["import { BEAT_OPERATION_1, BEAT_OPERATION_2 } from '../../common/utils/PostMessage'\nimport { AccountType, type Account, type CtaPostMessageMetadata } from '../../core'\nimport type { CtaSlot, CtaStateContext } from './types'\n\n/**\n * Interim per-template post messages, carried over from the pulse beats, for CTAs the backend\n * does not yet describe with `cta.metadata`. Each resolver returns the same shape the backend\n * will send, or `null` for \"no message\" (the default then only opens the drawer for a block).\n * Backend `metadata` always wins, so entries become dead the moment tupac ships the key for\n * their template.\n *\n * Remove once every template here has `metadata` from the backend: delete this file, its test\n * and README section, and the `resolveCtaMetadata` call in `runCtaAction.ts`.\n */\nexport type CtaPostMessageResolver = (ctx: CtaStateContext) => CtaPostMessageMetadata | null\n\ntype BeatContext = Pick<CtaStateContext, 'beat' | 'stores'>\n\nconst isUsable = (account: Account) =>\n !account.is_hidden && !account.is_deleted && !account.is_closed\n\n/** The beat's primary account: from the beat when the host attached it, else the account store. */\nconst primaryAccountOf = ({ beat, stores }: BeatContext): Account | undefined =>\n beat.primary_account ??\n stores.accountStore.accounts.find((account) => account.guid === beat.primary_account_guid)\n\n/** The accounts the beat points at (`account_guids`), as loaded in the account store. */\nconst beatAccountsOf = ({ beat, stores }: BeatContext): Account[] => {\n const guids = new Set<string>(beat.account_guids)\n\n return stores.accountStore.accounts.filter(\n (account) => guids.has(account.guid) && isUsable(account),\n )\n}\n\nconst savingsAccountsOf = ({ stores }: BeatContext): Account[] =>\n stores.accountStore.accounts.filter(\n (account) => account.account_type === AccountType.SAVINGS && isUsable(account),\n )\n\nexport const ctaPostMessages: Record<string, Partial<Record<CtaSlot, CtaPostMessageResolver>>> = {\n /** \"Make Transfer\": the base `beat/cta` op_1, nothing beat-specific. */\n SaveAnExtra100Dollars: {\n primary: () => ({ action: BEAT_OPERATION_1 }),\n },\n\n /** \"Transfer\": op_1 with the primary account's external id. No primary account → default. */\n LowAccountBalance: {\n primary: (ctx) => {\n const account = primaryAccountOf(ctx)\n\n return account\n ? { action: BEAT_OPERATION_1, payload: { account_guid: account.external_guid } }\n : null\n },\n },\n\n /** Posts only while off track (negative remaining); on track the CTA opens the drawer. */\n MonthlyObligationsStatus: {\n primary: ({ beat }) =>\n (beat.payload?.total_remaining ?? 0) < 0 ? { action: BEAT_OPERATION_1 } : null,\n },\n\n /**\n * \"Open savings account\": only when nothing is designated and there is no savings account to\n * pick from. The other states save the pick through an `action_key` (see `ctaConfig.ts`).\n */\n DesignateEmergencySavingsAccount: {\n primary: ({ beat }) =>\n !beat.payload?.emergency_savings_account_guid && !beat.account_guids.length\n ? { action: BEAT_OPERATION_1 }\n : null,\n },\n\n /** op_1 with the withdrawn total and the beat's accounts (external ids). */\n ReplenishSavings: {\n primary: (ctx) => ({\n action: BEAT_OPERATION_1,\n payload: {\n total_amount_withdrawn: ctx.beat.payload?.total_amount,\n accounts: beatAccountsOf(ctx).map((account) => ({\n account_guid: account.external_guid,\n account_name: ctx.beat.payload?.account_name?.default,\n })),\n },\n }),\n },\n\n /** op_1 = transfer into the user's savings accounts; op_2 = open one when there are none. */\n SavingsOpportunityV2: {\n primary: (ctx) => {\n const source = primaryAccountOf(ctx)\n\n if (!source) return null\n\n const savings = savingsAccountsOf(ctx)\n const payload = {\n amount: ctx.beat.payload?.suggested_savings_amount,\n source_account_id: source.external_guid,\n }\n\n return savings.length\n ? {\n action: BEAT_OPERATION_1,\n payload: {\n ...payload,\n destination_account_ids: savings.map((account) => account.external_guid),\n },\n }\n : { action: BEAT_OPERATION_2, payload }\n },\n },\n}\n\n/** Backend `cta.metadata` first, else this template's interim resolver for the slot, else null. */\nexport const resolveCtaMetadata = (ctx: CtaStateContext): CtaPostMessageMetadata | null =>\n ctx.cta.metadata ?? ctaPostMessages[ctx.beat.template]?.[ctx.slot]?.(ctx) ?? null\n"],"mappings":";;AAkBA,IAAM,IAAA,CAAY,MAChB,CAAC,EAAQ,aAAa,CAAC,EAAQ,cAAc,CAAC,EAAQ,WAGlD,IAAA,CAAoB,EAAE,MAAA,GAAM,QAAA,EAAA,MAChC,EAAK,mBACL,EAAO,aAAa,SAAS,KAAA,CAAM,MAAY,EAAQ,SAAS,EAAK,oBAAoB,GAGrF,IAAA,CAAkB,EAAE,MAAA,GAAM,QAAA,EAAA,MAAqC;AACnE,QAAM,IAAQ,IAAI,IAAY,EAAK,aAAa;AAEhD,SAAO,EAAO,aAAa,SAAS,OAAA,CACjC,MAAY,EAAM,IAAI,EAAQ,IAAI,KAAK,EAAS,CAAO,CAC1D;AACF,GAEM,IAAA,CAAqB,EAAE,QAAA,EAAA,MAC3B,EAAO,aAAa,SAAS,OAAA,CAC1B,MAAY,EAAQ,iBAAiB,EAAY,WAAW,EAAS,CAAO,CAC/E,GAEW,IAAoF;AAAA,EAE/F,uBAAuB,EACrB,SAAA,OAAgB,EAAE,QAAQ,EAAiB,GAC7C;AAAA,EAGA,mBAAmB,EACjB,SAAA,CAAU,MAAQ;AAChB,UAAM,IAAU,EAAiB,CAAG;AAEpC,WAAO,IACH;AAAA,MAAE,QAAQ;AAAA,MAAkB,SAAS,EAAE,cAAc,EAAQ,cAAc;AAAA,IAAE,IAC7E;AAAA,EACN,EACF;AAAA,EAGA,0BAA0B,EACxB,SAAA,CAAU,EAAE,MAAA,EAAA,OACT,EAAK,SAAS,mBAAmB,KAAK,IAAI,EAAE,QAAQ,EAAiB,IAAI,KAC9E;AAAA,EAMA,kCAAkC,EAChC,SAAA,CAAU,EAAE,MAAA,EAAA,MACV,CAAC,EAAK,SAAS,kCAAkC,CAAC,EAAK,cAAc,SACjE,EAAE,QAAQ,EAAiB,IAC3B,KACR;AAAA,EAGA,kBAAkB,EAChB,SAAA,CAAU,OAAS;AAAA,IACjB,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,wBAAwB,EAAI,KAAK,SAAS;AAAA,MAC1C,UAAU,EAAe,CAAG,EAAE,IAAA,CAAK,OAAa;AAAA,QAC9C,cAAc,EAAQ;AAAA,QACtB,cAAc,EAAI,KAAK,SAAS,cAAc;AAAA,MAChD,EAAE;AAAA,IACJ;AAAA,EACF,GACF;AAAA,EAGA,sBAAsB,EACpB,SAAA,CAAU,MAAQ;AAChB,UAAM,IAAS,EAAiB,CAAG;AAEnC,QAAI,CAAC,EAAQ,QAAO;AAEpB,UAAM,IAAU,EAAkB,CAAG,GAC/B,IAAU;AAAA,MACd,QAAQ,EAAI,KAAK,SAAS;AAAA,MAC1B,mBAAmB,EAAO;AAAA,IAC5B;AAEA,WAAO,EAAQ,SACX;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,GAAG;AAAA,QACH,yBAAyB,EAAQ,IAAA,CAAK,MAAY,EAAQ,aAAa;AAAA,MACzE;AAAA,IACF,IACA;AAAA,MAAE,QAAQ;AAAA,MAAkB,SAAA;AAAA,IAAQ;AAAA,EAC1C,EACF;AACF,GAGa,IAAA,CAAsB,MACjC,EAAI,IAAI,YAAY,EAAgB,EAAI,KAAK,QAAA,IAAY,EAAI,IAAA,IAAQ,CAAG,KAAK"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { ctaActions } from './ctaConfig';
|
|
2
2
|
export { ctaHandlers, type CtaHandlerName } from './ctaHandlers';
|
|
3
|
+
export { ctaPostMessages, resolveCtaMetadata, type CtaPostMessageResolver } from './ctaPostMessages';
|
|
3
4
|
export { HOST_ACTIONS, type HostActionName } from './hostActions';
|
|
4
5
|
export { beatContextOf, resolveCtaState, resolveStep, runCtaAction, runCtaHandler, } from './runCtaAction';
|
|
5
6
|
export { useBeatCtaActions, useCtaHandler, type BeatCtaActions } from './useCtaAction';
|
|
@@ -6,7 +6,11 @@ export declare const beatContextOf: ({ beat, stores }: Pick<CtaStateContext, "be
|
|
|
6
6
|
export declare const resolveStep: ({ cta }: Pick<CtaStateContext, "cta">) => CtaStep;
|
|
7
7
|
/** Pending always disables; the step's `disabledWhen` may add to that. */
|
|
8
8
|
export declare const resolveCtaState: (ctx: CtaStateContext) => CtaState;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Runs the CTA's step: `run` handler, `onSuccess` effects, the CTA's post message if one resolves
|
|
11
|
+
* (backend `cta.metadata` or the template's `ctaPostMessages` entry; skipped when the handler
|
|
12
|
+
* throws), then the `next` target. No resolved message = no post.
|
|
13
|
+
*/
|
|
10
14
|
export declare const runCtaAction: (ctx: CtaContext) => Promise<void>;
|
|
11
15
|
/** Runs a handler directly (drawer Save buttons): same bookkeeping as a card CTA. True on success. */
|
|
12
16
|
export declare const runCtaHandler: (ctx: CtaHandlerContext, handlerName: CtaHandlerName, { onSuccess }?: RunHandlerOptions) => Promise<boolean>;
|
|
@@ -1,79 +1,82 @@
|
|
|
1
1
|
import { sendBeatCta as l } from "../../common/utils/PostMessage.es.js";
|
|
2
|
-
import { hasDrawerBlock as
|
|
3
|
-
import { HOST_ACTIONS as
|
|
4
|
-
import { ctaActions as
|
|
5
|
-
import { ctaHandlers as
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
userGuid: e.insights_payload?.user_guid ?? e.user_guid
|
|
2
|
+
import { hasDrawerBlock as f } from "../mappings/blocks.es.js";
|
|
3
|
+
import { HOST_ACTIONS as m } from "./hostActions.es.js";
|
|
4
|
+
import { ctaActions as C } from "./ctaConfig.es.js";
|
|
5
|
+
import { ctaHandlers as v } from "./ctaHandlers.es.js";
|
|
6
|
+
import { resolveCtaMetadata as g } from "./ctaPostMessages.es.js";
|
|
7
|
+
var w = {}, o = ({ cta: t }) => t.action_key && C[t.action_key] || w, H = (t) => {
|
|
8
|
+
const a = o(t);
|
|
9
|
+
return { disabled: t.stores.insightsV2Store.ctaStatus[t.beat.guid] === "pending" || (a.disabledWhen?.(t) ?? !1) };
|
|
10
|
+
}, n = ({ beat: t, slot: a, stores: r }) => {
|
|
11
|
+
r.insightsV2Store.openCtaDrawer(t.guid, a === "primary" ? "primaryCta" : "secondaryCta");
|
|
12
|
+
}, h = ({ beat: t, stores: a }, r) => {
|
|
13
|
+
const { is_mobile_webview: e, clientConfig: s } = a.appDataStore.appConfig, { event: i, action: d, payload: p } = r;
|
|
14
|
+
l(t, {
|
|
15
|
+
event: i ?? void 0,
|
|
16
|
+
op: d ?? void 0,
|
|
17
|
+
extras: p ?? void 0,
|
|
18
|
+
isMobileWebview: e,
|
|
19
|
+
webviewScheme: s?.master?.ui_message_webview_url_scheme,
|
|
20
|
+
userGuid: t.insights_payload?.user_guid ?? t.user_guid
|
|
22
21
|
});
|
|
23
|
-
},
|
|
24
|
-
t
|
|
25
|
-
},
|
|
26
|
-
|
|
22
|
+
}, S = (t) => {
|
|
23
|
+
f(t.cta.block) && n(t);
|
|
24
|
+
}, _ = (t, { beat: a, stores: r }) => {
|
|
25
|
+
t === "refreshBeat" && r.emitUpdate({ staleBeatGuid: a.guid });
|
|
26
|
+
}, y = ({ beat: t, slot: a, hostActions: r }) => {
|
|
27
|
+
a !== "primary" || t.cta_clicked_at || Promise.resolve().then(() => r.onCtaAction?.({
|
|
27
28
|
beatGuid: t.guid,
|
|
28
|
-
name:
|
|
29
|
+
name: m.markCtaClicked
|
|
29
30
|
})).catch(() => {
|
|
30
31
|
});
|
|
31
|
-
}, c = async (t,
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
34
|
-
await
|
|
35
|
-
for (const s of
|
|
36
|
-
}, u = async ({ beat: t, stores:
|
|
37
|
-
const { insightsV2Store: s } =
|
|
32
|
+
}, c = async (t, a, r) => {
|
|
33
|
+
const e = v[a];
|
|
34
|
+
if (!e) throw new Error(`Unknown CTA handler "${a}"`);
|
|
35
|
+
await e(t);
|
|
36
|
+
for (const s of r) _(s, t);
|
|
37
|
+
}, u = async ({ beat: t, stores: a }, r, e = {}) => {
|
|
38
|
+
const { insightsV2Store: s } = a;
|
|
38
39
|
s.setCtaStatus(t.guid, "pending");
|
|
39
40
|
try {
|
|
40
|
-
return await
|
|
41
|
+
return await r(), s.setCtaStatus(t.guid, "done"), !0;
|
|
41
42
|
} catch (i) {
|
|
42
|
-
return s.setCtaStatus(t.guid, "error"),
|
|
43
|
+
return s.setCtaStatus(t.guid, "error"), a.captureException(i, {
|
|
43
44
|
store: "ctaActions",
|
|
44
45
|
template: t.template,
|
|
45
|
-
...
|
|
46
|
+
...e
|
|
46
47
|
}), !1;
|
|
47
48
|
}
|
|
48
|
-
},
|
|
49
|
-
const
|
|
49
|
+
}, G = async (t) => {
|
|
50
|
+
const a = o(t), r = typeof a.params == "function" ? a.params(t) : a.params, e = {
|
|
50
51
|
...t,
|
|
51
52
|
params: {
|
|
52
|
-
...
|
|
53
|
+
...r,
|
|
53
54
|
...t.params
|
|
54
55
|
}
|
|
55
56
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const s =
|
|
59
|
-
s
|
|
57
|
+
a.skipCtaClicked || y(e), await u(e, async () => {
|
|
58
|
+
a.run && await c(e, a.run, a.onSuccess ?? []);
|
|
59
|
+
const s = g(e);
|
|
60
|
+
s && h(e, s);
|
|
61
|
+
const i = a.next ?? (a.run ? "none" : "default");
|
|
62
|
+
i === "default" ? S(e) : i === "drawer" && n(e);
|
|
60
63
|
});
|
|
61
|
-
},
|
|
64
|
+
}, k = {
|
|
62
65
|
label: "",
|
|
63
66
|
block: ""
|
|
64
|
-
},
|
|
65
|
-
const
|
|
67
|
+
}, M = (t, a, { onSuccess: r = [] } = {}) => {
|
|
68
|
+
const e = {
|
|
66
69
|
...t,
|
|
67
|
-
cta:
|
|
70
|
+
cta: k,
|
|
68
71
|
slot: "primary"
|
|
69
72
|
};
|
|
70
|
-
return u(
|
|
73
|
+
return u(e, () => c(e, a, r), { handler: a });
|
|
71
74
|
};
|
|
72
75
|
export {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
H as resolveCtaState,
|
|
77
|
+
o as resolveStep,
|
|
78
|
+
G as runCtaAction,
|
|
79
|
+
M as runCtaHandler
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
//# sourceMappingURL=runCtaAction.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runCtaAction.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/runCtaAction.ts"],"sourcesContent":["import { sendBeatCta } from '../../common/utils/PostMessage'\nimport { hasDrawerBlock } from '../mappings/blocks'\nimport { ctaActions } from './ctaConfig'\nimport { ctaHandlers, type CtaHandlerName } from './ctaHandlers'\nimport { HOST_ACTIONS } from './hostActions'\nimport type {\n CtaContext,\n CtaEffect,\n CtaHandler,\n CtaHandlerContext,\n CtaState,\n CtaStateContext,\n CtaStep,\n RunHandlerOptions,\n} from './types'\n\nconst DEFAULT_STEP: CtaStep = {}\n\n// ─── Resolution ─────────────────────────────────────────────────────────────\n\n/** The beat's own context (input a block collected for it), `{}` until something is written. */\nexport const beatContextOf = ({ beat, stores }: Pick<CtaStateContext, 'beat' | 'stores'>) =>\n stores.insightsV2Store.beatContext[beat.guid] ?? {}\n\n/** The step named by the CTA's `action_key`, or the empty default (no key / unknown key). */\nexport const resolveStep = ({ cta }: Pick<CtaStateContext, 'cta'>): CtaStep =>\n (cta.action_key && ctaActions[cta.action_key]) || DEFAULT_STEP\n\n/** Pending always disables; the step's `disabledWhen` may add to that. */\nexport const resolveCtaState = (ctx: CtaStateContext): CtaState => {\n const step = resolveStep(ctx)\n const pending = ctx.stores.insightsV2Store.ctaStatus[ctx.beat.guid] === 'pending'\n\n return { disabled: pending || (step.disabledWhen?.(ctx) ?? false) }\n}\n\n// ─── Targets and effects ────────────────────────────────────────────────────\n\n/** Opens the beat's drawer for whichever slot triggered the current step. */\nconst openDrawerForSlot = ({\n beat,\n slot,\n stores,\n}: Pick<CtaContext, 'beat' | 'slot' | 'stores'>) => {\n stores.insightsV2Store.openCtaDrawer(\n beat.guid,\n slot === 'primary' ? 'primaryCta' : 'secondaryCta',\n )\n}\n\n
|
|
1
|
+
{"version":3,"file":"runCtaAction.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/runCtaAction.ts"],"sourcesContent":["import { sendBeatCta } from '../../common/utils/PostMessage'\nimport { hasDrawerBlock } from '../mappings/blocks'\nimport { ctaActions } from './ctaConfig'\nimport { resolveCtaMetadata } from './ctaPostMessages'\nimport { ctaHandlers, type CtaHandlerName } from './ctaHandlers'\nimport { HOST_ACTIONS } from './hostActions'\nimport type { CtaPostMessageMetadata } from '../../core'\nimport type {\n CtaContext,\n CtaEffect,\n CtaHandler,\n CtaHandlerContext,\n CtaState,\n CtaStateContext,\n CtaStep,\n RunHandlerOptions,\n} from './types'\n\nconst DEFAULT_STEP: CtaStep = {}\n\n// ─── Resolution ─────────────────────────────────────────────────────────────\n\n/** The beat's own context (input a block collected for it), `{}` until something is written. */\nexport const beatContextOf = ({ beat, stores }: Pick<CtaStateContext, 'beat' | 'stores'>) =>\n stores.insightsV2Store.beatContext[beat.guid] ?? {}\n\n/** The step named by the CTA's `action_key`, or the empty default (no key / unknown key). */\nexport const resolveStep = ({ cta }: Pick<CtaStateContext, 'cta'>): CtaStep =>\n (cta.action_key && ctaActions[cta.action_key]) || DEFAULT_STEP\n\n/** Pending always disables; the step's `disabledWhen` may add to that. */\nexport const resolveCtaState = (ctx: CtaStateContext): CtaState => {\n const step = resolveStep(ctx)\n const pending = ctx.stores.insightsV2Store.ctaStatus[ctx.beat.guid] === 'pending'\n\n return { disabled: pending || (step.disabledWhen?.(ctx) ?? false) }\n}\n\n// ─── Targets and effects ────────────────────────────────────────────────────\n\n/** Opens the beat's drawer for whichever slot triggered the current step. */\nconst openDrawerForSlot = ({\n beat,\n slot,\n stores,\n}: Pick<CtaContext, 'beat' | 'slot' | 'stores'>) => {\n stores.insightsV2Store.openCtaDrawer(\n beat.guid,\n slot === 'primary' ? 'primaryCta' : 'secondaryCta',\n )\n}\n\n/**\n * Posts this CTA's resolved message (event, op and beat-specific payload, from the backend's\n * `cta.metadata` or the template's `ctaPostMessages` entry) to the host.\n */\nconst postCtaMessage = ({ beat, stores }: CtaContext, metadata: CtaPostMessageMetadata) => {\n const { is_mobile_webview, clientConfig } = stores.appDataStore.appConfig\n const { event, action, payload } = metadata\n\n sendBeatCta(beat, {\n event: event ?? undefined,\n op: action ?? undefined,\n extras: payload ?? undefined,\n isMobileWebview: is_mobile_webview,\n webviewScheme: clientConfig?.master?.ui_message_webview_url_scheme,\n userGuid: beat.insights_payload?.user_guid ?? beat.user_guid,\n })\n}\n\n/**\n * Default target: open the drawer for a real block, otherwise nothing. A post message is never\n * implied by a missing block; it is sent only when one resolves (see runCtaAction).\n */\nconst runDefaultTarget = (ctx: CtaContext) => {\n if (hasDrawerBlock(ctx.cta.block)) openDrawerForSlot(ctx)\n}\n\nconst runEffect = (effect: CtaEffect, { beat, stores }: CtaContext) => {\n switch (effect) {\n case 'refreshBeat':\n stores.emitUpdate({ staleBeatGuid: beat.guid })\n break\n }\n}\n\n/**\n * First primary click records `cta_clicked_at` through the host. Fire-and-forget: whatever the\n * host does (reject, throw, return nothing) must never fail the user's action.\n */\nconst markCtaClicked = ({ beat, slot, hostActions }: CtaContext) => {\n if (slot !== 'primary' || beat.cta_clicked_at) return\n\n // Wrapping the call in `.then` turns a synchronous throw into a rejection, and the `.catch`\n // swallows both: a failed cta_clicked_at write is not the user's action.\n Promise.resolve()\n .then(() =>\n hostActions.onCtaAction?.({ beatGuid: beat.guid, name: HOST_ACTIONS.markCtaClicked }),\n )\n .catch(() => {})\n}\n\n// ─── Shared bookkeeping ─────────────────────────────────────────────────────\n\n/** Runs a named handler, then its `onSuccess` effects. Shared by card CTAs and drawer saves. */\nconst runHandlerWithEffects = async (\n ctx: CtaContext,\n handlerName: CtaHandlerName,\n onSuccess: CtaEffect[],\n): Promise<void> => {\n // Names are typed, but the map is still indexed defensively: config can come from anywhere.\n const handler = (ctaHandlers as Record<string, CtaHandler>)[handlerName]\n\n if (!handler) throw new Error(`Unknown CTA handler \"${handlerName}\"`)\n\n await handler(ctx)\n\n for (const effect of onSuccess) runEffect(effect, ctx)\n}\n\n/** Per-beat status around `work`: `pending` -> `done` | `error` (+ captureException). */\nconst withCtaStatus = async (\n { beat, stores }: Pick<CtaContext, 'beat' | 'stores'>,\n work: () => Promise<void>,\n captureContext: Record<string, string> = {},\n): Promise<boolean> => {\n const { insightsV2Store } = stores\n\n insightsV2Store.setCtaStatus(beat.guid, 'pending')\n\n try {\n await work()\n insightsV2Store.setCtaStatus(beat.guid, 'done')\n\n return true\n } catch (error) {\n insightsV2Store.setCtaStatus(beat.guid, 'error')\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n ...captureContext,\n })\n\n return false\n }\n}\n\n// ─── Entry points ───────────────────────────────────────────────────────────\n\n/**\n * Runs the CTA's step: `run` handler, `onSuccess` effects, the CTA's post message if one resolves\n * (backend `cta.metadata` or the template's `ctaPostMessages` entry; skipped when the handler\n * throws), then the `next` target. No resolved message = no post.\n */\nexport const runCtaAction = async (ctx: CtaContext): Promise<void> => {\n const step = resolveStep(ctx)\n const stepParams = typeof step.params === 'function' ? step.params(ctx) : step.params\n const stepCtx: CtaContext = { ...ctx, params: { ...stepParams, ...ctx.params } }\n\n if (!step.skipCtaClicked) markCtaClicked(stepCtx)\n\n await withCtaStatus(stepCtx, async () => {\n if (step.run) await runHandlerWithEffects(stepCtx, step.run, step.onSuccess ?? [])\n\n const metadata = resolveCtaMetadata(stepCtx)\n\n if (metadata) postCtaMessage(stepCtx, metadata)\n\n const target = step.next ?? (step.run ? 'none' : 'default')\n\n if (target === 'default') runDefaultTarget(stepCtx)\n else if (target === 'drawer') openDrawerForSlot(stepCtx)\n })\n}\n\n/** Placeholder CTA for handlers run from inside a drawer, where no card slot is involved. */\nconst DIRECT_CTA = { label: '', block: '' }\n\n/** Runs a handler directly (drawer Save buttons): same bookkeeping as a card CTA. True on success. */\nexport const runCtaHandler = (\n ctx: CtaHandlerContext,\n handlerName: CtaHandlerName,\n { onSuccess = [] }: RunHandlerOptions = {},\n): Promise<boolean> => {\n const fullCtx: CtaContext = { ...ctx, cta: DIRECT_CTA, slot: 'primary' }\n\n return withCtaStatus(fullCtx, () => runHandlerWithEffects(fullCtx, handlerName, onSuccess), {\n handler: handlerName,\n })\n}\n"],"mappings":"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"}
|
|
@@ -35,7 +35,7 @@ export interface CtaStep {
|
|
|
35
35
|
/** Effects executed after `run` resolves successfully. */
|
|
36
36
|
onSuccess?: CtaEffect[];
|
|
37
37
|
/**
|
|
38
|
-
* After `run`: `default` = drawer if the CTA has a block, else
|
|
38
|
+
* After `run`: `default` = drawer if the CTA has a block, else nothing; `drawer` forces the
|
|
39
39
|
* drawer; `none` stops. Omitted = `none` with `run`, `default` without.
|
|
40
40
|
*/
|
|
41
41
|
next?: 'default' | 'drawer' | 'none';
|