@mx-cartographer/experiences 9.6.13 → 9.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/common/components/CurrencyDialog.d.ts +6 -1
  3. package/dist/common/components/CurrencyDialog.es.js +24 -23
  4. package/dist/common/components/CurrencyDialog.es.js.map +1 -1
  5. package/dist/core/types/localization/InsightsFeedCopy.d.ts +2 -0
  6. package/dist/insights-v2/actionBlocks/ActionBlockLayer.es.js +3 -3
  7. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js +64 -59
  8. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js.map +1 -1
  9. package/dist/insights-v2/actionBlocks/ConnectWidgetActionBlock.es.js +17 -11
  10. package/dist/insights-v2/actionBlocks/ConnectWidgetActionBlock.es.js.map +1 -1
  11. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js +63 -57
  12. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js.map +1 -1
  13. package/dist/insights-v2/analytics/analyticsEventsConfig.d.ts +67 -0
  14. package/dist/insights-v2/analytics/analyticsEventsConfig.es.js +59 -0
  15. package/dist/insights-v2/analytics/analyticsEventsConfig.es.js.map +1 -0
  16. package/dist/insights-v2/analytics/index.d.ts +3 -0
  17. package/dist/insights-v2/analytics/resolveAnalyticsEvent.d.ts +5 -0
  18. package/dist/insights-v2/analytics/resolveAnalyticsEvent.es.js +19 -0
  19. package/dist/insights-v2/analytics/resolveAnalyticsEvent.es.js.map +1 -0
  20. package/dist/insights-v2/analytics/useAnalyticsEvent.d.ts +3 -0
  21. package/dist/insights-v2/analytics/useAnalyticsEvent.es.js +20 -0
  22. package/dist/insights-v2/analytics/useAnalyticsEvent.es.js.map +1 -0
  23. package/dist/insights-v2/beatActions.d.ts +18 -13
  24. package/dist/insights-v2/beatActions.es.js.map +1 -1
  25. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/BarchartBlock.es.js +24 -22
  26. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/BarchartBlock.es.js.map +1 -1
  27. package/dist/insights-v2/blocks/ComponentBlocks/DebtsBlock/DebtsBlock.d.ts +16 -12
  28. package/dist/insights-v2/blocks/ComponentBlocks/DebtsBlock/DebtsBlock.es.js +67 -46
  29. package/dist/insights-v2/blocks/ComponentBlocks/DebtsBlock/DebtsBlock.es.js.map +1 -1
  30. package/dist/insights-v2/blocks/ComponentBlocks/DebtsBlock/layout.d.ts +17 -0
  31. package/dist/insights-v2/blocks/ComponentBlocks/DebtsBlock/layout.es.js +24 -0
  32. package/dist/insights-v2/blocks/ComponentBlocks/DebtsBlock/layout.es.js.map +1 -0
  33. package/dist/insights-v2/components/BaseLayout.es.js +89 -79
  34. package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
  35. package/dist/insights-v2/components/TransactionListCard.es.js +15 -14
  36. package/dist/insights-v2/components/TransactionListCard.es.js.map +1 -1
  37. package/dist/insights-v2/ctaActions/ctaHandlers.d.ts +1 -6
  38. package/dist/insights-v2/ctaActions/ctaHandlers.es.js.map +1 -1
  39. package/dist/insights-v2/ctaActions/index.d.ts +1 -1
  40. package/dist/insights-v2/ctaActions/runCtaAction.d.ts +20 -5
  41. package/dist/insights-v2/ctaActions/runCtaAction.es.js +63 -41
  42. package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
  43. package/dist/insights-v2/ctaActions/types.d.ts +6 -5
  44. package/dist/insights-v2/index.es.js +84 -84
  45. package/dist/insights-v2/mappings/blocks.es.js +2 -2
  46. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## [9.6.15] - 10-15-2026
