@mx-cartographer/experiences 9.7.90 → 9.7.92
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 +9 -0
- package/dist/core/types/Insights.d.ts +4 -0
- package/dist/core/types/Insights.es.js.map +1 -1
- package/dist/insights-v2/components/CompactInsightCard.es.js +176 -127
- package/dist/insights-v2/components/CompactInsightCard.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/index.d.ts +1 -1
- package/dist/insights-v2/ctaActions/runCtaAction.d.ts +6 -0
- package/dist/insights-v2/ctaActions/runCtaAction.es.js +1 -0
- package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [9.7.92] - 11-03-2026
|
|
2
|
+
|
|
3
|
+
- **ADDED** - Duplicate Payment beat on Insights 2.0 (MEW2-3421): the `with_header_footer` transaction card whose "View Transactions" CTA opens the Transaction List drawer as a plain list. Fixture, story, contract test and block doc
|
|
4
|
+
|
|
5
|
+
## [9.7.91] - 11-03-2026
|
|
6
|
+
|
|
7
|
+
- **ADDED** - Switch Direct Deposit's embedded card (Figma `1941:167601`): a swap badge on `linked_logos`, and a "Switch Direct Deposit" link that posts tupac's op_1 to the host (no drawer, no Atomic) (WEC-2197)
|
|
8
|
+
- **CHANGED** - Embedded cards follow Figma's embedded template: description 2px under the title and at most two lines, link 8px under it, visual centred (WEC-2197)
|
|
9
|
+
|
|
1
10
|
## [9.7.90] - 11-03-2026
|
|
2
11
|
|
|
3
12
|
- **FIXED** - Unexpected Subscription Payment design QA (WEC-2126), USP only: dark "Not subscribed" bar border and icon in `text.primary` (were black on black), status Undo 13/16, `warning_wash` row card at elevation/1 (`TransactionListCard elevated`, with the dark-mode tint), Subscription Status dialog at elevation/16 with 12/16 answer buttons and a 220px reasons dialog
|
|
@@ -229,6 +229,10 @@ export interface InDescriptionCTA {
|
|
|
229
229
|
block?: string;
|
|
230
230
|
variant?: string | null;
|
|
231
231
|
action_key?: string | null;
|
|
232
|
+
/** tupac's flat host message for the link (`action` plus fields), e.g. Switch's op_1. */
|
|
233
|
+
post_message_metadata?: ({
|
|
234
|
+
action?: string | null;
|
|
235
|
+
} & Record<string, unknown>) | null;
|
|
232
236
|
/** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */
|
|
233
237
|
drilldown_data?: DrilldownData;
|
|
234
238
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Insights.es.js","names":[],"sources":["../../../src/core/types/Insights.ts"],"sourcesContent":["// ============ Core InsightsV2 Types ============\n\nimport { InsightsInstanceSlotData } from './InsightsInstanceSlotData'\n\nexport interface ThemedSVGImageProps {\n divProps?: React.HTMLAttributes<HTMLDivElement>\n image: React.ComponentType<React.SVGProps<SVGSVGElement>>\n isMini?: boolean\n}\n\n/**\n * Emitted by a block when the user drills into a specific datum (e.g. clicks a\n * chart bar). Named fields rather than positional args so blocks with differing\n * shapes can extend this without the call sites transposing values.\n */\nexport interface DrilldownPayload {\n chartBarIndex: number\n tabIndex?: number\n transaction_guid?: string\n block?: string\n}\n\n/**\n * Which envelope a beat was fetched with. `embedded` (the transaction-row pill + details-drawer\n * card) and `notification` are V2 additions; a surface asks for one with `widget_type=` on the fetch.\n */\nexport type WidgetType = 'full' | 'mini' | 'micro' | 'embedded' | 'notification'\n\n/**\n * V2: the transaction-row tag, its own small section of the `embedded` envelope (the same grammar\n * as `description` / `footer`, with room for a tone or an icon later). Omitted for an insight that\n * tags no transaction row. It deliberately is *not* `short_title`, which stays a per-variant\n * attribute with a second consumer (the bank-holiday sub-header).\n */\nexport interface Pill {\n text: string\n}\n\n/**\n * V2: facts a drilldown shows that the card does not. On the payload: what every CTA of the card\n * shares (e.g. the account number for the Transaction Details drawer, or the guid list a list drawer\n * shows, `transaction_guids`). On a CTA: the figures that CTA's own drilldown renders (the Transaction\n * List summary header's `amount`, `start_date` / `end_date` and `count`, see `readSummary`). Scalars\n * or string lists, omitted when the insight has none. Never carries copy or user actions (memo,\n * tags, hide, flag come from the transaction store). Values come from the server, so a reader still\n * checks each value's runtime shape.\n */\nexport type DrilldownData = Record<string, string | number | boolean | null | string[]>\n\nexport interface InsightsPayload {\n widget_type: WidgetType\n instance_slot: InstanceSlot\n /**\n * V1 only. The v2 contract carries everything a block needs on `instance_slot.data`,\n * and omits this key entirely (see the LargeTransactionV2 payload). Blocks that still require it\n * get it as a required field through `BlockProps`, which re-declares it.\n */\n instance_slot_data?: InsightsInstanceSlotData\n description: Description\n primary_cta?: PrimaryCTA\n secondary_cta?: SecondaryCTA\n title?: string\n sub_title?: Subtitle\n /**\n * V2 rename of `sub_title`: a flat string, with the logo promoted to the payload-level `logo`\n * field below. `sub_title` wins when both are present.\n */\n subtitle?: string\n /** V2: header logo paired with the flat `subtitle`: an entity guid or a status icon (`LogoRef`). */\n logo?: LogoRef\n /** V2: which design branch the backend picked (e.g. `no_merchant`). */\n variant?: string\n /** V2, `embedded` envelope only: the transaction-row pill. */\n pill?: Pill\n /**\n * V2: drilldown-only facts, e.g. `{ account_number: 'XXXXXXXX043' }`, or the guid list a list\n * drawer shows (`{ transaction_guids: [...] }`).\n */\n drilldown_data?: DrilldownData\n footer?: Footer\n /**\n * V2: the link inside the description: on the full card a drawer of its own (Low Account\n * Balance's \"Transactions ›\"); on mini and micro the card's action, sent instead of `primary_cta`.\n * Absent when the beat has no link.\n */\n in_description_cta?: InDescriptionCTA\n guid: string\n user_guid: string\n client_guid?: string\n /** Template name, e.g. `LargeTransactionV2`. */\n template?: string\n revision?: number\n /** Unix seconds. */\n created_at?: number\n /** Unix seconds. */\n updated_at?: number\n /** Unix seconds. */\n active_at?: number\n displayed_at?: number | null\n dismissed_at: number | null\n cta_clicked_at?: number | null\n has_been_displayed?: boolean\n is_dismissed: boolean\n is_deleted?: boolean\n is_hidden_by_system?: boolean\n is_obsolete?: boolean\n is_refreshable?: boolean\n is_sampled?: boolean\n is_zero_state?: boolean\n status?: string | null\n /**\n * V2 (LargeTransactionV2 onward): the entities the beat is about, carried on the payload so\n * action blocks do not have to reach back out to the beat envelope.\n */\n primary_account_guid?: string | null\n primary_transaction_guid?: string | null\n primary_category_guid?: string | null\n primary_merchant_guid?: string | null\n /**\n * V2: the repeating transaction a subscription beat is about (Upcoming Subscription, Subscription\n * Price Increase). Read by `recurring_transaction_block`, see\n * `actionBlocks/RecurringTransactionBlock/subscriptionBeats.ts`.\n */\n primary_repeating_transaction_guid?: string | null\n}\n\nexport interface Legends {\n label: string\n value: number\n additional_value?: number\n additional_value_type?: string\n value_type?: string\n}\n\nexport interface Logo {\n category_guid?: string\n category_name?: string\n institution_guid?: string\n institution_name?: string\n logo_color?: string\n /** Draw the `logo_name` icon filled (V2 `ICO-` logos). */\n logo_fill?: boolean\n logo_name?: string\n merchant_guid?: string\n merchant_name?: string\n}\n\nexport interface Subtitle {\n logo?: Logo\n text: string\n}\n\n/**\n * A V2 logo, shared by the header (`InsightsPayload.logo`) and every instance-slot row. It takes\n * one of two shapes:\n *\n * - **Entity**: `guid`, whose prefix (`INS-` / `MCH-` / `CAT-`) picks the logo component, plus an\n * optional `fallback_guid` for when that component's asset cannot load. Today that is the\n * category behind an `MCH-` guid, which `MerchantLogo` shows as a tile when the merchant image\n * is missing.\n * - **Status icon**: `icon` (an mx-icons name, e.g. `warning`) and `color` (a theme palette path,\n * e.g. `error.main`), for subheaders that show a state rather than an entity (V1's\n * `logo_name` / `logo_color`).\n *\n * `alt` is the accessible name for either shape. A logo sends one shape; if it sends both, `icon`\n * wins, as `logo_name` does in V1's `SubHeader`.\n */\nexport interface LogoRef {\n alt?: string\n color?: string\n fallback_guid?: string\n guid?: string\n icon?: string\n}\n\nexport interface CategoryLogo {\n category_guid: string\n category_name: string\n}\n\nexport interface MerchantLogo {\n merchant_guid: string\n merchant_name: string\n}\n\nexport interface InstitutionLogo {\n institution_guid: string\n institution_name: string\n}\n\nexport interface Description {\n text: string\n html_text: string\n}\n\n/**\n * Post message a CTA press sends to the host, resolved by the backend for the beat's current\n * state. Absent/null = the backend has not described one: the frontend uses the template's\n * interim `ctaPostMessages` entry if there is one, else sends no message. `payload` carries only\n * beat-specific extras; the frontend always adds `beat_guid`, `beat_template`, `user_guid` and\n * `action_string`.\n */\nexport interface CtaPostMessageMetadata {\n /** Post message event; defaults to `beat/cta`. */\n event?: string | null\n /** Operation the host should perform, e.g. `op_1`, `op_2`, `op_3`. Defaults to `op_1`. */\n action?: string | null\n /** Beat-specific fields merged into the message metadata (account ids, amounts, ...). */\n payload?: Record<string, unknown> | null\n}\n\nexport interface PrimaryCTA {\n label: string\n block: string\n variant?: string | null\n /**\n * What pressing the CTA does beyond opening its block, named by the backend (ditto\n * `primary_cta_action`, passed through by tupac). Looked up in `insights-v2/ctaActions`;\n * null / absent / unknown means the CTA only opens its block or posts its message.\n */\n action_key?: string | null\n metadata?: CtaPostMessageMetadata | null\n /** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */\n drilldown_data?: DrilldownData\n}\n\nexport interface SecondaryCTA {\n label: string\n /**\n * Drawer block to open. `null` when the CTA opens nothing (ditto `secondary_cta_block: null`,\n * e.g. Fee Detected's \"Contact Support\"): the press posts its message to the host instead.\n */\n block: string | null\n variant?: string | null\n /** See `PrimaryCTA.action_key` (ditto `secondary_cta_action`). */\n action_key?: string | null\n metadata?: CtaPostMessageMetadata | null\n /** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */\n drilldown_data?: DrilldownData\n}\n\n/**\n * The description link: its label, the action block it opens and, like a CTA's, the variant that\n * tells that block what to show (ditto's `in_description_cta_variant`, e.g. `low_balance`). On mini\n * and micro it words the card's action; `block` is empty when that action opens no drawer.\n */\nexport interface InDescriptionCTA {\n label: string\n block?: string\n variant?: string | null\n action_key?: string | null\n /** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */\n drilldown_data?: DrilldownData\n}\n\nexport interface Footer {\n /** V1: a pre-formatted date string. */\n date?: string\n /**\n * The account label, on both V1 and V2. The backend resolves the plurality: one account sends\n * its name (e.g. `Epic Bank`), several send a localized count (e.g. `2 Accounts`), none sends\n * `null` (tupac's `footer_account_label` returns nil for a beat with no accounts, as Payday Cash\n * Left Over Status does). There is deliberately no separate `accounts` key.\n */\n account?: string | null\n /**\n * V2 (LargeTransactionV2 onward): unix seconds, formatted by `Footer` for display.\n * Replaces `date`; `date` wins when a beat somehow sends both.\n */\n timestamp?: number\n}\n\n// ============ Instance Slot Types ============\n// Values are the snake_case block names the backend sends on a beat's instance slot; the PascalCase\n// keys stay as friendly references (e.g. BlockType.BarChartBlock === 'bar_chart_block').\nexport const BlockType = {\n AccountBlock: 'account_block',\n AccountSwitchBlock: 'account_switch_block',\n BarChartBlock: 'bar_chart_block',\n BudgetBlock: 'budget_block',\n DirectDepositBlock: 'direct_deposit_block',\n ImageBlock: 'image_block',\n MerchantBlock: 'merchant_block',\n TransactionBlock: 'transaction_block',\n DonutChartBlock: 'donut_chart_block',\n DoubleBarChartBlock: 'double_bar_chart_block',\n DoubleProgressBarBlock: 'double_progress_bar_block',\n DebtsBlock: 'debts_block',\n GoalsBlock: 'goals_block',\n NoSpendDaysBlock: 'no_spend_days_block',\n RepeatingTransactionBlock: 'repeating_transaction_block',\n SaveExtraBlock: 'save_extra_block',\n SavingsAmountBlock: 'savings_amount_block',\n StepChartBlock: 'step_chart_block',\n SVGImageBlock: 'svg_image_block',\n UpcomingBillBlock: 'upcoming_bill_block',\n UpcomingSubscriptionBlock: 'upcoming_subscription_block',\n WithdrawalBlock: 'withdrawal_block',\n} as const\n\nexport type BlockType = (typeof BlockType)[keyof typeof BlockType]\n\n/**\n * A pre-localized row on a V2 instance slot. The backend resolves the copy and formatting; the\n * logo is a `LogoRef` (an entity guid or a status icon, see `logoFromRef`).\n */\nexport interface InstanceSlotRow {\n amount?: number\n logo?: LogoRef\n primary_text?: string\n secondary_text?: string\n}\n\n/**\n * V2: the slots a block's variant filled, keyed by slot name; ditto owns the names and omits any\n * slot it has no value for (`lib/ditto/blocks/<block>.yml`). Read with `slot()`, which names the\n * kind the caller expects.\n */\nexport interface InstanceSlotData {\n [slot: string]: unknown\n}\n\n/**\n * Reads one slot off a block's `data`; undefined when ditto did not fill it.\n *\n * The caller names the kind, because the slot's kind lives in the block's ditto schema and not\n * here — a block fills slots with whatever its card needs, so no fixed list of kinds would hold\n * (`account_switch_block` alone carries a boolean and `budget_block` a nested object).\n */\nexport const slot = <T = unknown>(\n data: InstanceSlotData | undefined,\n name: string,\n): T | undefined => data?.[name] as T | undefined\n\n/**\n * V2: which block renders this beat's content, and what it renders.\n *\n * `block` is optional: a beat whose template has no ditto config arrives as an empty `{}` and the\n * card renders its copy and CTAs alone, so a reader cannot assume a block is named. One interface\n * with `block` optional rather than a union with `Record<string, never>`, which TypeScript absorbs\n * — that union left `block` reading as required and typed a promise it did not keep.\n *\n * `variant` is optional for the same reason — the empty `{}` carries neither. Every ditto beat\n * file that names a block also names a variant, so a *filled* slot always has one; ditto drops a\n * nil variant rather than sending null, which is why the type is `string | null | undefined`.\n */\nexport interface InstanceSlot {\n block?: BlockType\n variant?: string | null\n /** The slots this block's variant declares in ditto. */\n data?: InstanceSlotData\n // Direct Deposit block (Set Up Direct Deposit, Switch Direct Deposit, Missing\n // Interest Rate): account facts carried flat on the slot, beside `data`.\n /** Primary line on the hero card, e.g. \"Epic Bank Checking, *1234\". */\n account_name?: string\n /** Secondary line beneath the name, e.g. \"Checking Account\" / \"Credit Card\". */\n account_type?: string\n /** Status pill label, e.g. \"Direct Deposit Not Set Up\" / \"Interest Rate: 19.99% APR\". */\n status_text?: string\n institution_guid?: string\n institution_name?: string\n // Shared across multiple V2 component blocks:\n // `amount` — UpcomingBillBlock (projected bill), UpcomingSubscriptionBlock (price per cadence).\n // (SavingsOpportunityBlock now reads `amount` off `instance_slot.data`, not this flat field.)\n // `merchant_guid` / `merchant_name` — UpcomingBillBlock (bill row logo + name),\n // UpcomingSubscriptionBlock (hero logo + name).\n /** Dollar amount (unformatted); the block formats it as currency. */\n amount?: number\n // Withdrawal block (Emergency Fund Withdrawal): one row flat on the slot - `amount` above is the\n // withdrawal, shown with its date line \"{amount_label} {withdrawal_date}\" over the balance row.\n /** Pre-localized lead-in for the date line, e.g. \"Withdrawn on\". */\n amount_label?: string\n /** The withdrawal's calendar day, `YYYY-MM-DD` (e.g. `2026-10-20`); the card formats it. */\n withdrawal_date?: string\n /** Account balance after the withdrawal (unformatted); the card formats it as currency. */\n balance?: number\n /** Pre-localized label for the balance row, e.g. \"Balance\". */\n balance_label?: string\n /** Merchant guid used to render the inline / hero logo via `LogoFromGuid` (`MCH-...`). */\n merchant_guid?: string\n /** Merchant display name — shown as logo alt text and as the merchant/hero label. */\n merchant_name?: string\n // Upcoming Bill block (Projected Bill Payment): the block derives the Sun→Sat week strip\n // around `due_date` at render time.\n /** Due date of the projected bill in ISO form (e.g. `2025-10-30`). */\n due_date?: string\n // Account block (confetti tile): V1 flat fallback for the label line when `instance_slot.data` is\n // absent — `AccountBlock` reads `data.sub_text` first, then this. (The Savings Opportunity coupon\n // now reads `primary_text`/`amount` off `data`, so it no longer reads this flat field.)\n /** Caption/label line for the confetti tile (V1 fallback). */\n label?: string\n // Upcoming Subscription block: billing cadence for the price pill (`${amount} / ${cadence}`).\n /** Billing cadence word that trails the price, e.g. `month` or `year`. */\n cadence?: string\n // Account block (status on/off-track, deposit, payoff variants): card content carried flat on\n // the slot (the V2 contract drops `instance_slot_data`). `amount`, `label`, `merchant_guid`,\n // `merchant_name`, `institution_guid`, `institution_name` are shared fields declared above.\n /** Secondary line beneath the amount on the standard card, e.g. \"Remaining after expenses\". */\n sub_text?: string\n /** Header bar text (standard card) or the colored category strip label (deposit cards). */\n header_text?: string\n /** Generic-tile icon name used when no merchant/institution logo is available. */\n icon_name?: string\n /** Category guid used to render the deposit-card logo fallback. */\n category_guid?: string\n // Debts block: `amount` is the current balance; the shared account/institution fields above.\n /** Projected payoff date in ISO form (e.g. `2027-03-01`); the block formats it as month + year. */\n payoff_date?: string\n /** Monthly payment toward the debt (unformatted); the block formats it as currency. */\n monthly_payment?: number\n /** Annual interest rate as a percentage (e.g. `19.99`); the block formats it as a percent. */\n interest_rate?: number\n start_date?: string\n no_spend_days?: string[]\n}\n"],"mappings":"AAyUA,IAAa,IAAA,CACX,GACA,MACkB,IAAO,CAAA"}
|
|
1
|
+
{"version":3,"file":"Insights.es.js","names":[],"sources":["../../../src/core/types/Insights.ts"],"sourcesContent":["// ============ Core InsightsV2 Types ============\n\nimport { InsightsInstanceSlotData } from './InsightsInstanceSlotData'\n\nexport interface ThemedSVGImageProps {\n divProps?: React.HTMLAttributes<HTMLDivElement>\n image: React.ComponentType<React.SVGProps<SVGSVGElement>>\n isMini?: boolean\n}\n\n/**\n * Emitted by a block when the user drills into a specific datum (e.g. clicks a\n * chart bar). Named fields rather than positional args so blocks with differing\n * shapes can extend this without the call sites transposing values.\n */\nexport interface DrilldownPayload {\n chartBarIndex: number\n tabIndex?: number\n transaction_guid?: string\n block?: string\n}\n\n/**\n * Which envelope a beat was fetched with. `embedded` (the transaction-row pill + details-drawer\n * card) and `notification` are V2 additions; a surface asks for one with `widget_type=` on the fetch.\n */\nexport type WidgetType = 'full' | 'mini' | 'micro' | 'embedded' | 'notification'\n\n/**\n * V2: the transaction-row tag, its own small section of the `embedded` envelope (the same grammar\n * as `description` / `footer`, with room for a tone or an icon later). Omitted for an insight that\n * tags no transaction row. It deliberately is *not* `short_title`, which stays a per-variant\n * attribute with a second consumer (the bank-holiday sub-header).\n */\nexport interface Pill {\n text: string\n}\n\n/**\n * V2: facts a drilldown shows that the card does not. On the payload: what every CTA of the card\n * shares (e.g. the account number for the Transaction Details drawer, or the guid list a list drawer\n * shows, `transaction_guids`). On a CTA: the figures that CTA's own drilldown renders (the Transaction\n * List summary header's `amount`, `start_date` / `end_date` and `count`, see `readSummary`). Scalars\n * or string lists, omitted when the insight has none. Never carries copy or user actions (memo,\n * tags, hide, flag come from the transaction store). Values come from the server, so a reader still\n * checks each value's runtime shape.\n */\nexport type DrilldownData = Record<string, string | number | boolean | null | string[]>\n\nexport interface InsightsPayload {\n widget_type: WidgetType\n instance_slot: InstanceSlot\n /**\n * V1 only. The v2 contract carries everything a block needs on `instance_slot.data`,\n * and omits this key entirely (see the LargeTransactionV2 payload). Blocks that still require it\n * get it as a required field through `BlockProps`, which re-declares it.\n */\n instance_slot_data?: InsightsInstanceSlotData\n description: Description\n primary_cta?: PrimaryCTA\n secondary_cta?: SecondaryCTA\n title?: string\n sub_title?: Subtitle\n /**\n * V2 rename of `sub_title`: a flat string, with the logo promoted to the payload-level `logo`\n * field below. `sub_title` wins when both are present.\n */\n subtitle?: string\n /** V2: header logo paired with the flat `subtitle`: an entity guid or a status icon (`LogoRef`). */\n logo?: LogoRef\n /** V2: which design branch the backend picked (e.g. `no_merchant`). */\n variant?: string\n /** V2, `embedded` envelope only: the transaction-row pill. */\n pill?: Pill\n /**\n * V2: drilldown-only facts, e.g. `{ account_number: 'XXXXXXXX043' }`, or the guid list a list\n * drawer shows (`{ transaction_guids: [...] }`).\n */\n drilldown_data?: DrilldownData\n footer?: Footer\n /**\n * V2: the link inside the description: on the full card a drawer of its own (Low Account\n * Balance's \"Transactions ›\"); on mini and micro the card's action, sent instead of `primary_cta`.\n * Absent when the beat has no link.\n */\n in_description_cta?: InDescriptionCTA\n guid: string\n user_guid: string\n client_guid?: string\n /** Template name, e.g. `LargeTransactionV2`. */\n template?: string\n revision?: number\n /** Unix seconds. */\n created_at?: number\n /** Unix seconds. */\n updated_at?: number\n /** Unix seconds. */\n active_at?: number\n displayed_at?: number | null\n dismissed_at: number | null\n cta_clicked_at?: number | null\n has_been_displayed?: boolean\n is_dismissed: boolean\n is_deleted?: boolean\n is_hidden_by_system?: boolean\n is_obsolete?: boolean\n is_refreshable?: boolean\n is_sampled?: boolean\n is_zero_state?: boolean\n status?: string | null\n /**\n * V2 (LargeTransactionV2 onward): the entities the beat is about, carried on the payload so\n * action blocks do not have to reach back out to the beat envelope.\n */\n primary_account_guid?: string | null\n primary_transaction_guid?: string | null\n primary_category_guid?: string | null\n primary_merchant_guid?: string | null\n /**\n * V2: the repeating transaction a subscription beat is about (Upcoming Subscription, Subscription\n * Price Increase). Read by `recurring_transaction_block`, see\n * `actionBlocks/RecurringTransactionBlock/subscriptionBeats.ts`.\n */\n primary_repeating_transaction_guid?: string | null\n}\n\nexport interface Legends {\n label: string\n value: number\n additional_value?: number\n additional_value_type?: string\n value_type?: string\n}\n\nexport interface Logo {\n category_guid?: string\n category_name?: string\n institution_guid?: string\n institution_name?: string\n logo_color?: string\n /** Draw the `logo_name` icon filled (V2 `ICO-` logos). */\n logo_fill?: boolean\n logo_name?: string\n merchant_guid?: string\n merchant_name?: string\n}\n\nexport interface Subtitle {\n logo?: Logo\n text: string\n}\n\n/**\n * A V2 logo, shared by the header (`InsightsPayload.logo`) and every instance-slot row. It takes\n * one of two shapes:\n *\n * - **Entity**: `guid`, whose prefix (`INS-` / `MCH-` / `CAT-`) picks the logo component, plus an\n * optional `fallback_guid` for when that component's asset cannot load. Today that is the\n * category behind an `MCH-` guid, which `MerchantLogo` shows as a tile when the merchant image\n * is missing.\n * - **Status icon**: `icon` (an mx-icons name, e.g. `warning`) and `color` (a theme palette path,\n * e.g. `error.main`), for subheaders that show a state rather than an entity (V1's\n * `logo_name` / `logo_color`).\n *\n * `alt` is the accessible name for either shape. A logo sends one shape; if it sends both, `icon`\n * wins, as `logo_name` does in V1's `SubHeader`.\n */\nexport interface LogoRef {\n alt?: string\n color?: string\n fallback_guid?: string\n guid?: string\n icon?: string\n}\n\nexport interface CategoryLogo {\n category_guid: string\n category_name: string\n}\n\nexport interface MerchantLogo {\n merchant_guid: string\n merchant_name: string\n}\n\nexport interface InstitutionLogo {\n institution_guid: string\n institution_name: string\n}\n\nexport interface Description {\n text: string\n html_text: string\n}\n\n/**\n * Post message a CTA press sends to the host, resolved by the backend for the beat's current\n * state. Absent/null = the backend has not described one: the frontend uses the template's\n * interim `ctaPostMessages` entry if there is one, else sends no message. `payload` carries only\n * beat-specific extras; the frontend always adds `beat_guid`, `beat_template`, `user_guid` and\n * `action_string`.\n */\nexport interface CtaPostMessageMetadata {\n /** Post message event; defaults to `beat/cta`. */\n event?: string | null\n /** Operation the host should perform, e.g. `op_1`, `op_2`, `op_3`. Defaults to `op_1`. */\n action?: string | null\n /** Beat-specific fields merged into the message metadata (account ids, amounts, ...). */\n payload?: Record<string, unknown> | null\n}\n\nexport interface PrimaryCTA {\n label: string\n block: string\n variant?: string | null\n /**\n * What pressing the CTA does beyond opening its block, named by the backend (ditto\n * `primary_cta_action`, passed through by tupac). Looked up in `insights-v2/ctaActions`;\n * null / absent / unknown means the CTA only opens its block or posts its message.\n */\n action_key?: string | null\n metadata?: CtaPostMessageMetadata | null\n /** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */\n drilldown_data?: DrilldownData\n}\n\nexport interface SecondaryCTA {\n label: string\n /**\n * Drawer block to open. `null` when the CTA opens nothing (ditto `secondary_cta_block: null`,\n * e.g. Fee Detected's \"Contact Support\"): the press posts its message to the host instead.\n */\n block: string | null\n variant?: string | null\n /** See `PrimaryCTA.action_key` (ditto `secondary_cta_action`). */\n action_key?: string | null\n metadata?: CtaPostMessageMetadata | null\n /** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */\n drilldown_data?: DrilldownData\n}\n\n/**\n * The description link: its label, the action block it opens and, like a CTA's, the variant that\n * tells that block what to show (ditto's `in_description_cta_variant`, e.g. `low_balance`). On mini\n * and micro it words the card's action; `block` is empty when that action opens no drawer.\n */\nexport interface InDescriptionCTA {\n label: string\n block?: string\n variant?: string | null\n action_key?: string | null\n /** tupac's flat host message for the link (`action` plus fields), e.g. Switch's op_1. */\n post_message_metadata?: ({ action?: string | null } & Record<string, unknown>) | null\n /** Figures this CTA's drilldown renders (e.g. a summary header's amount/dates/count). */\n drilldown_data?: DrilldownData\n}\n\nexport interface Footer {\n /** V1: a pre-formatted date string. */\n date?: string\n /**\n * The account label, on both V1 and V2. The backend resolves the plurality: one account sends\n * its name (e.g. `Epic Bank`), several send a localized count (e.g. `2 Accounts`), none sends\n * `null` (tupac's `footer_account_label` returns nil for a beat with no accounts, as Payday Cash\n * Left Over Status does). There is deliberately no separate `accounts` key.\n */\n account?: string | null\n /**\n * V2 (LargeTransactionV2 onward): unix seconds, formatted by `Footer` for display.\n * Replaces `date`; `date` wins when a beat somehow sends both.\n */\n timestamp?: number\n}\n\n// ============ Instance Slot Types ============\n// Values are the snake_case block names the backend sends on a beat's instance slot; the PascalCase\n// keys stay as friendly references (e.g. BlockType.BarChartBlock === 'bar_chart_block').\nexport const BlockType = {\n AccountBlock: 'account_block',\n AccountSwitchBlock: 'account_switch_block',\n BarChartBlock: 'bar_chart_block',\n BudgetBlock: 'budget_block',\n DirectDepositBlock: 'direct_deposit_block',\n ImageBlock: 'image_block',\n MerchantBlock: 'merchant_block',\n TransactionBlock: 'transaction_block',\n DonutChartBlock: 'donut_chart_block',\n DoubleBarChartBlock: 'double_bar_chart_block',\n DoubleProgressBarBlock: 'double_progress_bar_block',\n DebtsBlock: 'debts_block',\n GoalsBlock: 'goals_block',\n NoSpendDaysBlock: 'no_spend_days_block',\n RepeatingTransactionBlock: 'repeating_transaction_block',\n SaveExtraBlock: 'save_extra_block',\n SavingsAmountBlock: 'savings_amount_block',\n StepChartBlock: 'step_chart_block',\n SVGImageBlock: 'svg_image_block',\n UpcomingBillBlock: 'upcoming_bill_block',\n UpcomingSubscriptionBlock: 'upcoming_subscription_block',\n WithdrawalBlock: 'withdrawal_block',\n} as const\n\nexport type BlockType = (typeof BlockType)[keyof typeof BlockType]\n\n/**\n * A pre-localized row on a V2 instance slot. The backend resolves the copy and formatting; the\n * logo is a `LogoRef` (an entity guid or a status icon, see `logoFromRef`).\n */\nexport interface InstanceSlotRow {\n amount?: number\n logo?: LogoRef\n primary_text?: string\n secondary_text?: string\n}\n\n/**\n * V2: the slots a block's variant filled, keyed by slot name; ditto owns the names and omits any\n * slot it has no value for (`lib/ditto/blocks/<block>.yml`). Read with `slot()`, which names the\n * kind the caller expects.\n */\nexport interface InstanceSlotData {\n [slot: string]: unknown\n}\n\n/**\n * Reads one slot off a block's `data`; undefined when ditto did not fill it.\n *\n * The caller names the kind, because the slot's kind lives in the block's ditto schema and not\n * here — a block fills slots with whatever its card needs, so no fixed list of kinds would hold\n * (`account_switch_block` alone carries a boolean and `budget_block` a nested object).\n */\nexport const slot = <T = unknown>(\n data: InstanceSlotData | undefined,\n name: string,\n): T | undefined => data?.[name] as T | undefined\n\n/**\n * V2: which block renders this beat's content, and what it renders.\n *\n * `block` is optional: a beat whose template has no ditto config arrives as an empty `{}` and the\n * card renders its copy and CTAs alone, so a reader cannot assume a block is named. One interface\n * with `block` optional rather than a union with `Record<string, never>`, which TypeScript absorbs\n * — that union left `block` reading as required and typed a promise it did not keep.\n *\n * `variant` is optional for the same reason — the empty `{}` carries neither. Every ditto beat\n * file that names a block also names a variant, so a *filled* slot always has one; ditto drops a\n * nil variant rather than sending null, which is why the type is `string | null | undefined`.\n */\nexport interface InstanceSlot {\n block?: BlockType\n variant?: string | null\n /** The slots this block's variant declares in ditto. */\n data?: InstanceSlotData\n // Direct Deposit block (Set Up Direct Deposit, Switch Direct Deposit, Missing\n // Interest Rate): account facts carried flat on the slot, beside `data`.\n /** Primary line on the hero card, e.g. \"Epic Bank Checking, *1234\". */\n account_name?: string\n /** Secondary line beneath the name, e.g. \"Checking Account\" / \"Credit Card\". */\n account_type?: string\n /** Status pill label, e.g. \"Direct Deposit Not Set Up\" / \"Interest Rate: 19.99% APR\". */\n status_text?: string\n institution_guid?: string\n institution_name?: string\n // Shared across multiple V2 component blocks:\n // `amount` — UpcomingBillBlock (projected bill), UpcomingSubscriptionBlock (price per cadence).\n // (SavingsOpportunityBlock now reads `amount` off `instance_slot.data`, not this flat field.)\n // `merchant_guid` / `merchant_name` — UpcomingBillBlock (bill row logo + name),\n // UpcomingSubscriptionBlock (hero logo + name).\n /** Dollar amount (unformatted); the block formats it as currency. */\n amount?: number\n // Withdrawal block (Emergency Fund Withdrawal): one row flat on the slot - `amount` above is the\n // withdrawal, shown with its date line \"{amount_label} {withdrawal_date}\" over the balance row.\n /** Pre-localized lead-in for the date line, e.g. \"Withdrawn on\". */\n amount_label?: string\n /** The withdrawal's calendar day, `YYYY-MM-DD` (e.g. `2026-10-20`); the card formats it. */\n withdrawal_date?: string\n /** Account balance after the withdrawal (unformatted); the card formats it as currency. */\n balance?: number\n /** Pre-localized label for the balance row, e.g. \"Balance\". */\n balance_label?: string\n /** Merchant guid used to render the inline / hero logo via `LogoFromGuid` (`MCH-...`). */\n merchant_guid?: string\n /** Merchant display name — shown as logo alt text and as the merchant/hero label. */\n merchant_name?: string\n // Upcoming Bill block (Projected Bill Payment): the block derives the Sun→Sat week strip\n // around `due_date` at render time.\n /** Due date of the projected bill in ISO form (e.g. `2025-10-30`). */\n due_date?: string\n // Account block (confetti tile): V1 flat fallback for the label line when `instance_slot.data` is\n // absent — `AccountBlock` reads `data.sub_text` first, then this. (The Savings Opportunity coupon\n // now reads `primary_text`/`amount` off `data`, so it no longer reads this flat field.)\n /** Caption/label line for the confetti tile (V1 fallback). */\n label?: string\n // Upcoming Subscription block: billing cadence for the price pill (`${amount} / ${cadence}`).\n /** Billing cadence word that trails the price, e.g. `month` or `year`. */\n cadence?: string\n // Account block (status on/off-track, deposit, payoff variants): card content carried flat on\n // the slot (the V2 contract drops `instance_slot_data`). `amount`, `label`, `merchant_guid`,\n // `merchant_name`, `institution_guid`, `institution_name` are shared fields declared above.\n /** Secondary line beneath the amount on the standard card, e.g. \"Remaining after expenses\". */\n sub_text?: string\n /** Header bar text (standard card) or the colored category strip label (deposit cards). */\n header_text?: string\n /** Generic-tile icon name used when no merchant/institution logo is available. */\n icon_name?: string\n /** Category guid used to render the deposit-card logo fallback. */\n category_guid?: string\n // Debts block: `amount` is the current balance; the shared account/institution fields above.\n /** Projected payoff date in ISO form (e.g. `2027-03-01`); the block formats it as month + year. */\n payoff_date?: string\n /** Monthly payment toward the debt (unformatted); the block formats it as currency. */\n monthly_payment?: number\n /** Annual interest rate as a percentage (e.g. `19.99`); the block formats it as a percent. */\n interest_rate?: number\n start_date?: string\n no_spend_days?: string[]\n}\n"],"mappings":"AA2UA,IAAa,IAAA,CACX,GACA,MACkB,IAAO,CAAA"}
|
|
@@ -1,89 +1,137 @@
|
|
|
1
|
-
import { useInsightsV2Store as
|
|
2
|
-
import { slot as
|
|
3
|
-
import { getCategoryColor as
|
|
4
|
-
import { TinyBarChart as
|
|
5
|
-
import { BAR_ARIA_LABEL as
|
|
6
|
-
import { ThemedSVGImage as
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
|
|
1
|
+
import { useGlobalStore as O, useInsightsV2Store as V } from "../../common/context/hooks.es.js";
|
|
2
|
+
import { slot as b } from "../../core/types/Insights.es.js";
|
|
3
|
+
import { getCategoryColor as N } from "../../core/utils/CategoryUtil.es.js";
|
|
4
|
+
import { TinyBarChart as W } from "../../common/components/TinyBarChart.es.js";
|
|
5
|
+
import { BAR_ARIA_LABEL as G, buildChartData as P, buildDataSeries as j } from "../blocks/ChartBlocks/BarchartBlock/utils.es.js";
|
|
6
|
+
import { ThemedSVGImage as K } from "./chartblocks/ThemedSVGImage.es.js";
|
|
7
|
+
import { postCtaMessage as F } from "../ctaActions/runCtaAction.es.js";
|
|
8
|
+
import _ from "./LogoFromGuid.es.js";
|
|
9
|
+
import { createVariantBlock as H } from "../blocks/createVariantBlock.es.js";
|
|
10
|
+
import { ActionBlockLayer as M } from "../actionBlocks/ActionBlockLayer.es.js";
|
|
11
|
+
import w from "./BadgedTile.es.js";
|
|
12
|
+
import { InsightOptionsMenu as X } from "./InsightOptionsMenu.es.js";
|
|
13
|
+
import { ReactComponent as Z } from "./shared/images/FeeDetected.es.js";
|
|
14
|
+
import $ from "./StatusBadge.es.js";
|
|
15
|
+
import u from "@mui/material/Stack";
|
|
16
|
+
import { P as q, Text as v } from "@mxenabled/mxui";
|
|
17
|
+
import J from "@mui/material/Button";
|
|
18
|
+
import { Icon as y } from "@mxenabled/mx-icons";
|
|
19
|
+
import { Fragment as Q, jsx as t, jsxs as d } from "react/jsx-runtime";
|
|
20
|
+
import e from "@mui/material/Box";
|
|
21
|
+
import U from "@mui/material/Card";
|
|
22
|
+
var Y = 2, c = 44, k = 36, tt = 75, rt = 74, g = {
|
|
22
23
|
left: "50%",
|
|
23
24
|
position: "absolute",
|
|
24
25
|
top: "50%",
|
|
25
26
|
transform: "translate(-50%, -50%)"
|
|
26
|
-
}, C = ({ logo: r }) => r ? /* @__PURE__ */ t(
|
|
27
|
+
}, C = ({ logo: r }) => r ? /* @__PURE__ */ t(_, {
|
|
27
28
|
logo: r,
|
|
28
|
-
size:
|
|
29
|
-
}) : /* @__PURE__ */ t(
|
|
29
|
+
size: c
|
|
30
|
+
}) : /* @__PURE__ */ t(e, {
|
|
30
31
|
"data-testid": "compact-insight-card-fallback-icon",
|
|
31
32
|
sx: {
|
|
32
33
|
alignItems: "center",
|
|
33
34
|
bgcolor: "primary.lighter",
|
|
34
35
|
display: "flex",
|
|
35
|
-
height:
|
|
36
|
+
height: c,
|
|
36
37
|
justifyContent: "center",
|
|
37
|
-
width:
|
|
38
|
+
width: c
|
|
38
39
|
},
|
|
39
|
-
children: /* @__PURE__ */ t(
|
|
40
|
+
children: /* @__PURE__ */ t(y, {
|
|
40
41
|
color: "primary",
|
|
41
42
|
fill: !0,
|
|
42
43
|
name: "lightbulb",
|
|
43
44
|
size: 24
|
|
44
45
|
})
|
|
45
|
-
}),
|
|
46
|
+
}), I = ({ logo: r }) => /* @__PURE__ */ t(w, {
|
|
46
47
|
center: /* @__PURE__ */ t(C, { logo: r }),
|
|
47
|
-
size:
|
|
48
|
+
size: c,
|
|
48
49
|
sx: { flexShrink: 0 }
|
|
49
|
-
}),
|
|
50
|
+
}), it = ({ logo: r }) => /* @__PURE__ */ t(w, {
|
|
50
51
|
center: /* @__PURE__ */ t(C, { logo: r }),
|
|
51
|
-
size:
|
|
52
|
+
size: c,
|
|
52
53
|
sx: { flexShrink: 0 },
|
|
53
|
-
topRight: /* @__PURE__ */ t(
|
|
54
|
-
}),
|
|
54
|
+
topRight: /* @__PURE__ */ t($, { tone: "warning" })
|
|
55
|
+
}), et = () => /* @__PURE__ */ t(K, {
|
|
55
56
|
divProps: { style: {
|
|
56
57
|
flexShrink: 0,
|
|
57
58
|
lineHeight: 0,
|
|
58
59
|
width: "max-content"
|
|
59
60
|
} },
|
|
60
|
-
image:
|
|
61
|
-
}),
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
image: Z
|
|
62
|
+
}), x = 24, ot = () => /* @__PURE__ */ t(e, {
|
|
63
|
+
"aria-hidden": !0,
|
|
64
|
+
"data-testid": "linked-logos-badge",
|
|
65
|
+
sx: {
|
|
66
|
+
...g,
|
|
67
|
+
alignItems: "center",
|
|
68
|
+
bgcolor: "primary.main",
|
|
69
|
+
border: "2px solid",
|
|
70
|
+
borderColor: "primary.contrastText",
|
|
71
|
+
borderRadius: "12px",
|
|
72
|
+
display: "flex",
|
|
73
|
+
justifyContent: "center",
|
|
74
|
+
p: 2,
|
|
75
|
+
zIndex: 1
|
|
76
|
+
},
|
|
77
|
+
children: /* @__PURE__ */ t(y, {
|
|
78
|
+
name: "link",
|
|
79
|
+
size: 16,
|
|
80
|
+
sx: { color: "primary.contrastText" }
|
|
81
|
+
})
|
|
82
|
+
}), at = () => /* @__PURE__ */ t(e, {
|
|
83
|
+
"aria-hidden": !0,
|
|
84
|
+
"data-testid": "linked-logos-badge",
|
|
85
|
+
sx: {
|
|
86
|
+
...g,
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
bgcolor: "primary.contrastText",
|
|
89
|
+
borderRadius: "50%",
|
|
90
|
+
display: "flex",
|
|
91
|
+
height: x,
|
|
92
|
+
justifyContent: "center",
|
|
93
|
+
overflow: "hidden",
|
|
94
|
+
width: x,
|
|
95
|
+
zIndex: 1
|
|
96
|
+
},
|
|
97
|
+
children: /* @__PURE__ */ t(y, {
|
|
98
|
+
name: "swap_vertical_circle",
|
|
99
|
+
size: x * 1.2,
|
|
100
|
+
sx: {
|
|
101
|
+
color: "primary.main",
|
|
102
|
+
flexShrink: 0,
|
|
103
|
+
transform: "rotate(-90deg)"
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}), nt = ({ data: r, template: o }) => {
|
|
107
|
+
const { from_logo: l, to_logo: a } = r ?? {}, i = o === "SwitchDirectDeposit";
|
|
108
|
+
return /* @__PURE__ */ d(e, {
|
|
64
109
|
sx: {
|
|
65
110
|
flexShrink: 0,
|
|
66
|
-
height:
|
|
111
|
+
height: rt,
|
|
67
112
|
position: "relative",
|
|
68
|
-
width:
|
|
113
|
+
width: tt
|
|
69
114
|
},
|
|
70
115
|
children: [
|
|
71
|
-
/* @__PURE__ */ t(
|
|
116
|
+
/* @__PURE__ */ t(e, {
|
|
72
117
|
"aria-hidden": !0,
|
|
73
118
|
"data-testid": "linked-logos-ring",
|
|
74
119
|
sx: {
|
|
75
|
-
...
|
|
76
|
-
bgcolor: "primary.light",
|
|
120
|
+
...g,
|
|
77
121
|
borderRadius: "50%",
|
|
78
122
|
height: 36,
|
|
79
|
-
width: 36
|
|
123
|
+
width: 36,
|
|
124
|
+
...i ? {
|
|
125
|
+
border: "2px solid",
|
|
126
|
+
borderColor: "primary.light"
|
|
127
|
+
} : { bgcolor: "primary.light" }
|
|
80
128
|
}
|
|
81
129
|
}),
|
|
82
|
-
/* @__PURE__ */ t(
|
|
130
|
+
/* @__PURE__ */ t(e, {
|
|
83
131
|
"aria-hidden": !0,
|
|
84
132
|
"data-testid": "linked-logos-ring",
|
|
85
133
|
sx: {
|
|
86
|
-
...
|
|
134
|
+
...g,
|
|
87
135
|
border: "1px solid",
|
|
88
136
|
borderColor: "divider",
|
|
89
137
|
borderRadius: "50%",
|
|
@@ -91,7 +139,7 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
91
139
|
width: 48
|
|
92
140
|
}
|
|
93
141
|
}),
|
|
94
|
-
/* @__PURE__ */ t(
|
|
142
|
+
/* @__PURE__ */ t(e, {
|
|
95
143
|
sx: {
|
|
96
144
|
borderRadius: "4px",
|
|
97
145
|
left: 0,
|
|
@@ -99,12 +147,12 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
99
147
|
position: "absolute",
|
|
100
148
|
top: 0
|
|
101
149
|
},
|
|
102
|
-
children: /* @__PURE__ */ t(
|
|
103
|
-
logo:
|
|
104
|
-
size:
|
|
150
|
+
children: /* @__PURE__ */ t(_, {
|
|
151
|
+
logo: l,
|
|
152
|
+
size: k
|
|
105
153
|
})
|
|
106
154
|
}),
|
|
107
|
-
/* @__PURE__ */ t(
|
|
155
|
+
/* @__PURE__ */ t(e, {
|
|
108
156
|
sx: {
|
|
109
157
|
borderRadius: "4px",
|
|
110
158
|
bottom: 0,
|
|
@@ -112,57 +160,37 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
112
160
|
position: "absolute",
|
|
113
161
|
right: 0
|
|
114
162
|
},
|
|
115
|
-
children: /* @__PURE__ */ t(
|
|
163
|
+
children: /* @__PURE__ */ t(_, {
|
|
116
164
|
logo: a,
|
|
117
|
-
size:
|
|
165
|
+
size: k
|
|
118
166
|
})
|
|
119
167
|
}),
|
|
120
|
-
/* @__PURE__ */ t(
|
|
121
|
-
"aria-hidden": !0,
|
|
122
|
-
"data-testid": "linked-logos-badge",
|
|
123
|
-
sx: {
|
|
124
|
-
...f,
|
|
125
|
-
alignItems: "center",
|
|
126
|
-
bgcolor: "primary.main",
|
|
127
|
-
border: "2px solid",
|
|
128
|
-
borderColor: "primary.contrastText",
|
|
129
|
-
borderRadius: "12px",
|
|
130
|
-
display: "flex",
|
|
131
|
-
justifyContent: "center",
|
|
132
|
-
p: 2,
|
|
133
|
-
zIndex: 1
|
|
134
|
-
},
|
|
135
|
-
children: /* @__PURE__ */ t(v, {
|
|
136
|
-
name: "link",
|
|
137
|
-
size: 16,
|
|
138
|
-
sx: { color: "primary.contrastText" }
|
|
139
|
-
})
|
|
140
|
-
})
|
|
168
|
+
i ? /* @__PURE__ */ t(at, {}) : /* @__PURE__ */ t(ot, {})
|
|
141
169
|
]
|
|
142
170
|
});
|
|
143
|
-
},
|
|
144
|
-
const o =
|
|
171
|
+
}, st = ({ data: r }) => {
|
|
172
|
+
const o = j(b(r, "series"));
|
|
145
173
|
if (!o.length) return null;
|
|
146
|
-
const
|
|
147
|
-
return /* @__PURE__ */ t(
|
|
174
|
+
const l = b(r, "category_guid"), a = P(o, G).slice(-3);
|
|
175
|
+
return /* @__PURE__ */ t(e, {
|
|
148
176
|
"data-testid": "compact-insight-card-bar-chart",
|
|
149
177
|
sx: { flexShrink: 0 },
|
|
150
|
-
children: /* @__PURE__ */ t(
|
|
151
|
-
color: (
|
|
152
|
-
data:
|
|
178
|
+
children: /* @__PURE__ */ t(W, {
|
|
179
|
+
color: (i) => l ? N(l, i) : i.palette.primary.main,
|
|
180
|
+
data: a
|
|
153
181
|
})
|
|
154
182
|
});
|
|
155
|
-
},
|
|
183
|
+
}, lt = H({
|
|
156
184
|
variants: {
|
|
157
|
-
category_color:
|
|
158
|
-
linked_logos:
|
|
159
|
-
minimal:
|
|
160
|
-
with_illustration:
|
|
161
|
-
with_warning_badge:
|
|
185
|
+
category_color: st,
|
|
186
|
+
linked_logos: nt,
|
|
187
|
+
minimal: I,
|
|
188
|
+
with_illustration: et,
|
|
189
|
+
with_warning_badge: it
|
|
162
190
|
},
|
|
163
|
-
fallback:
|
|
191
|
+
fallback: I,
|
|
164
192
|
name: "InsightVisual"
|
|
165
|
-
}),
|
|
193
|
+
}), dt = {
|
|
166
194
|
BillAmountNotStandard: {
|
|
167
195
|
key: "in_description_cta",
|
|
168
196
|
trigger: "inDescriptionCta"
|
|
@@ -170,26 +198,37 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
170
198
|
ConnectDiscoveredAccountsV2: {
|
|
171
199
|
key: "in_description_cta",
|
|
172
200
|
trigger: "inDescriptionCta"
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
201
|
+
},
|
|
202
|
+
SwitchDirectDeposit: { key: "in_description_cta" }
|
|
203
|
+
}, ct = (r) => r.stopPropagation(), Tt = ({ beat: r, isCtaDisabled: o = !1, onCtaClick: l, onTap: a, payload: i }) => {
|
|
204
|
+
const S = O(), { openActionDrawer: D } = V(), { description: B, instance_slot: m, primary_cta: p, title: L } = i, n = dt[i.template ?? ""], h = n ? i[n.key] : p, f = i.in_description_cta?.post_message_metadata, E = !n || !!n.trigger || !!r && !!f, A = b(m.data, "logo"), R = (s) => {
|
|
205
|
+
if (s.stopPropagation(), !(o || !h)) {
|
|
206
|
+
if (n) {
|
|
207
|
+
if (r && n.trigger) D({
|
|
179
208
|
guid: r.guid,
|
|
180
|
-
trigger:
|
|
209
|
+
trigger: n.trigger
|
|
181
210
|
});
|
|
211
|
+
else if (r && f) {
|
|
212
|
+
const { action: T, ...z } = f;
|
|
213
|
+
F({
|
|
214
|
+
beat: r,
|
|
215
|
+
stores: S
|
|
216
|
+
}, {
|
|
217
|
+
action: T,
|
|
218
|
+
payload: z
|
|
219
|
+
});
|
|
220
|
+
}
|
|
182
221
|
return;
|
|
183
222
|
}
|
|
184
|
-
|
|
223
|
+
p && l?.(p);
|
|
185
224
|
}
|
|
186
225
|
};
|
|
187
|
-
return /* @__PURE__ */
|
|
226
|
+
return /* @__PURE__ */ d(Q, { children: [/* @__PURE__ */ d(U, {
|
|
188
227
|
"data-testid": "compact-insight-card",
|
|
189
|
-
...
|
|
190
|
-
onClick:
|
|
191
|
-
onKeyDown: (
|
|
192
|
-
|
|
228
|
+
...a ? {
|
|
229
|
+
onClick: a,
|
|
230
|
+
onKeyDown: (s) => {
|
|
231
|
+
s.target === s.currentTarget && (s.key === "Enter" || s.key === " ") && (s.preventDefault(), a?.());
|
|
193
232
|
},
|
|
194
233
|
role: "button",
|
|
195
234
|
tabIndex: 0
|
|
@@ -200,9 +239,9 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
200
239
|
pl: 8,
|
|
201
240
|
pr: 16,
|
|
202
241
|
pt: 8,
|
|
203
|
-
...
|
|
242
|
+
...a && { cursor: "pointer" }
|
|
204
243
|
},
|
|
205
|
-
children: [/* @__PURE__ */
|
|
244
|
+
children: [/* @__PURE__ */ d(u, {
|
|
206
245
|
sx: {
|
|
207
246
|
alignItems: "center",
|
|
208
247
|
flexDirection: "row",
|
|
@@ -211,41 +250,50 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
211
250
|
pl: 8,
|
|
212
251
|
pt: 4
|
|
213
252
|
},
|
|
214
|
-
children: [/* @__PURE__ */ t(
|
|
253
|
+
children: [/* @__PURE__ */ t(v, {
|
|
215
254
|
bold: !0,
|
|
216
255
|
variant: "body1",
|
|
217
|
-
children:
|
|
218
|
-
}), /* @__PURE__ */ t(
|
|
219
|
-
onClick:
|
|
256
|
+
children: L ?? ""
|
|
257
|
+
}), /* @__PURE__ */ t(e, {
|
|
258
|
+
onClick: ct,
|
|
220
259
|
sx: { display: "flex" },
|
|
221
|
-
children: /* @__PURE__ */ t(
|
|
260
|
+
children: /* @__PURE__ */ t(X, {})
|
|
222
261
|
})]
|
|
223
|
-
}), /* @__PURE__ */
|
|
262
|
+
}), /* @__PURE__ */ d(u, {
|
|
224
263
|
sx: {
|
|
264
|
+
alignItems: "center",
|
|
225
265
|
flexDirection: "row",
|
|
226
266
|
gap: 16,
|
|
227
|
-
pl: 8
|
|
228
|
-
pt: 4
|
|
267
|
+
pl: 8
|
|
229
268
|
},
|
|
230
|
-
children: [/* @__PURE__ */
|
|
269
|
+
children: [/* @__PURE__ */ d(u, {
|
|
231
270
|
sx: {
|
|
271
|
+
alignSelf: "stretch",
|
|
232
272
|
flex: 1,
|
|
233
273
|
gap: 8,
|
|
234
|
-
minWidth: 0
|
|
274
|
+
minWidth: 0,
|
|
275
|
+
pt: 2
|
|
235
276
|
},
|
|
236
|
-
children: [/* @__PURE__ */ t(
|
|
237
|
-
dangerouslySetInnerHTML: { __html:
|
|
277
|
+
children: [/* @__PURE__ */ t(q, {
|
|
278
|
+
dangerouslySetInnerHTML: { __html: B.html_text },
|
|
238
279
|
"data-testid": "compact-insight-card-description",
|
|
239
|
-
sx: {
|
|
280
|
+
sx: {
|
|
281
|
+
display: "-webkit-box",
|
|
282
|
+
fontWeight: "fontWeightRegular",
|
|
283
|
+
overflow: "hidden",
|
|
284
|
+
textOverflow: "ellipsis",
|
|
285
|
+
WebkitBoxOrient: "vertical",
|
|
286
|
+
WebkitLineClamp: Y
|
|
287
|
+
},
|
|
240
288
|
variant: "subtitle2"
|
|
241
|
-
}),
|
|
289
|
+
}), h?.label && E && /* @__PURE__ */ t(J, {
|
|
242
290
|
"aria-disabled": o,
|
|
243
|
-
onClick:
|
|
291
|
+
onClick: R,
|
|
244
292
|
size: "small",
|
|
245
293
|
sx: {
|
|
246
294
|
alignSelf: "flex-start",
|
|
247
|
-
mt: 5,
|
|
248
295
|
ml: -8,
|
|
296
|
+
my: 0,
|
|
249
297
|
px: 8,
|
|
250
298
|
...o && {
|
|
251
299
|
color: "text.disabled",
|
|
@@ -253,22 +301,23 @@ var d = 44, y = 36, M = 75, X = 74, f = {
|
|
|
253
301
|
}
|
|
254
302
|
},
|
|
255
303
|
variant: "text",
|
|
256
|
-
children: /* @__PURE__ */ t(
|
|
304
|
+
children: /* @__PURE__ */ t(v, {
|
|
257
305
|
bold: !0,
|
|
258
306
|
variant: "body2",
|
|
259
|
-
children:
|
|
307
|
+
children: h.label
|
|
260
308
|
})
|
|
261
309
|
})]
|
|
262
|
-
}), /* @__PURE__ */ t(
|
|
263
|
-
data:
|
|
264
|
-
logo:
|
|
265
|
-
|
|
310
|
+
}), /* @__PURE__ */ t(lt, {
|
|
311
|
+
data: m.data,
|
|
312
|
+
logo: A,
|
|
313
|
+
template: i.template,
|
|
314
|
+
variant: m.variant
|
|
266
315
|
})]
|
|
267
316
|
})]
|
|
268
|
-
}), r && /* @__PURE__ */ t(
|
|
317
|
+
}), r && /* @__PURE__ */ t(M, { beat: r })] });
|
|
269
318
|
};
|
|
270
319
|
export {
|
|
271
|
-
|
|
320
|
+
Tt as default
|
|
272
321
|
};
|
|
273
322
|
|
|
274
323
|
//# sourceMappingURL=CompactInsightCard.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompactInsightCard.es.js","names":[],"sources":["../../../src/insights-v2/components/CompactInsightCard.tsx"],"sourcesContent":["import type { KeyboardEvent, MouseEvent } from 'react'\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 { Icon } from '@mxenabled/mx-icons'\nimport { P, Text } from '@mxenabled/mxui'\n\nimport { useInsightsV2Store } from '../../common'\nimport { TinyBarChart } from '../../common/components/TinyBarChart'\nimport { slot } from '../../core'\nimport type {\n Beat,\n DataSeries,\n InsightsPayload,\n InstanceSlotData,\n LogoRef,\n PrimaryCTA,\n} from '../../core'\nimport type { ActionDrawerTrigger } from '../../core/stores'\nimport type { AccountSwitchBlockV2Data } from '../../core/types/InsightsInstanceSlotData'\nimport { getCategoryColor } from '../../core/utils/CategoryUtil'\nimport {\n BAR_ARIA_LABEL,\n buildChartData,\n buildDataSeries,\n} from '../blocks/ChartBlocks/BarchartBlock/utils'\nimport { ActionBlockLayer } from '../actionBlocks/ActionBlockLayer'\nimport { createVariantBlock } from '../blocks/createVariantBlock'\nimport BadgedTile from './BadgedTile'\nimport InsightOptionsMenu from './InsightOptionsMenu'\nimport LogoFromGuid from './LogoFromGuid'\nimport { ReactComponent as FeeDetectedIllustration } from './shared/images/FeeDetected.svg'\nimport { ThemedSVGImage } from './chartblocks/ThemedSVGImage'\nimport StatusBadge from './StatusBadge'\n\n/** Figma 1941:111897: the tile the logo fills is 44px (rows are 32, the notification row 33). */\nconst ICON_SIZE = 44\n\ninterface InsightVisualProps {\n /** The slot's `logo`, when ditto's variant declares and fills one. */\n logo?: LogoRef | null\n /** The slot's whole `data`, for a visual drawn from more than the logo (`category_color`, `linked_logos`). */\n data?: InstanceSlotData\n}\n\n// Figma 1943:219189: 36px logos at opposite corners of a 75×74 box, the badge and rings centred.\nconst LINKED_LOGO_SIZE = 36\nconst LINKED_BOX_WIDTH = 75\nconst LINKED_BOX_HEIGHT = 74\n\nconst CENTRED = {\n left: '50%',\n position: 'absolute',\n top: '50%',\n transform: 'translate(-50%, -50%)',\n} as const\n\n/** Figma 222:33404 (Embedded | Bill Amount Not Standard) shows the latest three bars. */\nconst BAR_CHART_BAR_COUNT = 3\n\n/** The slot's logo, or the neutral insight glyph when the slot carries none. */\nconst LogoOrFallback = ({ logo }: InsightVisualProps) =>\n logo ? (\n <LogoFromGuid logo={logo} size={ICON_SIZE} />\n ) : (\n <Box\n data-testid=\"compact-insight-card-fallback-icon\"\n sx={{\n alignItems: 'center',\n bgcolor: 'primary.lighter',\n display: 'flex',\n height: ICON_SIZE,\n justifyContent: 'center',\n width: ICON_SIZE,\n }}\n >\n <Icon color=\"primary\" fill={true} name=\"lightbulb\" size={24} />\n </Box>\n )\n\n/** Micro, and any variant not registered below: the logo alone. */\nconst PlainVisual = ({ logo }: InsightVisualProps) => (\n <BadgedTile center={<LogoOrFallback logo={logo} />} size={ICON_SIZE} sx={{ flexShrink: 0 }} />\n)\n\n/** ditto `with_warning_badge`: the logo badged with a status glyph (Figma 1941:111897). */\nconst WarningBadgeVisual = ({ logo }: InsightVisualProps) => (\n <BadgedTile\n center={<LogoOrFallback logo={logo} />}\n size={ICON_SIZE}\n sx={{ flexShrink: 0 }}\n topRight={<StatusBadge tone=\"warning\" />}\n />\n)\n\n/**\n * ditto `with_illustration`: artwork in place of the logo tile (Fee Detected's search illustration).\n * A single SVG themed at runtime by `ThemedSVGImage`: its `brand300` elements are recolored to the\n * theme's `primary.main` (so it follows dark mode), while `fill=\"white\"` areas stay white. The\n * slot's `logo` is deliberately unused: the illustration replaces the tile.\n */\nconst IllustrationVisual = () => {\n return (\n <ThemedSVGImage\n divProps={{ style: { flexShrink: 0, lineHeight: 0, width: 'max-content' } }}\n image={FeeDetectedIllustration}\n />\n )\n}\n\n/**\n * ditto `linked_logos` (Figma 1943:219189, Embedded | Discovered Accounts): the two institutions at\n * opposite corners of a 75×74 box — `from_logo` top-left, `to_logo` bottom-right — meeting on the\n * diagonal, with a link badge over two rings at the centre. The rings and badge are decorative (`aria-hidden`); each\n * logo's accessible name is its `alt`.\n */\nconst LinkedLogosVisual = ({ data }: InsightVisualProps) => {\n const { from_logo, to_logo } = (data ?? {}) as AccountSwitchBlockV2Data\n\n return (\n <Box\n sx={{\n flexShrink: 0,\n height: LINKED_BOX_HEIGHT,\n position: 'relative',\n width: LINKED_BOX_WIDTH,\n }}\n >\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-ring\"\n sx={{ ...CENTRED, bgcolor: 'primary.light', borderRadius: '50%', height: 36, width: 36 }}\n />\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-ring\"\n sx={{\n ...CENTRED,\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: '50%',\n height: 48,\n width: 48,\n }}\n />\n <Box sx={{ borderRadius: '4px', left: 0, overflow: 'hidden', position: 'absolute', top: 0 }}>\n <LogoFromGuid logo={from_logo} size={LINKED_LOGO_SIZE} />\n </Box>\n <Box\n sx={{ borderRadius: '4px', bottom: 0, overflow: 'hidden', position: 'absolute', right: 0 }}\n >\n <LogoFromGuid logo={to_logo} size={LINKED_LOGO_SIZE} />\n </Box>\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-badge\"\n sx={{\n ...CENTRED,\n alignItems: 'center',\n bgcolor: 'primary.main',\n border: '2px solid',\n borderColor: 'primary.contrastText',\n borderRadius: '12px',\n display: 'flex',\n justifyContent: 'center',\n p: 2,\n zIndex: 1,\n }}\n >\n <Icon name=\"link\" size={16} sx={{ color: 'primary.contrastText' }} />\n </Box>\n </Box>\n )\n}\n\n/**\n * ditto `category_color`: a tiny bar chart of the latest bars in place of the logo tile, tinted by\n * the slot's category, the newest bar solid (Figma 222:33404). Draws nothing without a series.\n */\nconst BarChartVisual = ({ data }: InsightVisualProps) => {\n const series = buildDataSeries(slot<DataSeries>(data, 'series'))\n if (!series.length) return null\n\n const categoryGuid = slot<string>(data, 'category_guid')\n const bars = buildChartData(series, BAR_ARIA_LABEL).slice(-BAR_CHART_BAR_COUNT)\n\n return (\n <Box data-testid=\"compact-insight-card-bar-chart\" sx={{ flexShrink: 0 }}>\n <TinyBarChart\n color={(theme) =>\n categoryGuid ? getCategoryColor(categoryGuid, theme) : theme.palette.primary.main\n }\n data={bars}\n />\n </Box>\n )\n}\n\n/**\n * The card's right-hand visual, one component per ditto layout variant.\n *\n * A variant names a rendering *shape*, not an insight or a surface — `with_header` already serves\n * both Large Transaction and Fee Detected — so an insight wanting the badged logo reuses\n * `with_warning_badge`. Only a genuinely different visual registers a new entry, and existing ones\n * are never touched.\n *\n * `minimal` draws the plain logo and is registered explicitly even though it matches the fallback:\n * micro and notification resolve it on every render, and an unregistered variant warns. The\n * fallback is for variants we genuinely do not know.\n */\nconst InsightVisual = createVariantBlock<InsightVisualProps>({\n variants: {\n category_color: BarChartVisual,\n linked_logos: LinkedLogosVisual,\n minimal: PlainVisual,\n with_illustration: IllustrationVisual,\n with_warning_badge: WarningBadgeVisual,\n },\n fallback: PlainVisual,\n name: 'InsightVisual',\n})\n\n/**\n * Beats whose embedded CTA the card reads from its own envelope key and opens in this card's\n * `ActionBlockLayer`, as a bar click does on the full card. The trigger names that key, so the layer\n * resolves the same CTA. Every other beat keeps `primary_cta` and hands it to the host's `onCtaClick`\n * (e.g. `contact_support_block`). A new beat adds its template here.\n */\nconst BEAT_CTA_CONFIG: Record<\n string,\n { key: 'primary_cta' | 'in_description_cta'; trigger: ActionDrawerTrigger }\n> = {\n BillAmountNotStandard: { key: 'in_description_cta', trigger: 'inDescriptionCta' },\n ConnectDiscoveredAccountsV2: { key: 'in_description_cta', trigger: 'inDescriptionCta' },\n}\n\nexport interface CompactInsightCardProps {\n /** A `micro` or `embedded` envelope. */\n payload: InsightsPayload\n /** The envelope's beat; when given, the card mounts its `ActionBlockLayer` for CTA drawers. */\n beat?: Beat\n /**\n * Makes the whole card the tap target. What a tap does is the host's decision (post to the\n * host app, open a drawer, …); the card never opens anything itself. Omit for a static card.\n */\n onTap?: () => void\n /** Receives the primary CTA when tapped. The card does not resolve the CTA's block itself. */\n onCtaClick?: (cta: PrimaryCTA) => void\n /**\n * Renders the CTA disabled — label shown, `aria-disabled`, not tappable — for a host that cannot\n * honour it (e.g. `contact_support_block` when the client's `enable_support_requests` is off).\n */\n isCtaDisabled?: boolean\n}\n\nconst stopPropagation = (event: MouseEvent) => event.stopPropagation()\n\n/**\n * The one compact renderer the micro and embedded envelopes share (`insights_v2.2.md` §8.3).\n *\n * Figma 1941:111897 (Embedded | Large Transaction): a title row with the overflow slot, the HTML\n * description with the primary CTA under it, and on the right the slot's `logo` in a\n * `<BadgedTile>`. Under ditto's `with_warning_badge` variant the tile takes a status badge; under\n * `minimal` it draws the logo alone. The card lays out from the envelope alone and never reads\n * `widget_type`.\n *\n * The icon falls back to the neutral insight glyph **here**, not in `LogoFromGuid`, when the slot\n * carries no logo. The `pill` on the same envelope is the transaction row's, not the card's.\n */\nconst CompactInsightCard = ({\n beat,\n isCtaDisabled = false,\n onCtaClick,\n onTap,\n payload,\n}: CompactInsightCardProps) => {\n const { openActionDrawer } = useInsightsV2Store()\n const { description, instance_slot, primary_cta, title } = payload\n const beatCta = BEAT_CTA_CONFIG[payload.template ?? '']\n const cta = beatCta ? payload[beatCta.key] : primary_cta\n // Reduced variants (`minimal`, `with_warning_badge`) declare a bare `logo` slot.\n const rowLogo = slot<LogoRef>(instance_slot.data, 'logo')\n\n const handleCtaClick = (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation()\n if (isCtaDisabled || !cta) return\n\n if (beatCta) {\n if (beat) openActionDrawer({ guid: beat.guid, trigger: beatCta.trigger })\n return\n }\n if (primary_cta) onCtaClick?.(primary_cta)\n }\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n // Only the card itself: Enter on the CTA button is the button's own click.\n if (event.target !== event.currentTarget) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onTap?.()\n }\n }\n\n const interactiveProps = onTap\n ? { onClick: onTap, onKeyDown: handleKeyDown, role: 'button', tabIndex: 0 }\n : {}\n\n return (\n <>\n <Card\n data-testid=\"compact-insight-card\"\n {...interactiveProps}\n sx={{\n minWidth: 288,\n pb: 12,\n pl: 8,\n pr: 16,\n pt: 8,\n ...(onTap && { cursor: 'pointer' }),\n }}\n >\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n gap: 8,\n justifyContent: 'space-between',\n pl: 8,\n pt: 4,\n }}\n >\n <Text bold={true} variant=\"body1\">\n {title ?? ''}\n </Text>\n {/* The menu renders only when the host wired `onMenuAction` (BeatActionsProvider). */}\n <Box onClick={stopPropagation} sx={{ display: 'flex' }}>\n <InsightOptionsMenu />\n </Box>\n </Stack>\n\n <Stack sx={{ flexDirection: 'row', gap: 16, pl: 8, pt: 4 }}>\n <Stack sx={{ flex: 1, gap: 8, minWidth: 0 }}>\n {/* `html_text` is rendered server-side by mrs_content from the ditto copy template (bold\n amount / merchant), not user-authored input; painted the same way `BaseLayout` paints\n the full card's description. */}\n <P\n dangerouslySetInnerHTML={{ __html: description.html_text }}\n data-testid=\"compact-insight-card-description\"\n sx={{ fontWeight: 'fontWeightRegular' }}\n variant=\"subtitle2\"\n />\n {cta?.label && (\n <Button\n aria-disabled={isCtaDisabled}\n onClick={handleCtaClick}\n size=\"small\"\n sx={{\n alignSelf: 'flex-start',\n // Figma 1941:111897 lines the CTA label up with the description's left edge. The\n // column already pads 8, so the button's own 8 is pulled back out — padding kept so\n // the tap target does not shrink.\n mt: 5,\n ml: -8,\n px: 8,\n ...(isCtaDisabled && {\n color: 'text.disabled',\n cursor: 'default',\n }),\n }}\n variant=\"text\"\n >\n <Text bold={true} variant=\"body2\">\n {cta.label}\n </Text>\n </Button>\n )}\n </Stack>\n\n <InsightVisual data={instance_slot.data} logo={rowLogo} variant={instance_slot.variant} />\n </Stack>\n </Card>\n {/* Outside the Card: the drawer is a portal, and its clicks would otherwise bubble to `onTap`. */}\n {beat && <ActionBlockLayer beat={beat} />}\n </>\n )\n}\n\nexport default CompactInsightCard\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompactInsightCard.es.js","names":[],"sources":["../../../src/insights-v2/components/CompactInsightCard.tsx"],"sourcesContent":["import type { KeyboardEvent, MouseEvent } from 'react'\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 { Icon } from '@mxenabled/mx-icons'\nimport { P, Text } from '@mxenabled/mxui'\n\nimport { useGlobalStore, useInsightsV2Store } from '../../common'\nimport { TinyBarChart } from '../../common/components/TinyBarChart'\nimport { slot } from '../../core'\nimport type {\n Beat,\n DataSeries,\n InsightsPayload,\n InstanceSlotData,\n LogoRef,\n PrimaryCTA,\n} from '../../core'\nimport type { ActionDrawerTrigger } from '../../core/stores'\nimport type { AccountSwitchBlockV2Data } from '../../core/types/InsightsInstanceSlotData'\nimport { getCategoryColor } from '../../core/utils/CategoryUtil'\nimport {\n BAR_ARIA_LABEL,\n buildChartData,\n buildDataSeries,\n} from '../blocks/ChartBlocks/BarchartBlock/utils'\nimport { ActionBlockLayer } from '../actionBlocks/ActionBlockLayer'\nimport { createVariantBlock } from '../blocks/createVariantBlock'\nimport { postCtaMessage } from '../ctaActions'\nimport BadgedTile from './BadgedTile'\nimport InsightOptionsMenu from './InsightOptionsMenu'\nimport LogoFromGuid from './LogoFromGuid'\nimport { ReactComponent as FeeDetectedIllustration } from './shared/images/FeeDetected.svg'\nimport { ThemedSVGImage } from './chartblocks/ThemedSVGImage'\nimport StatusBadge from './StatusBadge'\n\nconst DESCRIPTION_MAX_LINES = 2\n\n/** Figma 1941:111897: the tile the logo fills is 44px (rows are 32, the notification row 33). */\nconst ICON_SIZE = 44\n\ninterface InsightVisualProps {\n /** The slot's `logo`, when ditto's variant declares and fills one. */\n logo?: LogoRef | null\n /** The slot's whole `data`, for a visual drawn from more than the logo (`category_color`, `linked_logos`). */\n data?: InstanceSlotData\n /** The envelope's template, for a beat's own badge on `linked_logos`. */\n template?: string\n}\n\n// Figma 1943:219189: 36px logos at opposite corners of a 75×74 box, the badge and rings centred.\nconst LINKED_LOGO_SIZE = 36\nconst LINKED_BOX_WIDTH = 75\nconst LINKED_BOX_HEIGHT = 74\n\nconst CENTRED = {\n left: '50%',\n position: 'absolute',\n top: '50%',\n transform: 'translate(-50%, -50%)',\n} as const\n\n/** Figma 222:33404 (Embedded | Bill Amount Not Standard) shows the latest three bars. */\nconst BAR_CHART_BAR_COUNT = 3\n\n/** The slot's logo, or the neutral insight glyph when the slot carries none. */\nconst LogoOrFallback = ({ logo }: InsightVisualProps) =>\n logo ? (\n <LogoFromGuid logo={logo} size={ICON_SIZE} />\n ) : (\n <Box\n data-testid=\"compact-insight-card-fallback-icon\"\n sx={{\n alignItems: 'center',\n bgcolor: 'primary.lighter',\n display: 'flex',\n height: ICON_SIZE,\n justifyContent: 'center',\n width: ICON_SIZE,\n }}\n >\n <Icon color=\"primary\" fill={true} name=\"lightbulb\" size={24} />\n </Box>\n )\n\n/** Micro, and any variant not registered below: the logo alone. */\nconst PlainVisual = ({ logo }: InsightVisualProps) => (\n <BadgedTile center={<LogoOrFallback logo={logo} />} size={ICON_SIZE} sx={{ flexShrink: 0 }} />\n)\n\n/** ditto `with_warning_badge`: the logo badged with a status glyph (Figma 1941:111897). */\nconst WarningBadgeVisual = ({ logo }: InsightVisualProps) => (\n <BadgedTile\n center={<LogoOrFallback logo={logo} />}\n size={ICON_SIZE}\n sx={{ flexShrink: 0 }}\n topRight={<StatusBadge tone=\"warning\" />}\n />\n)\n\n/**\n * ditto `with_illustration`: artwork in place of the logo tile (Fee Detected's search illustration).\n * A single SVG themed at runtime by `ThemedSVGImage`: its `brand300` elements are recolored to the\n * theme's `primary.main` (so it follows dark mode), while `fill=\"white\"` areas stay white. The\n * slot's `logo` is deliberately unused: the illustration replaces the tile.\n */\nconst IllustrationVisual = () => {\n return (\n <ThemedSVGImage\n divProps={{ style: { flexShrink: 0, lineHeight: 0, width: 'max-content' } }}\n image={FeeDetectedIllustration}\n />\n )\n}\n\nconst SWAP_BADGE_SIZE = 24\n\nconst LinkBadge = () => (\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-badge\"\n sx={{\n ...CENTRED,\n alignItems: 'center',\n bgcolor: 'primary.main',\n border: '2px solid',\n borderColor: 'primary.contrastText',\n borderRadius: '12px',\n display: 'flex',\n justifyContent: 'center',\n p: 2,\n zIndex: 1,\n }}\n >\n <Icon name=\"link\" size={16} sx={{ color: 'primary.contrastText' }} />\n </Box>\n)\n\n/**\n * Switch Direct Deposit (Figma 1941:167601): the outlined `swap_vertical_circle`, turned horizontal,\n * over a contrast disc: a primary ring and arrows on white. Drawn 1.2× so its circle fills 24px.\n */\nconst SwapBadge = () => (\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-badge\"\n sx={{\n ...CENTRED,\n alignItems: 'center',\n bgcolor: 'primary.contrastText',\n borderRadius: '50%',\n display: 'flex',\n height: SWAP_BADGE_SIZE,\n justifyContent: 'center',\n overflow: 'hidden',\n width: SWAP_BADGE_SIZE,\n zIndex: 1,\n }}\n >\n <Icon\n name=\"swap_vertical_circle\"\n size={SWAP_BADGE_SIZE * 1.2}\n sx={{ color: 'primary.main', flexShrink: 0, transform: 'rotate(-90deg)' }}\n />\n </Box>\n)\n\n/**\n * ditto `linked_logos` (Figma 1943:219189, Embedded | Discovered Accounts): the two institutions at\n * opposite corners of a 75×74 box — `from_logo` top-left, `to_logo` bottom-right — meeting on the\n * diagonal, with a badge over two rings at the centre (Switch Direct Deposit: the swap badge on an\n * outlined inner ring). The rings and badge are decorative (`aria-hidden`); each logo's accessible\n * name is its `alt`.\n */\nconst LinkedLogosVisual = ({ data, template }: InsightVisualProps) => {\n const { from_logo, to_logo } = (data ?? {}) as AccountSwitchBlockV2Data\n const isSwap = template === 'SwitchDirectDeposit'\n\n return (\n <Box\n sx={{\n flexShrink: 0,\n height: LINKED_BOX_HEIGHT,\n position: 'relative',\n width: LINKED_BOX_WIDTH,\n }}\n >\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-ring\"\n sx={{\n ...CENTRED,\n borderRadius: '50%',\n height: 36,\n width: 36,\n ...(isSwap\n ? { border: '2px solid', borderColor: 'primary.light' }\n : { bgcolor: 'primary.light' }),\n }}\n />\n <Box\n aria-hidden={true}\n data-testid=\"linked-logos-ring\"\n sx={{\n ...CENTRED,\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: '50%',\n height: 48,\n width: 48,\n }}\n />\n <Box sx={{ borderRadius: '4px', left: 0, overflow: 'hidden', position: 'absolute', top: 0 }}>\n <LogoFromGuid logo={from_logo} size={LINKED_LOGO_SIZE} />\n </Box>\n <Box\n sx={{ borderRadius: '4px', bottom: 0, overflow: 'hidden', position: 'absolute', right: 0 }}\n >\n <LogoFromGuid logo={to_logo} size={LINKED_LOGO_SIZE} />\n </Box>\n {isSwap ? <SwapBadge /> : <LinkBadge />}\n </Box>\n )\n}\n\n/**\n * ditto `category_color`: a tiny bar chart of the latest bars in place of the logo tile, tinted by\n * the slot's category, the newest bar solid (Figma 222:33404). Draws nothing without a series.\n */\nconst BarChartVisual = ({ data }: InsightVisualProps) => {\n const series = buildDataSeries(slot<DataSeries>(data, 'series'))\n if (!series.length) return null\n\n const categoryGuid = slot<string>(data, 'category_guid')\n const bars = buildChartData(series, BAR_ARIA_LABEL).slice(-BAR_CHART_BAR_COUNT)\n\n return (\n <Box data-testid=\"compact-insight-card-bar-chart\" sx={{ flexShrink: 0 }}>\n <TinyBarChart\n color={(theme) =>\n categoryGuid ? getCategoryColor(categoryGuid, theme) : theme.palette.primary.main\n }\n data={bars}\n />\n </Box>\n )\n}\n\n/**\n * The card's right-hand visual, one component per ditto layout variant.\n *\n * A variant names a rendering *shape*, not an insight or a surface — `with_header` already serves\n * both Large Transaction and Fee Detected — so an insight wanting the badged logo reuses\n * `with_warning_badge`. Only a genuinely different visual registers a new entry, and existing ones\n * are never touched.\n *\n * `minimal` draws the plain logo and is registered explicitly even though it matches the fallback:\n * micro and notification resolve it on every render, and an unregistered variant warns. The\n * fallback is for variants we genuinely do not know.\n */\nconst InsightVisual = createVariantBlock<InsightVisualProps>({\n variants: {\n category_color: BarChartVisual,\n linked_logos: LinkedLogosVisual,\n minimal: PlainVisual,\n with_illustration: IllustrationVisual,\n with_warning_badge: WarningBadgeVisual,\n },\n fallback: PlainVisual,\n name: 'InsightVisual',\n})\n\n/**\n * Beats whose embedded CTA the card reads from its own envelope key and opens in this card's\n * `ActionBlockLayer`, as a bar click does on the full card. The trigger names that key, so the layer\n * resolves the same CTA. Without a trigger the link posts its host message instead (Switch Direct\n * Deposit's op_1: Atomic runs only in pulse). Every other beat keeps `primary_cta` and hands it to\n * the host's `onCtaClick` (e.g. `contact_support_block`). A new beat adds its template here.\n */\nconst BEAT_CTA_CONFIG: Record<\n string,\n { key: 'primary_cta' | 'in_description_cta'; trigger?: ActionDrawerTrigger }\n> = {\n BillAmountNotStandard: { key: 'in_description_cta', trigger: 'inDescriptionCta' },\n ConnectDiscoveredAccountsV2: { key: 'in_description_cta', trigger: 'inDescriptionCta' },\n SwitchDirectDeposit: { key: 'in_description_cta' },\n}\n\nexport interface CompactInsightCardProps {\n /** A `micro` or `embedded` envelope. */\n payload: InsightsPayload\n /** The envelope's beat; when given, the card mounts its `ActionBlockLayer` for CTA drawers. */\n beat?: Beat\n /**\n * Makes the whole card the tap target. What a tap does is the host's decision (post to the\n * host app, open a drawer, …); the card never opens anything itself. Omit for a static card.\n */\n onTap?: () => void\n /** Receives the primary CTA when tapped. The card does not resolve the CTA's block itself. */\n onCtaClick?: (cta: PrimaryCTA) => void\n /**\n * Renders the CTA disabled — label shown, `aria-disabled`, not tappable — for a host that cannot\n * honour it (e.g. `contact_support_block` when the client's `enable_support_requests` is off).\n */\n isCtaDisabled?: boolean\n}\n\nconst stopPropagation = (event: MouseEvent) => event.stopPropagation()\n\n/**\n * The one compact renderer the micro and embedded envelopes share (`insights_v2.2.md` §8.3).\n *\n * Figma 1941:111897 (Embedded | Large Transaction): a title row with the overflow slot, the HTML\n * description with the primary CTA under it, and on the right the slot's `logo` in a\n * `<BadgedTile>`. Under ditto's `with_warning_badge` variant the tile takes a status badge; under\n * `minimal` it draws the logo alone. The card lays out from the envelope alone and never reads\n * `widget_type`.\n *\n * The icon falls back to the neutral insight glyph **here**, not in `LogoFromGuid`, when the slot\n * carries no logo. The `pill` on the same envelope is the transaction row's, not the card's.\n */\nconst CompactInsightCard = ({\n beat,\n isCtaDisabled = false,\n onCtaClick,\n onTap,\n payload,\n}: CompactInsightCardProps) => {\n const stores = useGlobalStore()\n const { openActionDrawer } = useInsightsV2Store()\n const { description, instance_slot, primary_cta, title } = payload\n const beatCta = BEAT_CTA_CONFIG[payload.template ?? '']\n const cta = beatCta ? payload[beatCta.key] : primary_cta\n const message = payload.in_description_cta?.post_message_metadata\n // A link that posts needs its beat and message; without them it draws nothing.\n const canAct = !beatCta || !!beatCta.trigger || (!!beat && !!message)\n // Reduced variants (`minimal`, `with_warning_badge`) declare a bare `logo` slot.\n const rowLogo = slot<LogoRef>(instance_slot.data, 'logo')\n\n const handleCtaClick = (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation()\n if (isCtaDisabled || !cta) return\n\n if (beatCta) {\n if (beat && beatCta.trigger) openActionDrawer({ guid: beat.guid, trigger: beatCta.trigger })\n else if (beat && message) {\n const { action, ...extras } = message\n postCtaMessage({ beat, stores }, { action, payload: extras })\n }\n return\n }\n if (primary_cta) onCtaClick?.(primary_cta)\n }\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n // Only the card itself: Enter on the CTA button is the button's own click.\n if (event.target !== event.currentTarget) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onTap?.()\n }\n }\n\n const interactiveProps = onTap\n ? { onClick: onTap, onKeyDown: handleKeyDown, role: 'button', tabIndex: 0 }\n : {}\n\n return (\n <>\n <Card\n data-testid=\"compact-insight-card\"\n {...interactiveProps}\n sx={{\n minWidth: 288,\n pb: 12,\n pl: 8,\n pr: 16,\n pt: 8,\n ...(onTap && { cursor: 'pointer' }),\n }}\n >\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n gap: 8,\n justifyContent: 'space-between',\n pl: 8,\n pt: 4,\n }}\n >\n <Text bold={true} variant=\"body1\">\n {title ?? ''}\n </Text>\n {/* The menu renders only when the host wired `onMenuAction` (BeatActionsProvider). */}\n <Box onClick={stopPropagation} sx={{ display: 'flex' }}>\n <InsightOptionsMenu />\n </Box>\n </Stack>\n\n <Stack sx={{ alignItems: 'center', flexDirection: 'row', gap: 16, pl: 8 }}>\n <Stack sx={{ alignSelf: 'stretch', flex: 1, gap: 8, minWidth: 0, pt: 2 }}>\n {/* `html_text` is rendered server-side by mrs_content from the ditto copy template (bold\n amount / merchant), not user-authored input; painted the same way `BaseLayout` paints\n the full card's description. */}\n <P\n dangerouslySetInnerHTML={{ __html: description.html_text }}\n data-testid=\"compact-insight-card-description\"\n sx={{\n display: '-webkit-box',\n fontWeight: 'fontWeightRegular',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n WebkitBoxOrient: 'vertical',\n WebkitLineClamp: DESCRIPTION_MAX_LINES,\n }}\n variant=\"subtitle2\"\n />\n {cta?.label && canAct && (\n <Button\n aria-disabled={isCtaDisabled}\n onClick={handleCtaClick}\n size=\"small\"\n sx={{\n alignSelf: 'flex-start',\n // The label lines up with the description (the column pads 8; the tap target keeps\n // its padding). `my: 0` drops the theme's -12px text-button margin.\n ml: -8,\n my: 0,\n px: 8,\n ...(isCtaDisabled && {\n color: 'text.disabled',\n cursor: 'default',\n }),\n }}\n variant=\"text\"\n >\n <Text bold={true} variant=\"body2\">\n {cta.label}\n </Text>\n </Button>\n )}\n </Stack>\n\n <InsightVisual\n data={instance_slot.data}\n logo={rowLogo}\n template={payload.template}\n variant={instance_slot.variant}\n />\n </Stack>\n </Card>\n {/* Outside the Card: the drawer is a portal, and its clicks would otherwise bubble to `onTap`. */}\n {beat && <ActionBlockLayer beat={beat} />}\n </>\n )\n}\n\nexport default CompactInsightCard\n"],"mappings":"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"}
|
|
@@ -2,6 +2,6 @@ export { ctaActions } from './ctaConfig';
|
|
|
2
2
|
export { ctaHandlers, type CtaHandlerName } from './ctaHandlers';
|
|
3
3
|
export { ctaPostMessages, resolveCtaMetadata, type CtaPostMessageResolver } from './ctaPostMessages';
|
|
4
4
|
export { HOST_ACTIONS, type HostActionName } from './hostActions';
|
|
5
|
-
export { beatContextOf, reportCtaClick, resolveCtaState, resolveStep, runCtaAction, runCtaHandler, runInDescriptionCta, sendCtaAnalytics, } from './runCtaAction';
|
|
5
|
+
export { beatContextOf, postCtaMessage, reportCtaClick, resolveCtaState, resolveStep, runCtaAction, runCtaHandler, runInDescriptionCta, sendCtaAnalytics, } from './runCtaAction';
|
|
6
6
|
export { useBeatCtaActions, useCtaHandler, type BeatCtaActions } from './useCtaAction';
|
|
7
7
|
export type { CtaContext, CtaEffect, CtaHandler, CtaHandlerContext, CtaParams, CtaPresentation, CtaSurveyAnswer, CtaSlot, CtaState, CtaStateContext, CtaStep, RunHandlerOptions, RunInDescriptionCtaArgs, } from './types';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AnalyticsEventConfigEntry } from '../analytics/analyticsEventsConfig';
|
|
2
2
|
import { CtaHandlerName } from './ctaHandlers';
|
|
3
|
+
import { CtaPostMessageMetadata } from '../../core';
|
|
3
4
|
import { CtaContext, CtaHandlerContext, CtaState, CtaStateContext, CtaStep, RunHandlerOptions, RunInDescriptionCtaArgs } from './types';
|
|
4
5
|
/** The beat's own context (input a block collected for it), `{}` until something is written. */
|
|
5
6
|
export declare const beatContextOf: ({ beat, stores }: Pick<CtaStateContext, "beat" | "stores">) => Record<string, unknown>;
|
|
@@ -11,6 +12,11 @@ export declare const resolveStep: ({ cta }: {
|
|
|
11
12
|
}) => CtaStep;
|
|
12
13
|
/** Pending always disables, as may the step's `disabledWhen`; its `presenter` may replace the button. */
|
|
13
14
|
export declare const resolveCtaState: (ctx: CtaStateContext) => CtaState;
|
|
15
|
+
/**
|
|
16
|
+
* Posts this CTA's resolved message (event, op and beat-specific payload, from the backend's
|
|
17
|
+
* `cta.metadata` or the template's `ctaPostMessages` entry) to the host.
|
|
18
|
+
*/
|
|
19
|
+
export declare const postCtaMessage: ({ beat, stores }: Pick<CtaContext, "beat" | "stores">, metadata: CtaPostMessageMetadata) => void;
|
|
14
20
|
type AnalyticsCtx = Pick<CtaContext, 'beat' | 'stores' | 'hostActions'>;
|
|
15
21
|
/**
|
|
16
22
|
* The one way to fire a catalogued analytics event. Takes the catalogue entry itself (e.g.
|
|
@@ -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 {\n ANALYTICS_EVENTS_CONFIG,\n type AnalyticsEventConfigEntry,\n} from '../analytics/analyticsEventsConfig'\nimport { resolveAnalyticsEvent } from '../analytics/resolveAnalyticsEvent'\nimport { hasDrawerBlock } from '../mappings/hasDrawerBlock'\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 RunInDescriptionCtaArgs,\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 }: { cta: { action_key?: string | null } }): CtaStep =>\n (cta.action_key && ctaActions[cta.action_key]) || DEFAULT_STEP\n\n/** Pending always disables, as may the step's `disabledWhen`; its `presenter` may replace the button. */\nexport const resolveCtaState = (ctx: CtaStateContext): CtaState => {\n const step = resolveStep(ctx)\n const pending = ctx.stores.insightsV2Store.ctaStatus[ctx.beat.guid] === 'pending'\n const presentation = step.presenter?.(ctx)\n\n return {\n disabled: pending || (step.disabledWhen?.(ctx) ?? false),\n ...(presentation && { presentation }),\n }\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 * Whether the step's target opens the drawer: `drawer` always, `default` only for a real block (a\n * missing block never implies a post message), `none` never. Omitted: `none` with `run`, else `default`.\n */\nconst opensDrawer = (step: CtaStep, cta: { block?: string | null }) => {\n const target = step.next ?? (step.run ? 'none' : 'default')\n\n return target === 'drawer' || (target === 'default' && hasDrawerBlock(cta.block))\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\ntype AnalyticsCtx = Pick<CtaContext, 'beat' | 'stores' | 'hostActions'>\n\n/**\n * The one way to fire a catalogued analytics event. Takes the catalogue entry itself (e.g.\n * `ANALYTICS_EVENTS_CONFIG.PRIMARY_ACTION_SELECTED`), not a lookup key — the call site already\n * has the entry via property access, so there is no key to look up or mistype. Resolves the\n * payload and hands it to the host directly. Never throws: a failed fire is captured, it\n * cannot fail or disable anything.\n */\nexport const sendCtaAnalytics = (ctx: AnalyticsCtx, event: AnalyticsEventConfigEntry): void => {\n try {\n ctx.hostActions.onAnalyticsEvent?.(resolveAnalyticsEvent(event, ctx.beat))\n } catch (error) {\n ctx.stores.captureException(error, {\n store: 'ctaActions',\n template: ctx.beat.template,\n handler: 'sendCtaAnalytics',\n })\n }\n}\n\n// First primary click: fires FIRST_TIME_PRIMARY_ACTION_SELECTED and records cta_clicked_at.\nconst markCtaClicked = (ctx: AnalyticsCtx & Pick<CtaContext, 'slot'>): void => {\n const { beat, slot, hostActions, stores } = ctx\n\n if (slot !== 'primary' || beat.cta_clicked_at) return\n\n sendCtaAnalytics(ctx, ANALYTICS_EVENTS_CONFIG.FIRST_TIME_PRIMARY_ACTION_SELECTED)\n\n // Fire-and-forget; a failed write must not fail the user's action, but still report it.\n Promise.resolve()\n .then(() =>\n hostActions.onCtaAction?.({ beatGuid: beat.guid, name: HOST_ACTIONS.markCtaClicked }),\n )\n .catch((error) =>\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n handler: 'markCtaClicked',\n }),\n )\n}\n\n/**\n * Trigger 1 + the first-click bookkeeping together: every CTA press's generic analytics\n * (`Primary/Secondary Action Selected`), plus `FIRST_TIME_PRIMARY_ACTION_SELECTED` /\n * `cta_clicked_at` on the first primary press. The one call site for \"a CTA got pressed\",\n * used by the runner and by the contact-support bypass in `BaseLayout`.\n */\nexport const reportCtaClick = (\n ctx: AnalyticsCtx & Pick<CtaContext, 'slot'>,\n { skipCtaClicked = false }: { skipCtaClicked?: boolean } = {},\n): void => {\n sendCtaAnalytics(\n ctx,\n ctx.slot === 'primary'\n ? ANALYTICS_EVENTS_CONFIG.PRIMARY_ACTION_SELECTED\n : ANALYTICS_EVENTS_CONFIG.SECONDARY_ACTION_SELECTED,\n )\n\n if (!skipCtaClicked) markCtaClicked(ctx)\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/** The step's params, overridden by the caller's. */\nconst withStepParams = (step: CtaStep, ctx: CtaContext): CtaContext => {\n const stepParams = typeof step.params === 'function' ? step.params(ctx) : step.params\n\n return { ...ctx, params: { ...stepParams, ...ctx.params } }\n}\n\n// ─── Entry points ───────────────────────────────────────────────────────────\n\n// Runs the CTA's step: handler, onSuccess effects, post message, then the next target.\nexport const runCtaAction = async (ctx: CtaContext): Promise<void> => {\n const step = resolveStep(ctx)\n const stepCtx = withStepParams(step, ctx)\n\n reportCtaClick(stepCtx, { skipCtaClicked: step.skipCtaClicked })\n\n await withCtaStatus(stepCtx, async () => {\n if (step.run) await runHandlerWithEffects(stepCtx, step.run, step.onSuccess ?? [])\n\n // A step's own event, e.g. select_savings_account (Saved) / open_savings_account (Created).\n if (step.analytics) sendCtaAnalytics(stepCtx, step.analytics)\n\n const metadata = resolveCtaMetadata(stepCtx)\n\n if (metadata) postCtaMessage(stepCtx, metadata)\n\n if (opensDrawer(step, stepCtx.cta)) openDrawerForSlot(stepCtx)\n })\n}\n\n/**\n * A pinned beat's auto-open: runs the card action's (`in_description_cta`) step and opens its block,\n * with no press analytics or post message. Does nothing while the step shows a status (e.g. an\n * answered subscription), nothing or a disabled button. Never rejects: failures are reported.\n */\nexport const runInDescriptionCta = async ({\n beat,\n stores,\n hostActions = {},\n}: RunInDescriptionCtaArgs): Promise<void> => {\n const link = beat.insights_payload?.in_description_cta\n\n if (!link) return\n\n const cta = { ...link, block: link.block ?? '' }\n const step = resolveStep({ cta })\n\n try {\n const ctx = withStepParams(step, {\n beat,\n cta,\n slot: 'primary',\n stores,\n hostActions,\n params: {},\n })\n\n await step.prepare?.(ctx)\n\n // Read after the load; `disabled` also covers a press made meanwhile (pending).\n const { disabled, presentation } = resolveCtaState(ctx)\n\n if (disabled || presentation) return\n\n if (step.run) {\n const { run, onSuccess = [] } = step\n\n if (!(await withCtaStatus(ctx, () => runHandlerWithEffects(ctx, run, onSuccess)))) return\n }\n\n // Never replaces a drawer the user opened meanwhile.\n if (opensDrawer(step, cta) && !stores.insightsV2Store.actionDrawer.isDrawerOpen) {\n stores.insightsV2Store.openActionDrawer({\n guid: beat.guid,\n trigger: 'inDescriptionCta',\n cta: { block: cta.block, variant: cta.variant, action_key: cta.action_key },\n })\n }\n } catch (error) {\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n handler: 'runInDescriptionCta',\n })\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). True on success. */\nexport const runCtaHandler = (\n ctx: CtaHandlerContext,\n handlerName: CtaHandlerName,\n { onSuccess = [], analytics }: RunHandlerOptions = {},\n): Promise<boolean> => {\n const fullCtx: CtaContext = { ...ctx, cta: DIRECT_CTA, slot: 'primary' }\n\n return withCtaStatus(\n fullCtx,\n async () => {\n await runHandlerWithEffects(fullCtx, handlerName, onSuccess)\n\n // Success-time analytics for drawer Saves.\n if (analytics) sendCtaAnalytics(fullCtx, analytics)\n },\n { handler: handlerName },\n )\n}\n"],"mappings":";;;;;;;;AAwBA,IAAM,IAAwB,CAAC,GASlB,IAAA,CAAe,EAAE,KAAA,EAAA,MAC3B,EAAI,cAAc,EAAW,EAAI,UAAA,KAAgB,GAGvC,IAAA,CAAmB,MAAmC;AACjE,QAAM,IAAO,EAAY,CAAG,GACtB,IAAU,EAAI,OAAO,gBAAgB,UAAU,EAAI,KAAK,IAAA,MAAU,WAClE,IAAe,EAAK,YAAY,CAAG;AAEzC,SAAO;AAAA,IACL,UAAU,MAAY,EAAK,eAAe,CAAG,KAAK;AAAA,IAClD,GAAI,KAAgB,EAAE,cAAA,EAAa;AAAA,EACrC;AACF,GAKM,IAAA,CAAqB,EACzB,MAAA,GACA,MAAA,GACA,QAAA,EAAA,MACkD;AAClD,EAAA,EAAO,gBAAgB,cACrB,EAAK,MACL,MAAS,YAAY,eAAe,cACtC;AACF,GAMM,IAAA,CAAkB,EAAE,MAAA,GAAM,QAAA,EAAA,GAAsB,MAAqC;AACzF,QAAM,EAAE,mBAAA,GAAmB,cAAA,EAAA,IAAiB,EAAO,aAAa,WAC1D,EAAE,OAAA,GAAO,QAAA,GAAQ,SAAA,EAAA,IAAY;AAEnC,EAAA,EAAY,GAAM;AAAA,IAChB,OAAO,KAAS;AAAA,IAChB,IAAI,KAAU;AAAA,IACd,QAAQ,KAAW;AAAA,IACnB,iBAAiB;AAAA,IACjB,eAAe,GAAc,QAAQ;AAAA,IACrC,UAAU,EAAK,kBAAkB,aAAa,EAAK;AAAA,EACrD,CAAC;AACH,GAMM,IAAA,CAAe,GAAe,MAAmC;AACrE,QAAM,IAAS,EAAK,SAAS,EAAK,MAAM,SAAS;AAEjD,SAAO,MAAW,YAAa,MAAW,aAAa,EAAe,EAAI,KAAK;AACjF,GAEM,IAAA,CAAa,GAAmB,EAAE,MAAA,GAAM,QAAA,EAAA,MAAyB;AACrE,EAAQ,MACD,iBACH,EAAO,WAAW,EAAE,eAAe,EAAK,KAAK,CAAC;AAGpD,GAWa,IAAA,CAAoB,GAAmB,MAA2C;AAC7F,MAAI;AACF,IAAA,EAAI,YAAY,mBAAmB,EAAsB,GAAO,EAAI,IAAI,CAAC;AAAA,EAC3E,SAAS,GAAO;AACd,IAAA,EAAI,OAAO,iBAAiB,GAAO;AAAA,MACjC,OAAO;AAAA,MACP,UAAU,EAAI,KAAK;AAAA,MACnB,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF,GAGM,IAAA,CAAkB,MAAuD;AAC7E,QAAM,EAAE,MAAA,GAAM,MAAA,GAAM,aAAA,GAAa,QAAA,EAAA,IAAW;AAE5C,EAAI,MAAS,aAAa,EAAK,mBAE/B,EAAiB,GAAK,EAAwB,kCAAkC,GAGhF,QAAQ,QAAQ,EACb,KAAA,MACC,EAAY,cAAc;AAAA,IAAE,UAAU,EAAK;AAAA,IAAM,MAAM,EAAa;AAAA,EAAe,CAAC,CACtF,EACC,MAAA,CAAO,MACN,EAAO,iBAAiB,GAAO;AAAA,IAC7B,OAAO;AAAA,IACP,UAAU,EAAK;AAAA,IACf,SAAS;AAAA,EACX,CAAC,CACH;AACJ,GAQa,IAAA,CACX,GACA,EAAE,gBAAA,IAAiB,GAAA,IAAwC,CAAC,MACnD;AACT,EAAA,EACE,GACA,EAAI,SAAS,YACT,EAAwB,0BACxB,EAAwB,yBAC9B,GAEK,KAAgB,EAAe,CAAG;AACzC,GAKM,IAAwB,OAC5B,GACA,GACA,MACkB;AAElB,QAAM,IAAW,EAA2C,CAAA;AAE5D,MAAI,CAAC,EAAS,OAAM,IAAI,MAAM,wBAAwB,CAAA,GAAc;AAEpE,QAAM,EAAQ,CAAG;AAEjB,aAAW,KAAU,EAAW,CAAA,EAAU,GAAQ,CAAG;AACvD,GAGM,IAAgB,OACpB,EAAE,MAAA,GAAM,QAAA,EAAA,GACR,GACA,IAAyC,CAAC,MACrB;AACrB,QAAM,EAAE,iBAAA,EAAA,IAAoB;AAE5B,EAAA,EAAgB,aAAa,EAAK,MAAM,SAAS;AAEjD,MAAI;AACF,iBAAM,EAAK,GACX,EAAgB,aAAa,EAAK,MAAM,MAAM,GAEvC;AAAA,EACT,SAAS,GAAO;AACd,WAAA,EAAgB,aAAa,EAAK,MAAM,OAAO,GAC/C,EAAO,iBAAiB,GAAO;AAAA,MAC7B,OAAO;AAAA,MACP,UAAU,EAAK;AAAA,MACf,GAAG;AAAA,IACL,CAAC,GAEM;AAAA,EACT;AACF,GAGM,IAAA,CAAkB,GAAe,MAAgC;AACrE,QAAM,IAAa,OAAO,EAAK,UAAW,aAAa,EAAK,OAAO,CAAG,IAAI,EAAK;AAE/E,SAAO;AAAA,IAAE,GAAG;AAAA,IAAK,QAAQ;AAAA,MAAE,GAAG;AAAA,MAAY,GAAG,EAAI;AAAA,IAAO;AAAA,EAAE;AAC5D,GAKa,IAAe,OAAO,MAAmC;AACpE,QAAM,IAAO,EAAY,CAAG,GACtB,IAAU,EAAe,GAAM,CAAG;AAExC,EAAA,EAAe,GAAS,EAAE,gBAAgB,EAAK,eAAe,CAAC,GAE/D,MAAM,EAAc,GAAS,YAAY;AACvC,IAAI,EAAK,OAAK,MAAM,EAAsB,GAAS,EAAK,KAAK,EAAK,aAAa,CAAC,CAAC,GAG7E,EAAK,aAAW,EAAiB,GAAS,EAAK,SAAS;AAE5D,UAAM,IAAW,EAAmB,CAAO;AAE3C,IAAI,KAAU,EAAe,GAAS,CAAQ,GAE1C,EAAY,GAAM,EAAQ,GAAG,KAAG,EAAkB,CAAO;AAAA,EAC/D,CAAC;AACH,GAOa,IAAsB,OAAO,EACxC,MAAA,GACA,QAAA,GACA,aAAA,IAAc,CAAC,EAAA,MAC6B;AAC5C,QAAM,IAAO,EAAK,kBAAkB;AAEpC,MAAI,CAAC,EAAM;AAEX,QAAM,IAAM;AAAA,IAAE,GAAG;AAAA,IAAM,OAAO,EAAK,SAAS;AAAA,EAAG,GACzC,IAAO,EAAY,EAAE,KAAA,EAAI,CAAC;AAEhC,MAAI;AACF,UAAM,IAAM,EAAe,GAAM;AAAA,MAC/B,MAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAM;AAAA,MACN,QAAA;AAAA,MACA,aAAA;AAAA,MACA,QAAQ,CAAC;AAAA,IACX,CAAC;AAED,UAAM,EAAK,UAAU,CAAG;AAGxB,UAAM,EAAE,UAAA,GAAU,cAAA,EAAA,IAAiB,EAAgB,CAAG;AAEtD,QAAI,KAAY,EAAc;AAE9B,QAAI,EAAK,KAAK;AACZ,YAAM,EAAE,KAAA,GAAK,WAAA,IAAY,CAAC,EAAA,IAAM;AAEhC,UAAI,CAAE,MAAM,EAAc,GAAA,MAAW,EAAsB,GAAK,GAAK,CAAS,CAAC,EAAI;AAAA,IACrF;AAGA,IAAI,EAAY,GAAM,CAAG,KAAK,CAAC,EAAO,gBAAgB,aAAa,gBACjE,EAAO,gBAAgB,iBAAiB;AAAA,MACtC,MAAM,EAAK;AAAA,MACX,SAAS;AAAA,MACT,KAAK;AAAA,QAAE,OAAO,EAAI;AAAA,QAAO,SAAS,EAAI;AAAA,QAAS,YAAY,EAAI;AAAA,MAAW;AAAA,IAC5E,CAAC;AAAA,EAEL,SAAS,GAAO;AACd,IAAA,EAAO,iBAAiB,GAAO;AAAA,MAC7B,OAAO;AAAA,MACP,UAAU,EAAK;AAAA,MACf,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF,GAGM,IAAa;AAAA,EAAE,OAAO;AAAA,EAAI,OAAO;AAAG,GAG7B,IAAA,CACX,GACA,GACA,EAAE,WAAA,IAAY,CAAC,GAAG,WAAA,EAAA,IAAiC,CAAC,MAC/B;AACrB,QAAM,IAAsB;AAAA,IAAE,GAAG;AAAA,IAAK,KAAK;AAAA,IAAY,MAAM;AAAA,EAAU;AAEvE,SAAO,EACL,GACA,YAAY;AACV,UAAM,EAAsB,GAAS,GAAa,CAAS,GAGvD,KAAW,EAAiB,GAAS,CAAS;AAAA,EACpD,GACA,EAAE,SAAS,EAAY,CACzB;AACF"}
|
|
1
|
+
{"version":3,"file":"runCtaAction.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/runCtaAction.ts"],"sourcesContent":["import { sendBeatCta } from '../../common/utils/PostMessage'\nimport {\n ANALYTICS_EVENTS_CONFIG,\n type AnalyticsEventConfigEntry,\n} from '../analytics/analyticsEventsConfig'\nimport { resolveAnalyticsEvent } from '../analytics/resolveAnalyticsEvent'\nimport { hasDrawerBlock } from '../mappings/hasDrawerBlock'\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 RunInDescriptionCtaArgs,\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 }: { cta: { action_key?: string | null } }): CtaStep =>\n (cta.action_key && ctaActions[cta.action_key]) || DEFAULT_STEP\n\n/** Pending always disables, as may the step's `disabledWhen`; its `presenter` may replace the button. */\nexport const resolveCtaState = (ctx: CtaStateContext): CtaState => {\n const step = resolveStep(ctx)\n const pending = ctx.stores.insightsV2Store.ctaStatus[ctx.beat.guid] === 'pending'\n const presentation = step.presenter?.(ctx)\n\n return {\n disabled: pending || (step.disabledWhen?.(ctx) ?? false),\n ...(presentation && { presentation }),\n }\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 */\nexport const postCtaMessage = (\n { beat, stores }: Pick<CtaContext, 'beat' | 'stores'>,\n metadata: CtaPostMessageMetadata,\n) => {\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 * Whether the step's target opens the drawer: `drawer` always, `default` only for a real block (a\n * missing block never implies a post message), `none` never. Omitted: `none` with `run`, else `default`.\n */\nconst opensDrawer = (step: CtaStep, cta: { block?: string | null }) => {\n const target = step.next ?? (step.run ? 'none' : 'default')\n\n return target === 'drawer' || (target === 'default' && hasDrawerBlock(cta.block))\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\ntype AnalyticsCtx = Pick<CtaContext, 'beat' | 'stores' | 'hostActions'>\n\n/**\n * The one way to fire a catalogued analytics event. Takes the catalogue entry itself (e.g.\n * `ANALYTICS_EVENTS_CONFIG.PRIMARY_ACTION_SELECTED`), not a lookup key — the call site already\n * has the entry via property access, so there is no key to look up or mistype. Resolves the\n * payload and hands it to the host directly. Never throws: a failed fire is captured, it\n * cannot fail or disable anything.\n */\nexport const sendCtaAnalytics = (ctx: AnalyticsCtx, event: AnalyticsEventConfigEntry): void => {\n try {\n ctx.hostActions.onAnalyticsEvent?.(resolveAnalyticsEvent(event, ctx.beat))\n } catch (error) {\n ctx.stores.captureException(error, {\n store: 'ctaActions',\n template: ctx.beat.template,\n handler: 'sendCtaAnalytics',\n })\n }\n}\n\n// First primary click: fires FIRST_TIME_PRIMARY_ACTION_SELECTED and records cta_clicked_at.\nconst markCtaClicked = (ctx: AnalyticsCtx & Pick<CtaContext, 'slot'>): void => {\n const { beat, slot, hostActions, stores } = ctx\n\n if (slot !== 'primary' || beat.cta_clicked_at) return\n\n sendCtaAnalytics(ctx, ANALYTICS_EVENTS_CONFIG.FIRST_TIME_PRIMARY_ACTION_SELECTED)\n\n // Fire-and-forget; a failed write must not fail the user's action, but still report it.\n Promise.resolve()\n .then(() =>\n hostActions.onCtaAction?.({ beatGuid: beat.guid, name: HOST_ACTIONS.markCtaClicked }),\n )\n .catch((error) =>\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n handler: 'markCtaClicked',\n }),\n )\n}\n\n/**\n * Trigger 1 + the first-click bookkeeping together: every CTA press's generic analytics\n * (`Primary/Secondary Action Selected`), plus `FIRST_TIME_PRIMARY_ACTION_SELECTED` /\n * `cta_clicked_at` on the first primary press. The one call site for \"a CTA got pressed\",\n * used by the runner and by the contact-support bypass in `BaseLayout`.\n */\nexport const reportCtaClick = (\n ctx: AnalyticsCtx & Pick<CtaContext, 'slot'>,\n { skipCtaClicked = false }: { skipCtaClicked?: boolean } = {},\n): void => {\n sendCtaAnalytics(\n ctx,\n ctx.slot === 'primary'\n ? ANALYTICS_EVENTS_CONFIG.PRIMARY_ACTION_SELECTED\n : ANALYTICS_EVENTS_CONFIG.SECONDARY_ACTION_SELECTED,\n )\n\n if (!skipCtaClicked) markCtaClicked(ctx)\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/** The step's params, overridden by the caller's. */\nconst withStepParams = (step: CtaStep, ctx: CtaContext): CtaContext => {\n const stepParams = typeof step.params === 'function' ? step.params(ctx) : step.params\n\n return { ...ctx, params: { ...stepParams, ...ctx.params } }\n}\n\n// ─── Entry points ───────────────────────────────────────────────────────────\n\n// Runs the CTA's step: handler, onSuccess effects, post message, then the next target.\nexport const runCtaAction = async (ctx: CtaContext): Promise<void> => {\n const step = resolveStep(ctx)\n const stepCtx = withStepParams(step, ctx)\n\n reportCtaClick(stepCtx, { skipCtaClicked: step.skipCtaClicked })\n\n await withCtaStatus(stepCtx, async () => {\n if (step.run) await runHandlerWithEffects(stepCtx, step.run, step.onSuccess ?? [])\n\n // A step's own event, e.g. select_savings_account (Saved) / open_savings_account (Created).\n if (step.analytics) sendCtaAnalytics(stepCtx, step.analytics)\n\n const metadata = resolveCtaMetadata(stepCtx)\n\n if (metadata) postCtaMessage(stepCtx, metadata)\n\n if (opensDrawer(step, stepCtx.cta)) openDrawerForSlot(stepCtx)\n })\n}\n\n/**\n * A pinned beat's auto-open: runs the card action's (`in_description_cta`) step and opens its block,\n * with no press analytics or post message. Does nothing while the step shows a status (e.g. an\n * answered subscription), nothing or a disabled button. Never rejects: failures are reported.\n */\nexport const runInDescriptionCta = async ({\n beat,\n stores,\n hostActions = {},\n}: RunInDescriptionCtaArgs): Promise<void> => {\n const link = beat.insights_payload?.in_description_cta\n\n if (!link) return\n\n const cta = { ...link, block: link.block ?? '' }\n const step = resolveStep({ cta })\n\n try {\n const ctx = withStepParams(step, {\n beat,\n cta,\n slot: 'primary',\n stores,\n hostActions,\n params: {},\n })\n\n await step.prepare?.(ctx)\n\n // Read after the load; `disabled` also covers a press made meanwhile (pending).\n const { disabled, presentation } = resolveCtaState(ctx)\n\n if (disabled || presentation) return\n\n if (step.run) {\n const { run, onSuccess = [] } = step\n\n if (!(await withCtaStatus(ctx, () => runHandlerWithEffects(ctx, run, onSuccess)))) return\n }\n\n // Never replaces a drawer the user opened meanwhile.\n if (opensDrawer(step, cta) && !stores.insightsV2Store.actionDrawer.isDrawerOpen) {\n stores.insightsV2Store.openActionDrawer({\n guid: beat.guid,\n trigger: 'inDescriptionCta',\n cta: { block: cta.block, variant: cta.variant, action_key: cta.action_key },\n })\n }\n } catch (error) {\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n handler: 'runInDescriptionCta',\n })\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). True on success. */\nexport const runCtaHandler = (\n ctx: CtaHandlerContext,\n handlerName: CtaHandlerName,\n { onSuccess = [], analytics }: RunHandlerOptions = {},\n): Promise<boolean> => {\n const fullCtx: CtaContext = { ...ctx, cta: DIRECT_CTA, slot: 'primary' }\n\n return withCtaStatus(\n fullCtx,\n async () => {\n await runHandlerWithEffects(fullCtx, handlerName, onSuccess)\n\n // Success-time analytics for drawer Saves.\n if (analytics) sendCtaAnalytics(fullCtx, analytics)\n },\n { handler: handlerName },\n )\n}\n"],"mappings":"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"}
|