2
+
3
+ - **ADDED** - `debts_block` `debt_summary` variant for the Debt Payoff Strategies beat when it spans several accounts (the architecture doc's Debts "Debt Summary" branch): a `TransactionListCard` on a primary-tinted slot with one row per debt account from `instance_slot.data.series` (pre-localized `logo`, `primary_text` name, `secondary_text` rate, `amount` balance, in the order sent; a row without a name is dropped), an optional `header_text`, and the total owed (`amount`) as the footer under a new optional `insights_feed.debts_block.total_balance_label` caption. With no rows the block renders nothing. Stories, fixtures and tests (WEC-1311)
4
+ - **CHANGED** - `DebtsBlock` now resolves its card per `instance_slot.variant` through a `layout.ts` table and `resolveVariantConfig` (the `AccountBlock` pattern; an unknown variant warns and renders the payoff card). Each variant needs only its own copy keys, so a host that has shipped the payoff labels but not `total_balance_label` keeps the payoff card intact. `projected_payoff` still reads its facts flat on `instance_slot` and renders exactly as before (WEC-1311)
5
+
6
+ ## [9.6.14] - 10-15-2026
7
+
8
+ - **ADDED** - `onAnalyticsEvent` callback on `BeatActions` (WEC-1849)
9
+ - **ADDED** - `src/insights-v2/analytics/` module: events config, resolver, `useAnalyticsEvent()` hook (WEC-1849)
10
+ - **ADDED** - analytics routed through ctaActions via `sendCtaAnalytics` (WEC-1849)
11
+ - **ADDED** - `PRIMARY_ACTION_SELECTED` / `SECONDARY_ACTION_SELECTED` / `FIRST_TIME_PRIMARY_ACTION_SELECTED` CTA press events (WEC-1849)
12
+ - **ADDED** - `CHART_BAR_CLICKED` / `DRAWER_CHART_BAR_CLICKED` / `TAB_SELECTED` chart analytics events (WEC-1849)
13
+ - **ADDED** - Declarative analytics trigger: `analytics` on `RunHandlerOptions` (WEC-1849)
14
+ - **ADDED** - LowAccountBalance + SavingsOpportunityV2 dialog analytics in `AmountInputModalBlock` (WEC-1849)
15
+ - **ADDED** - `OPENED_CONNECT_DRAWER` event in `ConnectWidgetActionBlock` (WEC-1849)
16
+ - **NOTE** - Scoped to Firefly analytics only (WEC-1849)
17
+ - **REFACTOR** - Collapsed the `sendCtaAnalytics` → fake-CTA → `ctaHandlers.sendAnalyticsEvent` chain into one function that resolves the payload and calls `onAnalyticsEvent` directly; removed the now-redundant runtime eventKey check (a typo'd key already fails typecheck). Kept `CtaStep.analytics` and the runner's `step.analytics` check for future `ctaConfig` entries (none use it yet; `RunHandlerOptions.analytics` has real callers today). Removed the `AnalyticsEvents` self-mapping constant — `sendCtaAnalytics`/`resolveAnalyticsEvent`/`useAnalyticsEvent()`/`RunHandlerOptions.analytics` now take the catalogue entry itself (e.g. `ANALYTICS_EVENTS_CONFIG.PRIMARY_ACTION_SELECTED`) instead of a lookup key, so there is nothing to look up and a typo'd property name fails typecheck at the call site. Dropped the unused `stores` parameter from `resolveAnalyticsEvent`/`postHog.properties`. Added `reportCtaClick` (press event + first-time bookkeeping together) and routed the `contact_support_block` bypass through it, so that CTA now also fires `FIRST_TIME_PRIMARY_ACTION_SELECTED` and records `cta_clicked_at` on its first press (WEC-1849)
18
+
1
19
  ## [9.6.13] - 10-15-2026
2
20
 
3
21
  - **ADDED** - `ICO-<name>` logo guids: `logoFromEntityGuid` / `logoFromRef` map them to a filled mx-icons status icon in the logo's `color`, `LogoFromGuid` draws it and `isLogoGuid` accepts the prefix; `SubHeader` draws `logo_fill` icons filled. Fee Detected's header sends `ICO-error` (WEC-1885)
@@ -12,6 +12,11 @@ interface CurrencyDialogProps {
12
12
  /** Disables the primary button on top of the built-in input check (e.g. while saving). */
13
13
  isPrimaryDisabled?: boolean;
14
14
  handleInputChange?: (value: string) => void;
15
+ /**
16
+ * Click on the amount input (e.g. analytics). Click, not focus: the input autofocuses on open,
17
+ * so a focus callback would fire per open rather than per user interaction.
18
+ */
19
+ handleInputClick?: () => void;
15
20
  handleModalClose: () => void;
16
21
  handlePrimaryAction: () => void;
17
22
  handleSecondaryAction?: () => void;
@@ -23,5 +28,5 @@ interface CurrencyDialogProps {
23
28
  subtitle?: ReactNode;
24
29
  title: string;
25
30
  }
26
- declare const CurrencyDialog: ({ closeAriaLabel, content, errorText, inputLabel, inputValue, isOpen, isPrimaryDisabled, handleInputChange, handleModalClose, handlePrimaryAction, handleSecondaryAction, primaryColor, primaryText, secondaryColor, secondaryText, subtitle, title, }: CurrencyDialogProps) => import("react/jsx-runtime").JSX.Element;
31
+ declare const CurrencyDialog: ({ closeAriaLabel, content, errorText, inputLabel, inputValue, isOpen, isPrimaryDisabled, handleInputChange, handleInputClick, handleModalClose, handlePrimaryAction, handleSecondaryAction, primaryColor, primaryText, secondaryColor, secondaryText, subtitle, title, }: CurrencyDialogProps) => import("react/jsx-runtime").JSX.Element;
27
32
  export default CurrencyDialog;
@@ -1,18 +1,18 @@
1
- import { useScreenSize as k } from "../hooks/useScreenSize.es.js";
1
+ import { useScreenSize as S } from "../hooks/useScreenSize.es.js";
2
2
  import n from "@mui/material/Stack";
3
- import { H3 as S, P as j } from "@mxenabled/mxui";
3
+ import { H3 as j, P as M } from "@mxenabled/mxui";
4
4
  import f from "@mui/material/Button";
5
- import { Icon as M } from "@mxenabled/mx-icons";
5
+ import { Icon as $ } from "@mxenabled/mx-icons";
6
6
  import { jsx as o, jsxs as t } from "react/jsx-runtime";
7
- import $ from "@mui/material/Dialog";
8
- import B from "@mui/material/DialogContent";
9
- import F from "@mui/material/IconButton";
10
- import H from "@mui/material/DialogActions";
11
- import w from "@mui/material/InputAdornment";
12
- import A from "@mui/material/TextField";
13
- var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValue: i, isOpen: u, isPrimaryDisabled: x = !1, handleInputChange: b, handleModalClose: m, handlePrimaryAction: s, handleSecondaryAction: C, primaryColor: D = "primary", primaryText: p, secondaryColor: v = "primary", secondaryText: l, subtitle: y, title: c }) => {
14
- const d = x || (r ? !(i !== "" && i !== "0" && !isNaN(parseFloat(i ?? "0"))) : !1), { isSmallMobile: I } = k();
15
- return /* @__PURE__ */ o($, {
7
+ import B from "@mui/material/Dialog";
8
+ import F from "@mui/material/DialogContent";
9
+ import H from "@mui/material/IconButton";
10
+ import w from "@mui/material/DialogActions";
11
+ import A from "@mui/material/InputAdornment";
12
+ import N from "@mui/material/TextField";
13
+ var X = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValue: i, isOpen: u, isPrimaryDisabled: x = !1, handleInputChange: b, handleInputClick: C, handleModalClose: m, handlePrimaryAction: s, handleSecondaryAction: D, primaryColor: k = "primary", primaryText: p, secondaryColor: v = "primary", secondaryText: l, subtitle: y, title: c }) => {
14
+ const d = x || (r ? !(i !== "" && i !== "0" && !isNaN(parseFloat(i ?? "0"))) : !1), { isSmallMobile: I } = S();
15
+ return /* @__PURE__ */ o(B, {
16
16
  "aria-label": c,
17
17
  "aria-labelledby": "modal-title",
18
18
  maxWidth: "xs",
@@ -23,7 +23,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
23
23
  "& .MuiDialogContent-root": { p: 0 },
24
24
  ...I && { "& .MuiDialog-paper": { minWidth: "100%" } }
25
25
  },
26
- children: /* @__PURE__ */ o(B, { children: /* @__PURE__ */ t("form", {
26
+ children: /* @__PURE__ */ o(F, { children: /* @__PURE__ */ t("form", {
27
27
  onSubmit: (e) => {
28
28
  e.preventDefault(), d || s();
29
29
  },
@@ -41,11 +41,11 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
41
41
  },
42
42
  children: [/* @__PURE__ */ t(n, {
43
43
  sx: { gap: 4 },
44
- children: [/* @__PURE__ */ o(S, {
44
+ children: [/* @__PURE__ */ o(j, {
45
45
  id: "modal-title",
46
46
  children: c
47
47
  }), y]
48
- }), /* @__PURE__ */ o(F, {
48
+ }), /* @__PURE__ */ o(H, {
49
49
  "aria-label": h,
50
50
  onClick: m,
51
51
  sx: {
@@ -54,7 +54,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
54
54
  right: 8,
55
55
  top: 18
56
56
  },
57
- children: /* @__PURE__ */ o(M, {
57
+ children: /* @__PURE__ */ o($, {
58
58
  name: "close",
59
59
  sx: { color: "action.active" }
60
60
  })
@@ -65,10 +65,10 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
65
65
  gap: 24,
66
66
  px: 24
67
67
  },
68
- children: [/* @__PURE__ */ o(j, {
68
+ children: [/* @__PURE__ */ o(M, {
69
69
  variant: "subtitle1",
70
70
  children: g
71
- }), r && /* @__PURE__ */ o(A, {
71
+ }), r && /* @__PURE__ */ o(N, {
72
72
  autoFocus: !0,
73
73
  error: !!a,
74
74
  helperText: a,
@@ -77,8 +77,9 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
77
77
  onChange: ({ target: { value: e } }) => {
78
78
  /^\d*\.?\d*$/.test(e) && b?.(e);
79
79
  },
80
+ onClick: C,
80
81
  slotProps: {
81
- input: { startAdornment: /* @__PURE__ */ o(w, {
82
+ input: { startAdornment: /* @__PURE__ */ o(A, {
82
83
  position: "start",
83
84
  children: "$"
84
85
  }) },
@@ -93,7 +94,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
93
94
  value: i
94
95
  })]
95
96
  }),
96
- /* @__PURE__ */ t(H, {
97
+ /* @__PURE__ */ t(w, {
97
98
  sx: {
98
99
  justifyContent: "flex-end",
99
100
  gap: 16,
@@ -103,11 +104,11 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
103
104
  children: [l && /* @__PURE__ */ o(f, {
104
105
  "aria-label": l,
105
106
  color: v,
106
- onClick: C,
107
+ onClick: D,
107
108
  children: l
108
109
  }), /* @__PURE__ */ o(f, {
109
110
  "aria-label": p,
110
- color: D,
111
+ color: k,
111
112
  disabled: d,
112
113
  onClick: s,
113
114
  variant: "contained",
@@ -119,7 +120,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
119
120
  });
120
121
  };
121
122
  export {
122
- U as default
123
+ X as default
123
124
  };
124
125
 
125
126
  //# sourceMappingURL=CurrencyDialog.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyDialog.es.js","names":[],"sources":["../../../src/common/components/CurrencyDialog.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, P } from '@mxenabled/mxui'\n\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport Dialog from '@mui/material/Dialog'\nimport DialogActions from '@mui/material/DialogActions'\nimport DialogContent from '@mui/material/DialogContent'\nimport IconButton from '@mui/material/IconButton'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\n\nimport { useScreenSize } from '../hooks'\n\ninterface CurrencyDialogProps {\n closeAriaLabel: string\n /** Body copy; a node so callers can emphasise part of it (e.g. a bold account name). */\n content?: ReactNode\n /** Validation or save error, shown under the input and marking it invalid. */\n errorText?: string\n inputLabel?: string\n inputValue?: string\n isOpen: boolean\n /** Disables the primary button on top of the built-in input check (e.g. while saving). */\n isPrimaryDisabled?: boolean\n handleInputChange?: (value: string) => void\n handleModalClose: () => void\n handlePrimaryAction: () => void\n handleSecondaryAction?: () => void\n primaryColor?: ButtonProps['color']\n primaryText: string\n secondaryColor?: ButtonProps['color']\n secondaryText?: string\n /** A line under the title, e.g. the account a setting applies to. */\n subtitle?: ReactNode\n title: string\n}\n\n// Created for V2 of BudgetDetailsDrawer.\n// The previous design will be replaced once the new design is finalized.\n// Also needs to be updated to use the mxui library\n// with the updated Dialog components.\n\nconst CurrencyDialog = ({\n closeAriaLabel,\n content,\n errorText,\n inputLabel,\n inputValue,\n isOpen,\n isPrimaryDisabled = false,\n handleInputChange,\n handleModalClose,\n handlePrimaryAction,\n handleSecondaryAction,\n primaryColor = 'primary',\n primaryText,\n secondaryColor = 'primary',\n secondaryText,\n subtitle,\n title,\n}: CurrencyDialogProps) => {\n const numericValue = parseFloat(inputValue ?? '0')\n const isValid = inputValue !== '' && inputValue !== '0' && !isNaN(numericValue)\n // One rule for the button and for Enter: the form's submit must not bypass a disabled button.\n const isPrimaryBlocked = isPrimaryDisabled || (inputLabel ? !isValid : false)\n\n const { isSmallMobile } = useScreenSize()\n\n return (\n <Dialog\n aria-label={title}\n aria-labelledby=\"modal-title\"\n maxWidth=\"xs\"\n onClose={handleModalClose}\n open={isOpen}\n role=\"dialog\"\n sx={{\n '& .MuiDialogContent-root': { p: 0 },\n ...(isSmallMobile && { '& .MuiDialog-paper': { minWidth: '100%' } }),\n }}\n >\n <DialogContent>\n <form\n onSubmit={(e) => {\n e.preventDefault()\n if (!isPrimaryBlocked) handlePrimaryAction()\n }}\n >\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n justifyContent: 'space-between',\n pb: 12,\n pl: 24,\n position: 'relative',\n pr: 16,\n pt: 24,\n }}\n >\n <Stack sx={{ gap: 4 }}>\n <H3 id=\"modal-title\">{title}</H3>\n {subtitle}\n </Stack>\n <IconButton\n aria-label={closeAriaLabel}\n onClick={handleModalClose}\n sx={{ minHeight: 24, position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n </Stack>\n <Stack sx={{ gap: 24, px: 24 }}>\n <P variant=\"subtitle1\">{content}</P>\n {inputLabel && (\n <TextField\n autoFocus={true}\n error={!!errorText}\n helperText={errorText}\n inputMode=\"decimal\"\n label={inputLabel}\n onChange={({ target: { value } }) => {\n if (!/^\\d*\\.?\\d*$/.test(value)) return\n\n handleInputChange?.(value)\n }}\n slotProps={{\n input: {\n startAdornment: <InputAdornment position=\"start\">$</InputAdornment>,\n },\n // Announce errors: focus is usually on the primary button when one appears.\n formHelperText: { role: 'alert' },\n htmlInput: {\n 'aria-label': inputLabel,\n noValidate: true,\n pattern: '[0-9]*\\\\.?[0-9]*',\n },\n }}\n type=\"text\"\n value={inputValue}\n />\n )}\n </Stack>\n <DialogActions\n sx={{\n justifyContent: 'flex-end',\n gap: 16,\n p: 24,\n '>:not(style)~:not(style)': { ml: 0 },\n }}\n >\n {secondaryText && (\n <Button\n aria-label={secondaryText}\n color={secondaryColor}\n onClick={handleSecondaryAction}\n >\n {secondaryText}\n </Button>\n )}\n <Button\n aria-label={primaryText}\n color={primaryColor}\n disabled={isPrimaryBlocked}\n onClick={handlePrimaryAction}\n variant=\"contained\"\n >\n {primaryText}\n </Button>\n </DialogActions>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default CurrencyDialog\n"],"mappings":"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"}
1
+ {"version":3,"file":"CurrencyDialog.es.js","names":[],"sources":["../../../src/common/components/CurrencyDialog.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, P } from '@mxenabled/mxui'\n\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport Dialog from '@mui/material/Dialog'\nimport DialogActions from '@mui/material/DialogActions'\nimport DialogContent from '@mui/material/DialogContent'\nimport IconButton from '@mui/material/IconButton'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\n\nimport { useScreenSize } from '../hooks'\n\ninterface CurrencyDialogProps {\n closeAriaLabel: string\n /** Body copy; a node so callers can emphasise part of it (e.g. a bold account name). */\n content?: ReactNode\n /** Validation or save error, shown under the input and marking it invalid. */\n errorText?: string\n inputLabel?: string\n inputValue?: string\n isOpen: boolean\n /** Disables the primary button on top of the built-in input check (e.g. while saving). */\n isPrimaryDisabled?: boolean\n handleInputChange?: (value: string) => void\n /**\n * Click on the amount input (e.g. analytics). Click, not focus: the input autofocuses on open,\n * so a focus callback would fire per open rather than per user interaction.\n */\n handleInputClick?: () => void\n handleModalClose: () => void\n handlePrimaryAction: () => void\n handleSecondaryAction?: () => void\n primaryColor?: ButtonProps['color']\n primaryText: string\n secondaryColor?: ButtonProps['color']\n secondaryText?: string\n /** A line under the title, e.g. the account a setting applies to. */\n subtitle?: ReactNode\n title: string\n}\n\n// Created for V2 of BudgetDetailsDrawer.\n// The previous design will be replaced once the new design is finalized.\n// Also needs to be updated to use the mxui library\n// with the updated Dialog components.\n\nconst CurrencyDialog = ({\n closeAriaLabel,\n content,\n errorText,\n inputLabel,\n inputValue,\n isOpen,\n isPrimaryDisabled = false,\n handleInputChange,\n handleInputClick,\n handleModalClose,\n handlePrimaryAction,\n handleSecondaryAction,\n primaryColor = 'primary',\n primaryText,\n secondaryColor = 'primary',\n secondaryText,\n subtitle,\n title,\n}: CurrencyDialogProps) => {\n const numericValue = parseFloat(inputValue ?? '0')\n const isValid = inputValue !== '' && inputValue !== '0' && !isNaN(numericValue)\n // One rule for the button and for Enter: the form's submit must not bypass a disabled button.\n const isPrimaryBlocked = isPrimaryDisabled || (inputLabel ? !isValid : false)\n\n const { isSmallMobile } = useScreenSize()\n\n return (\n <Dialog\n aria-label={title}\n aria-labelledby=\"modal-title\"\n maxWidth=\"xs\"\n onClose={handleModalClose}\n open={isOpen}\n role=\"dialog\"\n sx={{\n '& .MuiDialogContent-root': { p: 0 },\n ...(isSmallMobile && { '& .MuiDialog-paper': { minWidth: '100%' } }),\n }}\n >\n <DialogContent>\n <form\n onSubmit={(e) => {\n e.preventDefault()\n if (!isPrimaryBlocked) handlePrimaryAction()\n }}\n >\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n justifyContent: 'space-between',\n pb: 12,\n pl: 24,\n position: 'relative',\n pr: 16,\n pt: 24,\n }}\n >\n <Stack sx={{ gap: 4 }}>\n <H3 id=\"modal-title\">{title}</H3>\n {subtitle}\n </Stack>\n <IconButton\n aria-label={closeAriaLabel}\n onClick={handleModalClose}\n sx={{ minHeight: 24, position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n </Stack>\n <Stack sx={{ gap: 24, px: 24 }}>\n <P variant=\"subtitle1\">{content}</P>\n {inputLabel && (\n <TextField\n autoFocus={true}\n error={!!errorText}\n helperText={errorText}\n inputMode=\"decimal\"\n label={inputLabel}\n onChange={({ target: { value } }) => {\n if (!/^\\d*\\.?\\d*$/.test(value)) return\n\n handleInputChange?.(value)\n }}\n onClick={handleInputClick}\n slotProps={{\n input: {\n startAdornment: <InputAdornment position=\"start\">$</InputAdornment>,\n },\n // Announce errors: focus is usually on the primary button when one appears.\n formHelperText: { role: 'alert' },\n htmlInput: {\n 'aria-label': inputLabel,\n noValidate: true,\n pattern: '[0-9]*\\\\.?[0-9]*',\n },\n }}\n type=\"text\"\n value={inputValue}\n />\n )}\n </Stack>\n <DialogActions\n sx={{\n justifyContent: 'flex-end',\n gap: 16,\n p: 24,\n '>:not(style)~:not(style)': { ml: 0 },\n }}\n >\n {secondaryText && (\n <Button\n aria-label={secondaryText}\n color={secondaryColor}\n onClick={handleSecondaryAction}\n >\n {secondaryText}\n </Button>\n )}\n <Button\n aria-label={primaryText}\n color={primaryColor}\n disabled={isPrimaryBlocked}\n onClick={handlePrimaryAction}\n variant=\"contained\"\n >\n {primaryText}\n </Button>\n </DialogActions>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default CurrencyDialog\n"],"mappings":";;;;;;;;;;;;AAkDA,IAAM,IAAA,CAAkB,EACtB,gBAAA,GACA,SAAA,GACA,WAAA,GACA,YAAA,GACA,YAAA,GACA,QAAA,GACA,mBAAA,IAAoB,IACpB,mBAAA,GACA,kBAAA,GACA,kBAAA,GACA,qBAAA,GACA,uBAAA,GACA,cAAA,IAAe,WACf,aAAA,GACA,gBAAA,IAAiB,WACjB,eAAA,GACA,UAAA,GACA,OAAA,EAAA,MACyB;AAIzB,QAAM,IAAmB,MAAsB,IAAa,EAF5C,MAAe,MAAM,MAAe,OAAO,CAAC,MADvC,WAAW,KAAc,GACoB,CAAY,KAEP,KAEjE,EAAE,eAAA,EAAA,IAAkB,EAAc;AAExC,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,cAAY;AAAA,IACZ,mBAAgB;AAAA,IAChB,UAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAK;AAAA,IACL,IAAI;AAAA,MACF,4BAA4B,EAAE,GAAG,EAAE;AAAA,MACnC,GAAI,KAAiB,EAAE,sBAAsB,EAAE,UAAU,OAAO,EAAE;AAAA,IACpE;AAAA,IAEA,UAAA,gBAAA,EAAC,GAAD,EAAA,UACE,gBAAA,EAAC,QAAD;AAAA,MACE,UAAA,CAAW,MAAM;AACf,UAAE,eAAe,GACZ,KAAkB,EAAoB;AAAA,MAC7C;AAAA,MAJF,UAAA;AAAA,QAME,gBAAA,EAAC,GAAD;AAAA,UACE,IAAI;AAAA,YACF,YAAY;AAAA,YACZ,eAAe;AAAA,YACf,gBAAgB;AAAA,YAChB,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,UAAU;AAAA,YACV,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,UAVF,UAAA,CAYE,gBAAA,EAAC,GAAD;AAAA,YAAO,IAAI,EAAE,KAAK,EAAE;AAAA,YAApB,UAAA,CACE,gBAAA,EAAC,GAAD;AAAA,cAAI,IAAG;AAAA,cAAe,UAAA;AAAA,YAAU,CAAA,GAC/B,CACI;AAAA,UACP,CAAA,GAAA,gBAAA,EAAC,GAAD;AAAA,YACE,cAAY;AAAA,YACZ,SAAS;AAAA,YACT,IAAI;AAAA,cAAE,WAAW;AAAA,cAAI,UAAU;AAAA,cAAY,OAAO;AAAA,cAAG,KAAK;AAAA,YAAG;AAAA,YAE7D,UAAA,gBAAA,EAAC,GAAD;AAAA,cAAM,MAAK;AAAA,cAAQ,IAAI,EAAE,OAAO,gBAAgB;AAAA,YAAI,CAAA;AAAA,UAC1C,CAAA,CACP;AAAA;QACP,gBAAA,EAAC,GAAD;AAAA,UAAO,IAAI;AAAA,YAAE,KAAK;AAAA,YAAI,IAAI;AAAA,UAAG;AAAA,UAA7B,UAAA,CACE,gBAAA,EAAC,GAAD;AAAA,YAAG,SAAQ;AAAA,YAAa,UAAA;AAAA,UAAW,CAAA,GAClC,KACC,gBAAA,EAAC,GAAD;AAAA,YACE,WAAW;AAAA,YACX,OAAO,CAAC,CAAC;AAAA,YACT,YAAY;AAAA,YACZ,WAAU;AAAA,YACV,OAAO;AAAA,YACP,UAAA,CAAW,EAAE,QAAQ,EAAE,OAAA,EAAA,EAAA,MAAc;AACnC,cAAK,cAAc,KAAK,CAAK,KAE7B,IAAoB,CAAK;AAAA,YAC3B;AAAA,YACA,SAAS;AAAA,YACT,WAAW;AAAA,cACT,OAAO,EACL,gBAAgB,gBAAA,EAAC,GAAD;AAAA,gBAAgB,UAAS;AAAA,gBAAQ,UAAA;AAAA,cAAiB,CAAA,EACpE;AAAA,cAEA,gBAAgB,EAAE,MAAM,QAAQ;AAAA,cAChC,WAAW;AAAA,gBACT,cAAc;AAAA,gBACd,YAAY;AAAA,gBACZ,SAAS;AAAA,cACX;AAAA,YACF;AAAA,YACA,MAAK;AAAA,YACL,OAAO;AAAA,UACR,CAAA,CAEE;AAAA;QACP,gBAAA,EAAC,GAAD;AAAA,UACE,IAAI;AAAA,YACF,gBAAgB;AAAA,YAChB,KAAK;AAAA,YACL,GAAG;AAAA,YACH,4BAA4B,EAAE,IAAI,EAAE;AAAA,UACtC;AAAA,UANF,UAAA,CAQG,KACC,gBAAA,EAAC,GAAD;AAAA,YACE,cAAY;AAAA,YACZ,OAAO;AAAA,YACP,SAAS;AAAA,YAER,UAAA;AAAA,UACK,CAAA,GAEV,gBAAA,EAAC,GAAD;AAAA,YACE,cAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU;AAAA,YACV,SAAS;AAAA,YACT,SAAQ;AAAA,YAEP,UAAA;AAAA,UACK,CAAA,CACK;AAAA;MACX;AAAA,IACO,CAAA,EAAA,CAAA;AAAA,EACT,CAAA;AAEZ"}
@@ -251,6 +251,8 @@ export interface DebtsBlockCopy {
251
251
  payoff_date_label: string;
252
252
  monthly_payment_label: string;
253
253
  interest_rate_label: string;
254
+ /** Footer caption for the `debt_summary` total, e.g. "Total balance". Optional until the service ships it. */
255
+ total_balance_label?: string;
254
256
  }
255
257
  export interface InsightsFeedCopy {
256
258
  general: GeneralInsightsFeedCopy;
@@ -1,8 +1,8 @@
1
1
  import { useGlobalCopyStore as C, useGlobalUiStore as _, useInsightsV2Store as h } from "../../common/context/hooks.es.js";
2
2
  import B from "../../common/components/drawer/Drawer.es.js";
3
3
  import v from "../../common/components/Loader.es.js";
4
- import { getDynamicCtaBasedOnTriggers as w } from "./util.es.js";
5
- import { hasDrawerBlock as D } from "../mappings/hasDrawerBlock.es.js";
4
+ import { hasDrawerBlock as w } from "../mappings/hasDrawerBlock.es.js";
5
+ import { getDynamicCtaBasedOnTriggers as D } from "./util.es.js";
6
6
  import { actionBlocks as b } from "../mappings/blocks.es.js";
7
7
  import { observer as L } from "mobx-react-lite";
8
8
  import x from "@mui/material/Stack";
@@ -16,7 +16,7 @@ var m = ({ actionBlock: s, ...o }) => {
16
16
  if (!o.insights_payload) return null;
17
17
  const { primary_cta: u, secondary_cta: f, in_description_cta: k } = o.insights_payload, a = c.isDrawerOpen && c.guid === o.guid;
18
18
  if (!p) return a ? /* @__PURE__ */ e(v, {}) : null;
19
- const r = w(c, u, f, k), l = D(r?.block), t = l ? b[r?.block] : void 0;
19
+ const r = D(c, u, f, k), l = w(r?.block), t = l ? b[r?.block] : void 0;
20
20
  if (t?.presentation === "modal") return a ? /* @__PURE__ */ e(m, {
21
21
  actionBlock: t,
22
22
  beat: o
@@ -1,39 +1,41 @@
1
- import { buildDynamicCopy as G } from "../../../core/utils/Localization.es.js";
2
- import W from "../../../common/utils/AccountDisplayName.es.js";
3
- import { useGlobalCopyStore as j, useInsightsV2Store as z, useSettingsStore as q, useUserStore as J } from "../../../common/context/hooks.es.js";
4
- import K from "../../../common/components/Loader.es.js";
5
- import Q from "../../../common/components/CurrencyDialog.es.js";
6
- import { useBeatActions as X } from "../../beatActions.es.js";
7
- import { getDynamicCtaBasedOnTriggers as Y } from "../util.es.js";
8
- import { useCtaHandler as Z } from "../../ctaActions/useCtaAction.es.js";
9
- import { AMOUNT_INPUT_VARIANTS as oo, findLowBalanceProfile as to, isAmountInputVariant as L, isWholeAmount as eo, toInputValue as io } from "./layout.es.js";
1
+ import { buildDynamicCopy as j } from "../../../core/utils/Localization.es.js";
2
+ import z from "../../../common/utils/AccountDisplayName.es.js";
3
+ import { useGlobalCopyStore as q, useInsightsV2Store as J, useSettingsStore as K, useUserStore as Q } from "../../../common/context/hooks.es.js";
4
+ import X from "../../../common/components/Loader.es.js";
5
+ import Z from "../../../common/components/CurrencyDialog.es.js";
6
+ import { useBeatActions as oo } from "../../beatActions.es.js";
7
+ import { ANALYTICS_EVENTS_CONFIG as c } from "../../analytics/analyticsEventsConfig.es.js";
8
+ import { useCtaHandler as to } from "../../ctaActions/useCtaAction.es.js";
9
+ import { useAnalyticsEvent as eo } from "../../analytics/useAnalyticsEvent.es.js";
10
+ import { getDynamicCtaBasedOnTriggers as io } from "../util.es.js";
11
+ import { AMOUNT_INPUT_VARIANTS as no, findLowBalanceProfile as ro, isAmountInputVariant as x, isWholeAmount as ao, toInputValue as so } from "./layout.es.js";
10
12
  import r from "react";
11
- import { observer as no } from "mobx-react-lite";
12
- import T from "@mui/material/Stack";
13
- import { H3 as ro, InstitutionLogo as ao, Text as P } from "@mxenabled/mxui";
14
- import { Icon as so } from "@mxenabled/mx-icons";
13
+ import { observer as co } from "mobx-react-lite";
14
+ import P from "@mui/material/Stack";
15
+ import { H3 as lo, InstitutionLogo as uo, Text as H } from "@mxenabled/mxui";
16
+ import { Icon as mo } from "@mxenabled/mx-icons";
15
17
  import { jsx as e, jsxs as k } from "react/jsx-runtime";
16
- import co from "@mui/material/Dialog";
17
- import lo from "@mui/material/IconButton";
18
- var D = ({ children: y, closeAriaLabel: i, heading: o, onClose: l }) => {
18
+ import po from "@mui/material/Dialog";
19
+ import _o from "@mui/material/IconButton";
20
+ var w = ({ children: I, closeAriaLabel: i, heading: o, onClose: l }) => {
19
21
  const u = r.useId();
20
- return /* @__PURE__ */ e(co, {
22
+ return /* @__PURE__ */ e(po, {
21
23
  "aria-labelledby": o ? u : void 0,
22
24
  maxWidth: "xs",
23
25
  onClose: l,
24
26
  open: !0,
25
- children: /* @__PURE__ */ k(T, {
27
+ children: /* @__PURE__ */ k(P, {
26
28
  sx: {
27
29
  gap: 12,
28
30
  p: 24,
29
31
  position: "relative"
30
32
  },
31
33
  children: [
32
- o && /* @__PURE__ */ e(ro, {
34
+ o && /* @__PURE__ */ e(lo, {
33
35
  id: u,
34
36
  children: o
35
37
  }),
36
- /* @__PURE__ */ e(lo, {
38
+ /* @__PURE__ */ e(_o, {
37
39
  "aria-label": i || "Close",
38
40
  onClick: l,
39
41
  sx: {
@@ -41,88 +43,91 @@ var D = ({ children: y, closeAriaLabel: i, heading: o, onClose: l }) => {
41
43
  right: 8,
42
44
  top: 18
43
45
  },
44
- children: /* @__PURE__ */ e(so, {
46
+ children: /* @__PURE__ */ e(mo, {
45
47
  name: "close",
46
48
  sx: { color: "action.active" }
47
49
  })
48
50
  }),
49
- y
51
+ I
50
52
  ]
51
53
  })
52
54
  });
53
- }, Po = no(function({ beat: i }) {
54
- const { common: o, insights_feed: l } = j(), { actionDrawer: u, closeActionDrawer: c } = z(), f = q(), { userInsightProfile: v } = J(), b = Z(i, X()), { primary_cta: B, secondary_cta: N, in_description_cta: M } = i.insights_payload ?? {}, s = Y(u, B, N, M)?.variant, a = l?.amount_input_modal, t = s === oo.LOW_BALANCE_THRESHOLD, _ = t ? to(f.notificationProfiles, i.primary_account_guid) : void 0, I = t ? _?.threshold : v?.savings_opportunity_minimum_checking_account_balance, [V, E] = r.useState(t), d = r.useRef(!0);
55
+ }, ko = co(function({ beat: i }) {
56
+ const { common: o, insights_feed: l } = q(), { actionDrawer: u, closeActionDrawer: m } = J(), f = K(), { userInsightProfile: v } = Q(), S = to(i, oo()), T = eo(), { primary_cta: B, secondary_cta: O, in_description_cta: V } = i.insights_payload ?? {}, s = io(u, B, O, V)?.variant, a = l?.amount_input_modal, t = s === no.LOW_BALANCE_THRESHOLD, h = t ? ro(f.notificationProfiles, i.primary_account_guid) : void 0, E = t ? h?.threshold : v?.savings_opportunity_minimum_checking_account_balance, [G, R] = r.useState(t), d = r.useRef(!0);
55
57
  r.useEffect(() => (d.current = !0, () => {
56
58
  d.current = !1;
57
59
  }), []), r.useEffect(() => {
58
60
  t && f.loadNotificationProfiles().finally(() => {
59
- d.current && E(!1);
61
+ d.current && R(!1);
60
62
  });
61
63
  }, [t, f]);
62
- const [H, O] = r.useState(null), [S, A] = r.useState(!1), [R, h] = r.useState(!1), m = H ?? io(I);
63
- if (V) return /* @__PURE__ */ e(D, {
64
+ const [F, M] = r.useState(null), [A, C] = r.useState(!1), [U, y] = r.useState(!1), p = F ?? so(E);
65
+ if (G) return /* @__PURE__ */ e(w, {
64
66
  closeAriaLabel: o?.close_aria,
65
- onClose: c,
66
- children: /* @__PURE__ */ e(K, {})
67
+ onClose: m,
68
+ children: /* @__PURE__ */ e(X, {})
67
69
  });
68
- const n = L(s) ? a?.[s] : void 0, x = !!n?.title && !!n.description && !!n.input_label && !!a?.whole_number_error && !!a.save_error && !!o?.save_button && (!t || !!o.cancel_button) && !!o.close_aria;
69
- if (!a || !n || !x || !(t ? _ : v))
70
- return console.warn(`amount_input_modal_block: ${a ? L(s) ? x ? t ? "no low-balance profile for the account" : "user insight profile not set by the host" : `copy incomplete for "${s}"` : `unknown variant "${s}"` : "copy namespace missing"}`), /* @__PURE__ */ e(D, {
70
+ const n = x(s) ? a?.[s] : void 0, b = !!n?.title && !!n.description && !!n.input_label && !!a?.whole_number_error && !!a.save_error && !!o?.save_button && (!t || !!o.cancel_button) && !!o.close_aria;
71
+ if (!a || !n || !b || !(t ? h : v))
72
+ return console.warn(`amount_input_modal_block: ${a ? x(s) ? b ? t ? "no low-balance profile for the account" : "user insight profile not set by the host" : `copy incomplete for "${s}"` : `unknown variant "${s}"` : "copy namespace missing"}`), /* @__PURE__ */ e(w, {
71
73
  closeAriaLabel: o?.close_aria,
72
74
  heading: o?.error_heading,
73
- onClose: c,
74
- children: /* @__PURE__ */ e(P, { children: o?.error_message })
75
+ onClose: m,
76
+ children: /* @__PURE__ */ e(H, { children: o?.error_message })
75
77
  });
76
- const C = !t && !!I, w = i.primary_account ? W(i.primary_account) : "", g = eo(m), U = m !== "" && !g, $ = async () => {
77
- if (!g || S) return;
78
- const p = Number(m);
79
- A(!0), h(!1);
80
- const F = t ? await b("updateNotificationProfileThreshold", {
81
- profileGuid: _?.guid,
82
- threshold: p
83
- }) : await b("updateUserInsightProfile", { fields: { savings_opportunity_minimum_checking_account_balance: p } });
84
- d.current && (A(!1), F ? c() : h(!0));
78
+ const D = () => {
79
+ T(t ? c.EDIT_INSIGHT_THRESHOLD_CANCELED : c.EDIT_SAFETY_NET_CANCEL), m();
80
+ }, L = !t && !!E, N = i.primary_account ? z(i.primary_account) : "", g = ao(p), $ = p !== "" && !g, W = async () => {
81
+ if (!g || A) return;
82
+ const _ = Number(p);
83
+ C(!0), y(!1);
84
+ const Y = t ? await S("updateNotificationProfileThreshold", {
85
+ profileGuid: h?.guid,
86
+ threshold: _
87
+ }, { analytics: c.EDIT_INSIGHT_THRESHOLD_SAVE }) : await S("updateUserInsightProfile", { fields: { savings_opportunity_minimum_checking_account_balance: _ } }, { analytics: c.EDIT_SAFETY_NET_SAVE });
88
+ d.current && (C(!1), Y ? m() : y(!0));
85
89
  };
86
- return /* @__PURE__ */ e(Q, {
90
+ return /* @__PURE__ */ e(Z, {
87
91
  closeAriaLabel: o.close_aria,
88
- content: G(C && n.description_edit || n.description, /* @__PURE__ */ e("strong", { children: w }, "account")),
89
- errorText: U ? a.whole_number_error : R ? a.save_error : void 0,
90
- handleInputChange: (p) => {
91
- O(p), h(!1);
92
+ content: j(L && n.description_edit || n.description, /* @__PURE__ */ e("strong", { children: N }, "account")),
93
+ errorText: $ ? a.whole_number_error : U ? a.save_error : void 0,
94
+ handleInputChange: (_) => {
95
+ M(_), y(!1);
92
96
  },
93
- handleModalClose: c,
97
+ handleInputClick: t ? () => T(c.EDIT_INSIGHT_THRESHOLD_INPUT_SELECTED) : void 0,
98
+ handleModalClose: D,
94
99
  handlePrimaryAction: () => {
95
- $();
100
+ W();
96
101
  },
97
- handleSecondaryAction: c,
102
+ handleSecondaryAction: D,
98
103
  inputLabel: n.input_label,
99
- inputValue: m,
104
+ inputValue: p,
100
105
  isOpen: !0,
101
- isPrimaryDisabled: S || !g,
106
+ isPrimaryDisabled: A || !g,
102
107
  primaryText: o.save_button,
103
108
  secondaryText: t ? o.cancel_button : void 0,
104
- subtitle: t && i.primary_account ? /* @__PURE__ */ k(T, {
109
+ subtitle: t && i.primary_account ? /* @__PURE__ */ k(P, {
105
110
  sx: {
106
111
  alignItems: "center",
107
112
  flexDirection: "row",
108
113
  gap: 8
109
114
  },
110
- children: [/* @__PURE__ */ e(ao, {
115
+ children: [/* @__PURE__ */ e(uo, {
111
116
  alt: "",
112
117
  institutionGuid: i.primary_account.institution_guid ?? "",
113
118
  size: 16
114
- }), /* @__PURE__ */ e(P, {
119
+ }), /* @__PURE__ */ e(H, {
115
120
  color: "text.secondary",
116
121
  uppercase: !0,
117
122
  variant: "body2",
118
- children: w
123
+ children: N
119
124
  })]
120
125
  }) : void 0,
121
- title: C && n.title_edit || n.title
126
+ title: L && n.title_edit || n.title
122
127
  });
123
128
  });
124
129
  export {
125
- Po as AmountInputModalBlock
130
+ ko as AmountInputModalBlock
126
131
  };
127
132
 
128
133
  //# sourceMappingURL=AmountInputModalBlock.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AmountInputModalBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.tsx"],"sourcesContent":["import React from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport MuiDialog from '@mui/material/Dialog'\nimport IconButton from '@mui/material/IconButton'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, InstitutionLogo, Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n CurrencyDialog,\n Loader,\n buildDynamicCopy,\n useGlobalCopyStore,\n useInsightsV2Store,\n useSettingsStore,\n useUserStore,\n} from '../../../common'\nimport getAccountDisplayName from '../../../common/utils/AccountDisplayName'\nimport { useBeatActions } from '../../beatActions'\nimport { useCtaHandler } from '../../ctaActions'\nimport type { ActionBlockProps } from '../../mappings/blocks'\nimport { getDynamicCtaBasedOnTriggers } from '../util'\n\nimport {\n AMOUNT_INPUT_VARIANTS,\n findLowBalanceProfile,\n isAmountInputVariant,\n isWholeAmount,\n toInputValue,\n} from './layout'\n\n/**\n * The dialog frame for the states that have no form: loading, and a non-retriable problem. Named by\n * its heading and closable by button, Esc and backdrop, like the form dialog.\n */\nconst StatusDialog = ({\n children,\n closeAriaLabel,\n heading,\n onClose,\n}: React.PropsWithChildren<{ closeAriaLabel?: string; heading?: string; onClose: () => void }>) => {\n const headingId = React.useId()\n\n return (\n <MuiDialog\n aria-labelledby={heading ? headingId : undefined}\n maxWidth=\"xs\"\n onClose={onClose}\n open={true}\n >\n <Stack sx={{ gap: 12, p: 24, position: 'relative' }}>\n {heading && <H3 id={headingId}>{heading}</H3>}\n <IconButton\n aria-label={closeAriaLabel || 'Close'}\n onClick={onClose}\n sx={{ position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n {children}\n </Stack>\n </MuiDialog>\n )\n}\n\n/**\n * `amount_input_modal_block`: one whole-dollar setting edited in a dialog (Figma 441:24913 and\n * 5402:34137). The CTA's variant picks what is edited:\n * - `low_balance_threshold`: the `threshold` on the beat account's low-balance alert profile;\n * - `safety_net`: `savings_opportunity_minimum_checking_account_balance` on the insight profile.\n *\n * Registered with `presentation: 'modal'`, so no drawer opens behind it; closing the dialog ends\n * the action-drawer session. `observer` is required: the starting values are read from stores.\n */\nexport const AmountInputModalBlock = observer(function AmountInputModalBlock({\n beat,\n}: ActionBlockProps) {\n const { common: commonCopy, insights_feed: insightsFeedCopy } = useGlobalCopyStore()\n const { actionDrawer, closeActionDrawer } = useInsightsV2Store()\n const settingsStore = useSettingsStore()\n const { userInsightProfile } = useUserStore()\n const runHandler = useCtaHandler(beat, useBeatActions())\n\n const { primary_cta, secondary_cta, in_description_cta } = beat.insights_payload ?? {}\n const variant = getDynamicCtaBasedOnTriggers(\n actionDrawer,\n primary_cta,\n secondary_cta,\n in_description_cta,\n )?.variant\n const copy = insightsFeedCopy?.amount_input_modal\n const isLowBalance = variant === AMOUNT_INPUT_VARIANTS.LOW_BALANCE_THRESHOLD\n\n const lowBalanceProfile = isLowBalance\n ? findLowBalanceProfile(settingsStore.notificationProfiles, beat.primary_account_guid)\n : undefined\n const savedAmount = isLowBalance\n ? lowBalanceProfile?.threshold\n : userInsightProfile?.savings_opportunity_minimum_checking_account_balance\n\n // Mounted only while open (modal presentation), so mounting is the open: profiles are reloaded\n // here on each open, so the threshold prefills as saved now. `SettingsStore` swallows a failed\n // request, which leaves no profile for the account: the missing-profile error below covers it.\n const [isLoadingProfiles, setIsLoadingProfiles] = React.useState(isLowBalance)\n // False once unmounted: a save that resolves after the user closed the dialog must not close\n // (or update) whatever action session is open by then.\n const isMounted = React.useRef(true)\n\n React.useEffect(() => {\n isMounted.current = true\n\n return () => {\n isMounted.current = false\n }\n }, [])\n\n React.useEffect(() => {\n if (!isLowBalance) return\n\n void settingsStore.loadNotificationProfiles().finally(() => {\n if (isMounted.current) setIsLoadingProfiles(false)\n })\n }, [isLowBalance, settingsStore])\n\n // `null` until the user types: the field shows the saved amount until then.\n const [typed, setTyped] = React.useState<string | null>(null)\n const [isSaving, setIsSaving] = React.useState(false)\n const [saveFailed, setSaveFailed] = React.useState(false)\n\n const value = typed ?? toInputValue(savedAmount)\n\n if (isLoadingProfiles) {\n return (\n <StatusDialog closeAriaLabel={commonCopy?.close_aria} onClose={closeActionDrawer}>\n <Loader />\n </StatusDialog>\n )\n }\n\n // Copy ships separately (see the doc's Rollout order), so check every string the form renders:\n // a partial ship falls to the error dialog instead of throwing mid-render.\n const variantCopy = isAmountInputVariant(variant) ? copy?.[variant] : undefined\n const hasCopy =\n !!variantCopy?.title &&\n !!variantCopy.description &&\n !!variantCopy.input_label &&\n !!copy?.whole_number_error &&\n !!copy.save_error &&\n !!commonCopy?.save_button &&\n // Cancel renders for the low-balance variant only.\n (!isLowBalance || !!commonCopy.cancel_button) &&\n !!commonCopy.close_aria\n const hasProfile = isLowBalance ? !!lowBalanceProfile : !!userInsightProfile\n\n if (!copy || !variantCopy || !hasCopy || !hasProfile) {\n // One message to the user; distinct warnings so a rollout problem can be traced to its cause.\n console.warn(\n `amount_input_modal_block: ${\n !copy\n ? 'copy namespace missing'\n : !isAmountInputVariant(variant)\n ? `unknown variant \"${variant}\"`\n : !hasCopy\n ? `copy incomplete for \"${variant}\"`\n : isLowBalance\n ? 'no low-balance profile for the account'\n : 'user insight profile not set by the host'\n }`,\n )\n\n return (\n <StatusDialog\n closeAriaLabel={commonCopy?.close_aria}\n heading={commonCopy?.error_heading}\n onClose={closeActionDrawer}\n >\n <Text>{commonCopy?.error_message}</Text>\n </StatusDialog>\n )\n }\n\n const isEdit = !isLowBalance && !!savedAmount\n const accountName = beat.primary_account ? getAccountDisplayName(beat.primary_account) : ''\n const isValid = isWholeAmount(value)\n const isInvalid = value !== '' && !isValid\n\n const handleSave = async () => {\n // Guarded here too, not only by the disabled button: Enter submits the dialog's form directly.\n if (!isValid || isSaving) return\n\n const amount = Number(value)\n\n setIsSaving(true)\n setSaveFailed(false)\n\n const ok = isLowBalance\n ? await runHandler('updateNotificationProfileThreshold', {\n profileGuid: lowBalanceProfile?.guid,\n threshold: amount,\n })\n : await runHandler('updateUserInsightProfile', {\n fields: { savings_opportunity_minimum_checking_account_balance: amount },\n })\n\n if (!isMounted.current) return\n\n setIsSaving(false)\n\n if (ok) closeActionDrawer()\n else setSaveFailed(true)\n }\n\n return (\n <CurrencyDialog\n closeAriaLabel={commonCopy.close_aria}\n // Only strings and the <strong> go in, so the result is renderable (the helper's type also\n // admits raw DOM nodes).\n content={\n buildDynamicCopy(\n (isEdit && variantCopy.description_edit) || variantCopy.description,\n <strong key=\"account\">{accountName}</strong>,\n ) as React.ReactNode\n }\n errorText={isInvalid ? copy.whole_number_error : saveFailed ? copy.save_error : undefined}\n handleInputChange={(next) => {\n setTyped(next)\n setSaveFailed(false)\n }}\n handleModalClose={closeActionDrawer}\n handlePrimaryAction={() => void handleSave()}\n handleSecondaryAction={closeActionDrawer}\n inputLabel={variantCopy.input_label}\n inputValue={value}\n isOpen={true}\n isPrimaryDisabled={isSaving || !isValid}\n primaryText={commonCopy.save_button}\n secondaryText={isLowBalance ? commonCopy.cancel_button : undefined}\n subtitle={\n isLowBalance && beat.primary_account ? (\n <Stack sx={{ alignItems: 'center', flexDirection: 'row', gap: 8 }}>\n {/* Decorative: the account name is the visible text beside it. */}\n <InstitutionLogo\n alt=\"\"\n institutionGuid={beat.primary_account.institution_guid ?? ''}\n size={16}\n />\n <Text color=\"text.secondary\" uppercase={true} variant=\"body2\">\n {accountName}\n </Text>\n </Stack>\n ) : undefined\n }\n title={(isEdit && variantCopy.title_edit) || variantCopy.title}\n />\n )\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"AmountInputModalBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.tsx"],"sourcesContent":["import React from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport MuiDialog from '@mui/material/Dialog'\nimport IconButton from '@mui/material/IconButton'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, InstitutionLogo, Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n CurrencyDialog,\n Loader,\n buildDynamicCopy,\n useGlobalCopyStore,\n useInsightsV2Store,\n useSettingsStore,\n useUserStore,\n} from '../../../common'\nimport getAccountDisplayName from '../../../common/utils/AccountDisplayName'\nimport { ANALYTICS_EVENTS_CONFIG, useAnalyticsEvent } from '../../analytics'\nimport { useBeatActions } from '../../beatActions'\nimport { useCtaHandler } from '../../ctaActions'\nimport type { ActionBlockProps } from '../../mappings/blocks'\nimport { getDynamicCtaBasedOnTriggers } from '../util'\n\nimport {\n AMOUNT_INPUT_VARIANTS,\n findLowBalanceProfile,\n isAmountInputVariant,\n isWholeAmount,\n toInputValue,\n} from './layout'\n\n/**\n * The dialog frame for the states that have no form: loading, and a non-retriable problem. Named by\n * its heading and closable by button, Esc and backdrop, like the form dialog.\n */\nconst StatusDialog = ({\n children,\n closeAriaLabel,\n heading,\n onClose,\n}: React.PropsWithChildren<{ closeAriaLabel?: string; heading?: string; onClose: () => void }>) => {\n const headingId = React.useId()\n\n return (\n <MuiDialog\n aria-labelledby={heading ? headingId : undefined}\n maxWidth=\"xs\"\n onClose={onClose}\n open={true}\n >\n <Stack sx={{ gap: 12, p: 24, position: 'relative' }}>\n {heading && <H3 id={headingId}>{heading}</H3>}\n <IconButton\n aria-label={closeAriaLabel || 'Close'}\n onClick={onClose}\n sx={{ position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n {children}\n </Stack>\n </MuiDialog>\n )\n}\n\n/**\n * `amount_input_modal_block`: one whole-dollar setting edited in a dialog (Figma 441:24913 and\n * 5402:34137). The CTA's variant picks what is edited:\n * - `low_balance_threshold`: the `threshold` on the beat account's low-balance alert profile;\n * - `safety_net`: `savings_opportunity_minimum_checking_account_balance` on the insight profile.\n *\n * Registered with `presentation: 'modal'`, so no drawer opens behind it; closing the dialog ends\n * the action-drawer session. `observer` is required: the starting values are read from stores.\n */\nexport const AmountInputModalBlock = observer(function AmountInputModalBlock({\n beat,\n}: ActionBlockProps) {\n const { common: commonCopy, insights_feed: insightsFeedCopy } = useGlobalCopyStore()\n const { actionDrawer, closeActionDrawer } = useInsightsV2Store()\n const settingsStore = useSettingsStore()\n const { userInsightProfile } = useUserStore()\n const runHandler = useCtaHandler(beat, useBeatActions())\n const fireAnalyticsEvent = useAnalyticsEvent()\n\n const { primary_cta, secondary_cta, in_description_cta } = beat.insights_payload ?? {}\n const variant = getDynamicCtaBasedOnTriggers(\n actionDrawer,\n primary_cta,\n secondary_cta,\n in_description_cta,\n )?.variant\n const copy = insightsFeedCopy?.amount_input_modal\n const isLowBalance = variant === AMOUNT_INPUT_VARIANTS.LOW_BALANCE_THRESHOLD\n\n const lowBalanceProfile = isLowBalance\n ? findLowBalanceProfile(settingsStore.notificationProfiles, beat.primary_account_guid)\n : undefined\n const savedAmount = isLowBalance\n ? lowBalanceProfile?.threshold\n : userInsightProfile?.savings_opportunity_minimum_checking_account_balance\n\n // Mounted only while open (modal presentation), so mounting is the open: profiles are reloaded\n // here on each open, so the threshold prefills as saved now. `SettingsStore` swallows a failed\n // request, which leaves no profile for the account: the missing-profile error below covers it.\n const [isLoadingProfiles, setIsLoadingProfiles] = React.useState(isLowBalance)\n // False once unmounted: a save that resolves after the user closed the dialog must not close\n // (or update) whatever action session is open by then.\n const isMounted = React.useRef(true)\n\n React.useEffect(() => {\n isMounted.current = true\n\n return () => {\n isMounted.current = false\n }\n }, [])\n\n React.useEffect(() => {\n if (!isLowBalance) return\n\n void settingsStore.loadNotificationProfiles().finally(() => {\n if (isMounted.current) setIsLoadingProfiles(false)\n })\n }, [isLowBalance, settingsStore])\n\n // `null` until the user types: the field shows the saved amount until then.\n const [typed, setTyped] = React.useState<string | null>(null)\n const [isSaving, setIsSaving] = React.useState(false)\n const [saveFailed, setSaveFailed] = React.useState(false)\n\n const value = typed ?? toInputValue(savedAmount)\n\n if (isLoadingProfiles) {\n return (\n <StatusDialog closeAriaLabel={commonCopy?.close_aria} onClose={closeActionDrawer}>\n <Loader />\n </StatusDialog>\n )\n }\n\n // Copy ships separately (see the doc's Rollout order), so check every string the form renders:\n // a partial ship falls to the error dialog instead of throwing mid-render.\n const variantCopy = isAmountInputVariant(variant) ? copy?.[variant] : undefined\n const hasCopy =\n !!variantCopy?.title &&\n !!variantCopy.description &&\n !!variantCopy.input_label &&\n !!copy?.whole_number_error &&\n !!copy.save_error &&\n !!commonCopy?.save_button &&\n // Cancel renders for the low-balance variant only.\n (!isLowBalance || !!commonCopy.cancel_button) &&\n !!commonCopy.close_aria\n const hasProfile = isLowBalance ? !!lowBalanceProfile : !!userInsightProfile\n\n if (!copy || !variantCopy || !hasCopy || !hasProfile) {\n // One message to the user; distinct warnings so a rollout problem can be traced to its cause.\n console.warn(\n `amount_input_modal_block: ${\n !copy\n ? 'copy namespace missing'\n : !isAmountInputVariant(variant)\n ? `unknown variant \"${variant}\"`\n : !hasCopy\n ? `copy incomplete for \"${variant}\"`\n : isLowBalance\n ? 'no low-balance profile for the account'\n : 'user insight profile not set by the host'\n }`,\n )\n\n return (\n <StatusDialog\n closeAriaLabel={commonCopy?.close_aria}\n heading={commonCopy?.error_heading}\n onClose={closeActionDrawer}\n >\n <Text>{commonCopy?.error_message}</Text>\n </StatusDialog>\n )\n }\n\n // Trigger 4 — user dismissed without saving (close icon, Cancel button, backdrop). Pulse\n // parity: EditThreshold.js close button / EditSafetyNet.js cancel button.\n const handleDismiss = () => {\n fireAnalyticsEvent(\n isLowBalance\n ? ANALYTICS_EVENTS_CONFIG.EDIT_INSIGHT_THRESHOLD_CANCELED\n : ANALYTICS_EVENTS_CONFIG.EDIT_SAFETY_NET_CANCEL,\n )\n closeActionDrawer()\n }\n\n const isEdit = !isLowBalance && !!savedAmount\n const accountName = beat.primary_account ? getAccountDisplayName(beat.primary_account) : ''\n const isValid = isWholeAmount(value)\n const isInvalid = value !== '' && !isValid\n\n const handleSave = async () => {\n // Guarded here too, not only by the disabled button: Enter submits the dialog's form directly.\n if (!isValid || isSaving) return\n\n const amount = Number(value)\n\n setIsSaving(true)\n setSaveFailed(false)\n\n // Trigger 2: save analytics ride the handler run, fired only on success.\n const ok = isLowBalance\n ? await runHandler(\n 'updateNotificationProfileThreshold',\n { profileGuid: lowBalanceProfile?.guid, threshold: amount },\n { analytics: ANALYTICS_EVENTS_CONFIG.EDIT_INSIGHT_THRESHOLD_SAVE },\n )\n : await runHandler(\n 'updateUserInsightProfile',\n { fields: { savings_opportunity_minimum_checking_account_balance: amount } },\n { analytics: ANALYTICS_EVENTS_CONFIG.EDIT_SAFETY_NET_SAVE },\n )\n\n if (!isMounted.current) return\n\n setIsSaving(false)\n\n if (ok) closeActionDrawer()\n else setSaveFailed(true)\n }\n\n return (\n <CurrencyDialog\n closeAriaLabel={commonCopy.close_aria}\n // Only strings and the <strong> go in, so the result is renderable (the helper's type also\n // admits raw DOM nodes).\n content={\n buildDynamicCopy(\n (isEdit && variantCopy.description_edit) || variantCopy.description,\n <strong key=\"account\">{accountName}</strong>,\n ) as React.ReactNode\n }\n errorText={isInvalid ? copy.whole_number_error : saveFailed ? copy.save_error : undefined}\n handleInputChange={(next) => {\n setTyped(next)\n setSaveFailed(false)\n }}\n handleInputClick={\n // Pulse fires input-selected for the threshold dialog only (EditThreshold.js); the\n // safety-net dialog has no input event.\n isLowBalance\n ? () => fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.EDIT_INSIGHT_THRESHOLD_INPUT_SELECTED)\n : undefined\n }\n handleModalClose={handleDismiss}\n handlePrimaryAction={() => void handleSave()}\n handleSecondaryAction={handleDismiss}\n inputLabel={variantCopy.input_label}\n inputValue={value}\n isOpen={true}\n isPrimaryDisabled={isSaving || !isValid}\n primaryText={commonCopy.save_button}\n secondaryText={isLowBalance ? commonCopy.cancel_button : undefined}\n subtitle={\n isLowBalance && beat.primary_account ? (\n <Stack sx={{ alignItems: 'center', flexDirection: 'row', gap: 8 }}>\n {/* Decorative: the account name is the visible text beside it. */}\n <InstitutionLogo\n alt=\"\"\n institutionGuid={beat.primary_account.institution_guid ?? ''}\n size={16}\n />\n <Text color=\"text.secondary\" uppercase={true} variant=\"body2\">\n {accountName}\n </Text>\n </Stack>\n ) : undefined\n }\n title={(isEdit && variantCopy.title_edit) || variantCopy.title}\n />\n )\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuCA,IAAM,IAAA,CAAgB,EACpB,UAAA,GACA,gBAAA,GACA,SAAA,GACA,SAAA,EAAA,MACiG;AACjG,QAAM,IAAY,EAAM,MAAM;AAE9B,SACE,gBAAA,EAAC,IAAD;AAAA,IACE,mBAAiB,IAAU,IAAY;AAAA,IACvC,UAAS;AAAA,IACA,SAAA;AAAA,IACT,MAAM;AAAA,IAEN,UAAA,gBAAA,EAAC,GAAD;AAAA,MAAO,IAAI;AAAA,QAAE,KAAK;AAAA,QAAI,GAAG;AAAA,QAAI,UAAU;AAAA,MAAW;AAAA,MAAlD,UAAA;AAAA,QACG,KAAW,gBAAA,EAAC,IAAD;AAAA,UAAI,IAAI;AAAA,UAAY,UAAA;AAAA,QAAY,CAAA;AAAA,QAC5C,gBAAA,EAAC,IAAD;AAAA,UACE,cAAY,KAAkB;AAAA,UAC9B,SAAS;AAAA,UACT,IAAI;AAAA,YAAE,UAAU;AAAA,YAAY,OAAO;AAAA,YAAG,KAAK;AAAA,UAAG;AAAA,UAE9C,UAAA,gBAAA,EAAC,IAAD;AAAA,YAAM,MAAK;AAAA,YAAQ,IAAI,EAAE,OAAO,gBAAgB;AAAA,UAAI,CAAA;AAAA,QAC1C,CAAA;AAAA,QACX;AAAA,MACI;AAAA;EACE,CAAA;AAEf,GAWa,KAAwB,GAAS,SAA+B,EAC3E,MAAA,EAAA,GACmB;AACnB,QAAM,EAAE,QAAQ,GAAY,eAAe,EAAA,IAAqB,EAAmB,GAC7E,EAAE,cAAA,GAAc,mBAAA,EAAA,IAAsB,EAAmB,GACzD,IAAgB,EAAiB,GACjC,EAAE,oBAAA,EAAA,IAAuB,EAAa,GACtC,IAAa,GAAc,GAAM,GAAe,CAAC,GACjD,IAAqB,GAAkB,GAEvC,EAAE,aAAA,GAAa,eAAA,GAAe,oBAAA,EAAA,IAAuB,EAAK,oBAAoB,CAAC,GAC/E,IAAU,GACd,GACA,GACA,GACA,CACF,GAAG,SACG,IAAO,GAAkB,oBACzB,IAAe,MAAY,GAAsB,uBAEjD,IAAoB,IACtB,GAAsB,EAAc,sBAAsB,EAAK,oBAAoB,IACnF,QACE,IAAc,IAChB,GAAmB,YACnB,GAAoB,sDAKlB,CAAC,GAAmB,CAAA,IAAwB,EAAM,SAAS,CAAY,GAGvE,IAAY,EAAM,OAAO,EAAI;AAEnC,EAAA,EAAM,UAAA,OACJ,EAAU,UAAU,IAEpB,MAAa;AACX,IAAA,EAAU,UAAU;AAAA,EACtB,IACC,CAAC,CAAC,GAEL,EAAM,UAAA,MAAgB;AACpB,IAAK,KAEL,EAAmB,yBAAyB,EAAE,QAAA,MAAc;AAC1D,MAAI,EAAU,WAAS,EAAqB,EAAK;AAAA,IACnD,CAAC;AAAA,EACH,GAAG,CAAC,GAAc,CAAa,CAAC;AAGhC,QAAM,CAAC,GAAO,CAAA,IAAY,EAAM,SAAwB,IAAI,GACtD,CAAC,GAAU,CAAA,IAAe,EAAM,SAAS,EAAK,GAC9C,CAAC,GAAY,CAAA,IAAiB,EAAM,SAAS,EAAK,GAElD,IAAQ,KAAS,GAAa,CAAW;AAE/C,MAAI,EACF,QACE,gBAAA,EAAC,GAAD;AAAA,IAAc,gBAAgB,GAAY;AAAA,IAAY,SAAS;AAAA,IAC7D,UAAA,gBAAA,EAAC,GAAD,CAAS,CAAA;AAAA,EACG,CAAA;AAMlB,QAAM,IAAc,EAAqB,CAAO,IAAI,IAAO,CAAA,IAAW,QAChE,IACJ,CAAC,CAAC,GAAa,SACf,CAAC,CAAC,EAAY,eACd,CAAC,CAAC,EAAY,eACd,CAAC,CAAC,GAAM,sBACR,CAAC,CAAC,EAAK,cACP,CAAC,CAAC,GAAY,gBAEb,CAAC,KAAgB,CAAC,CAAC,EAAW,kBAC/B,CAAC,CAAC,EAAW;AAGf,MAAI,CAAC,KAAQ,CAAC,KAAe,CAAC,KAAW,EAFtB,IAAiB,IAAsB;AAIxD,mBAAQ,KACN,6BACG,IAEI,EAAqB,CAAO,IAE1B,IAEC,IACE,2CACA,6CAHF,wBAAwB,CAAA,MAF1B,oBAAoB,CAAA,MAFtB,wBAOM,EAEd,GAGE,gBAAA,EAAC,GAAD;AAAA,MACE,gBAAgB,GAAY;AAAA,MAC5B,SAAS,GAAY;AAAA,MACrB,SAAS;AAAA,MAET,UAAA,gBAAA,EAAC,GAAD,EAAA,UAAO,GAAY,cAAoB,CAAA;AAAA,IAC3B,CAAA;AAMlB,QAAM,IAAA,MAAsB;AAC1B,IAAA,EACE,IACI,EAAwB,kCACxB,EAAwB,sBAC9B,GACA,EAAkB;AAAA,EACpB,GAEM,IAAS,CAAC,KAAgB,CAAC,CAAC,GAC5B,IAAc,EAAK,kBAAkB,EAAsB,EAAK,eAAe,IAAI,IACnF,IAAU,GAAc,CAAK,GAC7B,IAAY,MAAU,MAAM,CAAC,GAE7B,IAAa,YAAY;AAE7B,QAAI,CAAC,KAAW,EAAU;AAE1B,UAAM,IAAS,OAAO,CAAK;AAE3B,IAAA,EAAY,EAAI,GAChB,EAAc,EAAK;AAGnB,UAAM,IAAK,IACP,MAAM,EACJ,sCACA;AAAA,MAAE,aAAa,GAAmB;AAAA,MAAM,WAAW;AAAA,IAAO,GAC1D,EAAE,WAAW,EAAwB,4BAA4B,CACnE,IACA,MAAM,EACJ,4BACA,EAAE,QAAQ,EAAE,sDAAsD,EAAO,EAAE,GAC3E,EAAE,WAAW,EAAwB,qBAAqB,CAC5D;AAEJ,IAAK,EAAU,YAEf,EAAY,EAAK,GAEb,IAAI,EAAkB,IACrB,EAAc,EAAI;AAAA,EACzB;AAEA,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,gBAAgB,EAAW;AAAA,IAG3B,SACE,EACG,KAAU,EAAY,oBAAqB,EAAY,aACxD,gBAAA,EAAC,UAAD,EAAA,UAAuB,EAAoB,GAA/B,SAA+B,CAC7C;AAAA,IAEF,WAAW,IAAY,EAAK,qBAAqB,IAAa,EAAK,aAAa;AAAA,IAChF,mBAAA,CAAoB,MAAS;AAC3B,MAAA,EAAS,CAAI,GACb,EAAc,EAAK;AAAA,IACrB;AAAA,IACA,kBAGE,IAAA,MACU,EAAmB,EAAwB,qCAAqC,IACtF;AAAA,IAEN,kBAAkB;AAAA,IAClB,qBAAA,MAAA;AAA2B,MAAK,EAAW;AAAA;AAAA,IAC3C,uBAAuB;AAAA,IACvB,YAAY,EAAY;AAAA,IACxB,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,mBAAmB,KAAY,CAAC;AAAA,IAChC,aAAa,EAAW;AAAA,IACxB,eAAe,IAAe,EAAW,gBAAgB;AAAA,IACzD,UACE,KAAgB,EAAK,kBACnB,gBAAA,EAAC,GAAD;AAAA,MAAO,IAAI;AAAA,QAAE,YAAY;AAAA,QAAU,eAAe;AAAA,QAAO,KAAK;AAAA,MAAE;AAAA,MAAhE,UAAA,CAEE,gBAAA,EAAC,IAAD;AAAA,QACE,KAAI;AAAA,QACJ,iBAAiB,EAAK,gBAAgB,oBAAoB;AAAA,QAC1D,MAAM;AAAA,MACP,CAAA,GACD,gBAAA,EAAC,GAAD;AAAA,QAAM,OAAM;AAAA,QAAiB,WAAW;AAAA,QAAM,SAAQ;AAAA,QACnD,UAAA;AAAA,MACG,CAAA,CACD;AAAA,IACL,CAAA,IAAA;AAAA,IAEN,OAAQ,KAAU,EAAY,cAAe,EAAY;AAAA,EAC1D,CAAA;AAEL,CAAC"}
@@ -1,16 +1,22 @@
1
- import { useAccountStore as c, useInsightsV2Store as i } from "../../common/context/hooks.es.js";
2
- import s from "../../common/components/ConnectWidgetPanel.es.js";
3
- import { useEffect as u, useRef as f } from "react";
4
- import { observer as m } from "mobx-react-lite";
5
- import { jsx as p } from "react/jsx-runtime";
6
- var v = m(function({ beat: o }) {
7
- const { actionDrawer: e } = i(), { refreshAccounts: n } = c(), t = e.isDrawerOpen && e.guid === o.guid, r = f(t);
8
- return u(() => {
9
- r.current && !t && n(), r.current = t;
10
- }, [t]), /* @__PURE__ */ p(s, { isActive: t });
1
+ import { useAccountStore as u, useInsightsV2Store as m } from "../../common/context/hooks.es.js";
2
+ import a from "../../common/components/ConnectWidgetPanel.es.js";
3
+ import { ANALYTICS_EVENTS_CONFIG as p } from "../analytics/analyticsEventsConfig.es.js";
4
+ import { useAnalyticsEvent as A } from "../analytics/useAnalyticsEvent.es.js";
5
+ import { useEffect as c, useRef as i } from "react";
6
+ import { observer as E } from "mobx-react-lite";
7
+ import { jsx as C } from "react/jsx-runtime";
8
+ var S = E(function({ beat: s }) {
9
+ const { actionDrawer: e } = m(), { refreshAccounts: f } = u(), r = A(), t = e.isDrawerOpen && e.guid === s.guid, n = i(t);
10
+ c(() => {
11
+ n.current && !t && f(), n.current = t;
12
+ }, [t]);
13
+ const o = i(!1);
14
+ return c(() => {
15
+ t && !o.current && r(p.OPENED_CONNECT_DRAWER), o.current = t;
16
+ }, [t, r]), /* @__PURE__ */ C(a, { isActive: t });
11
17
  });
12
18
  export {
13
- v as ConnectWidgetActionBlock
19
+ S as ConnectWidgetActionBlock
14
20
  };
15
21
 
16
22
  //# sourceMappingURL=ConnectWidgetActionBlock.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectWidgetActionBlock.es.js","names":[],"sources":["../../../src/insights-v2/actionBlocks/ConnectWidgetActionBlock.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { observer } from 'mobx-react-lite'\n\nimport { ConnectWidgetPanel, useAccountStore, useInsightsV2Store } from '../../common'\nimport type { ActionBlockProps } from '../mappings/blocks'\n\n/**\n * `connect_widget_block` — opens MX's Connect widget itself via the shared\n * `ConnectWidgetPanel` (src/common): SSO url via ConnectStore and the\n * iframe-based `@mxenabled/web-widget-sdk` ConnectWidget, with loading/error\n * statuses.\n *\n * Renders the panel directly (not `ConnectDrawer`, which wraps it in its own\n * `Drawer`) — `ActionBlockLayer` already supplies the action drawer's chrome\n * (title from the CTA label, close button), so a second nested `Drawer` here\n * would produce two stacked drawers with two headers and two close buttons.\n *\n * `ActionBlockLayer` does not unmount a drawer-presentation block when the\n * drawer closes — it only toggles the `Drawer`'s `isOpen` — so the account\n * refresh (parity with `ConnectDrawer`'s own `onClose`) is driven by watching\n * this beat's `actionDrawer.isDrawerOpen` transition from true to false,\n * rather than an unmount effect, which would never fire.\n */\nexport const ConnectWidgetActionBlock = observer(function ConnectWidgetActionBlock({\n beat,\n}: ActionBlockProps) {\n const { actionDrawer } = useInsightsV2Store()\n const { refreshAccounts } = useAccountStore()\n\n const isCurrentBeatOpen = actionDrawer.isDrawerOpen && actionDrawer.guid === beat.guid\n const wasOpen = useRef(isCurrentBeatOpen)\n\n useEffect(() => {\n if (wasOpen.current && !isCurrentBeatOpen) {\n void refreshAccounts()\n }\n wasOpen.current = isCurrentBeatOpen\n }, [isCurrentBeatOpen])\n\n return <ConnectWidgetPanel isActive={isCurrentBeatOpen} />\n})\n"],"mappings":";;;;;AAuBA,IAAa,IAA2B,EAAS,SAAkC,EACjF,MAAA,EAAA,GACmB;AACnB,QAAM,EAAE,cAAA,EAAA,IAAiB,EAAmB,GACtC,EAAE,iBAAA,EAAA,IAAoB,EAAgB,GAEtC,IAAoB,EAAa,gBAAgB,EAAa,SAAS,EAAK,MAC5E,IAAU,EAAO,CAAiB;AAExC,SAAA,EAAA,MAAgB;AACd,IAAI,EAAQ,WAAW,CAAC,KACtB,EAAqB,GAEvB,EAAQ,UAAU;AAAA,EACpB,GAAG,CAAC,CAAiB,CAAC,GAEf,gBAAA,EAAC,GAAD,EAAoB,UAAU,EAAoB,CAAA;AAC3D,CAAC"}
1
+ {"version":3,"file":"ConnectWidgetActionBlock.es.js","names":[],"sources":["../../../src/insights-v2/actionBlocks/ConnectWidgetActionBlock.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { observer } from 'mobx-react-lite'\n\nimport { ConnectWidgetPanel, useAccountStore, useInsightsV2Store } from '../../common'\nimport { ANALYTICS_EVENTS_CONFIG, useAnalyticsEvent } from '../analytics'\nimport type { ActionBlockProps } from '../mappings/blocks'\n\n/**\n * `connect_widget_block` — opens MX's Connect widget itself via the shared\n * `ConnectWidgetPanel` (src/common): SSO url via ConnectStore and the\n * iframe-based `@mxenabled/web-widget-sdk` ConnectWidget, with loading/error\n * statuses.\n *\n * Renders the panel directly (not `ConnectDrawer`, which wraps it in its own\n * `Drawer`) — `ActionBlockLayer` already supplies the action drawer's chrome\n * (title from the CTA label, close button), so a second nested `Drawer` here\n * would produce two stacked drawers with two headers and two close buttons.\n *\n * `ActionBlockLayer` does not unmount a drawer-presentation block when the\n * drawer closes — it only toggles the `Drawer`'s `isOpen` — so the account\n * refresh (parity with `ConnectDrawer`'s own `onClose`) is driven by watching\n * this beat's `actionDrawer.isDrawerOpen` transition from true to false,\n * rather than an unmount effect, which would never fire.\n */\nexport const ConnectWidgetActionBlock = observer(function ConnectWidgetActionBlock({\n beat,\n}: ActionBlockProps) {\n const { actionDrawer } = useInsightsV2Store()\n const { refreshAccounts } = useAccountStore()\n const fireAnalyticsEvent = useAnalyticsEvent()\n\n const isCurrentBeatOpen = actionDrawer.isDrawerOpen && actionDrawer.guid === beat.guid\n const wasOpen = useRef(isCurrentBeatOpen)\n\n useEffect(() => {\n if (wasOpen.current && !isCurrentBeatOpen) {\n void refreshAccounts()\n }\n wasOpen.current = isCurrentBeatOpen\n }, [isCurrentBeatOpen])\n\n // OPENED_CONNECT_DRAWER: the drawer actually opened (any path — CTA or otherwise). Own ref +\n // effect, separate from the refresh logic above: this one is rising-edge, so its ref starts\n // `false` — the block's first render typically happens with the drawer already open (children\n // render on open), and that first open must fire too. Fires once per open, re-arms on close.\n // Pulse parity: ConnectDiscoveredAccounts/Beat.js onPrimaryCtaClick's `opened_connect_drawer`.\n const hasFiredOpen = useRef(false)\n\n useEffect(() => {\n if (isCurrentBeatOpen && !hasFiredOpen.current) {\n fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.OPENED_CONNECT_DRAWER)\n }\n hasFiredOpen.current = isCurrentBeatOpen\n }, [isCurrentBeatOpen, fireAnalyticsEvent])\n\n return <ConnectWidgetPanel isActive={isCurrentBeatOpen} />\n})\n"],"mappings":";;;;;;;AAwBA,IAAa,IAA2B,EAAS,SAAkC,EACjF,MAAA,EAAA,GACmB;AACnB,QAAM,EAAE,cAAA,EAAA,IAAiB,EAAmB,GACtC,EAAE,iBAAA,EAAA,IAAoB,EAAgB,GACtC,IAAqB,EAAkB,GAEvC,IAAoB,EAAa,gBAAgB,EAAa,SAAS,EAAK,MAC5E,IAAU,EAAO,CAAiB;AAExC,EAAA,EAAA,MAAgB;AACd,IAAI,EAAQ,WAAW,CAAC,KACtB,EAAqB,GAEvB,EAAQ,UAAU;AAAA,EACpB,GAAG,CAAC,CAAiB,CAAC;AAOtB,QAAM,IAAe,EAAO,EAAK;AAEjC,SAAA,EAAA,MAAgB;AACd,IAAI,KAAqB,CAAC,EAAa,WACrC,EAAmB,EAAwB,qBAAqB,GAElE,EAAa,UAAU;AAAA,EACzB,GAAG,CAAC,GAAmB,CAAkB,CAAC,GAEnC,gBAAA,EAAC,GAAD,EAAoB,UAAU,EAAoB,CAAA;AAC3D,CAAC"}