@mx-cartographer/experiences 9.5.3 → 9.5.4

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 (29) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/common/components/CurrencyDialog.d.ts +10 -2
  3. package/dist/common/components/CurrencyDialog.es.js +56 -50
  4. package/dist/common/components/CurrencyDialog.es.js.map +1 -1
  5. package/dist/core/types/localization/InsightsFeedCopy.d.ts +21 -0
  6. package/dist/core/types/localization/index.d.ts +1 -1
  7. package/dist/insights-v2/actionBlocks/ActionBlockLayer.es.js +31 -25
  8. package/dist/insights-v2/actionBlocks/ActionBlockLayer.es.js.map +1 -1
  9. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.d.ts +13 -0
  10. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js +128 -0
  11. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js.map +1 -0
  12. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/index.d.ts +1 -0
  13. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/layout.d.ts +14 -0
  14. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/layout.es.js +14 -0
  15. package/dist/insights-v2/actionBlocks/AmountInputModalBlock/layout.es.js.map +1 -0
  16. package/dist/insights-v2/components/BaseLayout.es.js +22 -22
  17. package/dist/insights-v2/ctaActions/runCtaAction.es.js +10 -10
  18. package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
  19. package/dist/insights-v2/index.es.js +17 -17
  20. package/dist/insights-v2/mappings/actionBlocksKey.d.ts +1 -0
  21. package/dist/insights-v2/mappings/actionBlocksKey.es.js +2 -1
  22. package/dist/insights-v2/mappings/actionBlocksKey.es.js.map +1 -1
  23. package/dist/insights-v2/mappings/blocks.d.ts +2 -1
  24. package/dist/insights-v2/mappings/blocks.es.js +62 -62
  25. package/dist/insights-v2/mappings/blocks.es.js.map +1 -1
  26. package/dist/insights-v2/mappings/hasDrawerBlock.d.ts +1 -0
  27. package/dist/insights-v2/mappings/hasDrawerBlock.es.js +13 -0
  28. package/dist/insights-v2/mappings/hasDrawerBlock.es.js.map +1 -0
  29. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [9.5.4] - 10-09-2026
2
+
3
+ - **ADDED** - `amount_input_modal_block`, the first modal action block: edits one whole-dollar setting in a dialog, picked by the CTA variant. `low_balance_threshold` (Low Account Balance "Edit Alert Settings") saves the threshold on the account's low-balance alert profile; `safety_net` (Savings Opportunity V2 "Set Reminder" / "Adjust Reminder") saves the safety net on the user insight profile. Saves run through `useCtaHandler`. Copy under `insights_feed.amount_input_modal`; mock beats, stories and `docs/action-blocks/amount-input-modal-block.md`.
4
+ - **ADDED** - `ActionBlockEntry.presentation: 'modal'`: `ActionBlockLayer` renders such a block without the drawer, and only while it is open for its beat.
5
+ - **ADDED** - Optional `CurrencyDialog` props `subtitle`, `errorText` and `isPrimaryDisabled`; `content` now accepts a node. Its helper text (validation or save error) is now announced with `role="alert"`. Existing callers are otherwise unchanged.
6
+ - **CHANGED** - `hasDrawerBlock` moved to `insights-v2/mappings/hasDrawerBlock.ts` (still re-exported from `mappings/blocks`) so action blocks can import `ctaActions` without an import cycle.
7
+
1
8
  ## [9.5.3] - 10-09-2026
2
9
 
3
10
  - **CHANGED** - Repeating-transaction create flows show Sherlock-ineligible rows disabled instead of hiding them; `SelectTransaction` drives `TransactionList`'s new `isRowDisabled` prop instead of `filter` (MEW-4143)
@@ -1,10 +1,16 @@
1
+ import { ReactNode } from 'react';
1
2
  import { ButtonProps } from '@mui/material/Button';
2
3
  interface CurrencyDialogProps {
3
4
  closeAriaLabel: string;
4
- content?: string;
5
+ /** Body copy; a node so callers can emphasise part of it (e.g. a bold account name). */
6
+ content?: ReactNode;
7
+ /** Validation or save error, shown under the input and marking it invalid. */
8
+ errorText?: string;
5
9
  inputLabel?: string;
6
10
  inputValue?: string;
7
11
  isOpen: boolean;
12
+ /** Disables the primary button on top of the built-in input check (e.g. while saving). */
13
+ isPrimaryDisabled?: boolean;
8
14
  handleInputChange?: (value: string) => void;
9
15
  handleModalClose: () => void;
10
16
  handlePrimaryAction: () => void;
@@ -13,7 +19,9 @@ interface CurrencyDialogProps {
13
19
  primaryText: string;
14
20
  secondaryColor?: ButtonProps['color'];
15
21
  secondaryText?: string;
22
+ /** A line under the title, e.g. the account a setting applies to. */
23
+ subtitle?: ReactNode;
16
24
  title: string;
17
25
  }
18
- declare const CurrencyDialog: ({ closeAriaLabel, content, inputLabel, inputValue, isOpen, handleInputChange, handleModalClose, handlePrimaryAction, handleSecondaryAction, primaryColor, primaryText, secondaryColor, secondaryText, title, }: CurrencyDialogProps) => import("react/jsx-runtime").JSX.Element;
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;
19
27
  export default CurrencyDialog;
@@ -1,34 +1,34 @@
1
- import { useScreenSize as v } from "../hooks/useScreenSize.es.js";
2
- import p from "@mui/material/Stack";
3
- import { H3 as I, P as y } from "@mxenabled/mxui";
4
- import c from "@mui/material/Button";
5
- import { Icon as S } from "@mxenabled/mx-icons";
6
- import { jsx as o, jsxs as e } from "react/jsx-runtime";
7
- import j from "@mui/material/Dialog";
8
- import k from "@mui/material/DialogContent";
9
- import M from "@mui/material/IconButton";
10
- import $ from "@mui/material/DialogActions";
11
- import F from "@mui/material/InputAdornment";
12
- import w from "@mui/material/TextField";
13
- var O = ({ closeAriaLabel: d, content: f, inputLabel: t, inputValue: i, isOpen: h, handleInputChange: u, handleModalClose: n, handlePrimaryAction: a, handleSecondaryAction: g, primaryColor: x = "primary", primaryText: m, secondaryColor: b = "primary", secondaryText: l, title: s }) => {
14
- const C = i !== "" && i !== "0" && !isNaN(parseFloat(i ?? "0")), { isSmallMobile: D } = v();
15
- return /* @__PURE__ */ o(j, {
16
- "aria-label": s,
1
+ import { useScreenSize as k } from "../hooks/useScreenSize.es.js";
2
+ import n from "@mui/material/Stack";
3
+ import { H3 as S, P as j } from "@mxenabled/mxui";
4
+ import f from "@mui/material/Button";
5
+ import { Icon as M } from "@mxenabled/mx-icons";
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($, {
16
+ "aria-label": c,
17
17
  "aria-labelledby": "modal-title",
18
18
  maxWidth: "xs",
19
- onClose: n,
20
- open: h,
19
+ onClose: m,
20
+ open: u,
21
21
  role: "dialog",
22
22
  sx: {
23
23
  "& .MuiDialogContent-root": { p: 0 },
24
- ...D && { "& .MuiDialog-paper": { minWidth: "100%" } }
24
+ ...I && { "& .MuiDialog-paper": { minWidth: "100%" } }
25
25
  },
26
- children: /* @__PURE__ */ o(k, { children: /* @__PURE__ */ e("form", {
27
- onSubmit: (r) => {
28
- r.preventDefault(), a();
26
+ children: /* @__PURE__ */ o(B, { children: /* @__PURE__ */ t("form", {
27
+ onSubmit: (e) => {
28
+ e.preventDefault(), d || s();
29
29
  },
30
30
  children: [
31
- /* @__PURE__ */ e(p, {
31
+ /* @__PURE__ */ t(n, {
32
32
  sx: {
33
33
  alignItems: "center",
34
34
  flexDirection: "row",
@@ -39,46 +39,52 @@ var O = ({ closeAriaLabel: d, content: f, inputLabel: t, inputValue: i, isOpen:
39
39
  pr: 16,
40
40
  pt: 24
41
41
  },
42
- children: [/* @__PURE__ */ o(I, {
43
- id: "modal-title",
44
- children: s
45
- }), /* @__PURE__ */ o(M, {
46
- "aria-label": d,
47
- onClick: n,
42
+ children: [/* @__PURE__ */ t(n, {
43
+ sx: { gap: 4 },
44
+ children: [/* @__PURE__ */ o(S, {
45
+ id: "modal-title",
46
+ children: c
47
+ }), y]
48
+ }), /* @__PURE__ */ o(F, {
49
+ "aria-label": h,
50
+ onClick: m,
48
51
  sx: {
49
52
  minHeight: 24,
50
53
  position: "absolute",
51
54
  right: 8,
52
55
  top: 18
53
56
  },
54
- children: /* @__PURE__ */ o(S, {
57
+ children: /* @__PURE__ */ o(M, {
55
58
  name: "close",
56
59
  sx: { color: "action.active" }
57
60
  })
58
61
  })]
59
62
  }),
60
- /* @__PURE__ */ e(p, {
63
+ /* @__PURE__ */ t(n, {
61
64
  sx: {
62
65
  gap: 24,
63
66
  px: 24
64
67
  },
65
- children: [/* @__PURE__ */ o(y, {
68
+ children: [/* @__PURE__ */ o(j, {
66
69
  variant: "subtitle1",
67
- children: f
68
- }), t && /* @__PURE__ */ o(w, {
70
+ children: g
71
+ }), r && /* @__PURE__ */ o(A, {
69
72
  autoFocus: !0,
73
+ error: !!a,
74
+ helperText: a,
70
75
  inputMode: "decimal",
71
- label: t,
72
- onChange: ({ target: { value: r } }) => {
73
- /^\d*\.?\d*$/.test(r) && u?.(r);
76
+ label: r,
77
+ onChange: ({ target: { value: e } }) => {
78
+ /^\d*\.?\d*$/.test(e) && b?.(e);
74
79
  },
75
80
  slotProps: {
76
- input: { startAdornment: /* @__PURE__ */ o(F, {
81
+ input: { startAdornment: /* @__PURE__ */ o(w, {
77
82
  position: "start",
78
83
  children: "$"
79
84
  }) },
85
+ formHelperText: { role: "alert" },
80
86
  htmlInput: {
81
- "aria-label": t,
87
+ "aria-label": r,
82
88
  noValidate: !0,
83
89
  pattern: "[0-9]*\\.?[0-9]*"
84
90
  }
@@ -87,25 +93,25 @@ var O = ({ closeAriaLabel: d, content: f, inputLabel: t, inputValue: i, isOpen:
87
93
  value: i
88
94
  })]
89
95
  }),
90
- /* @__PURE__ */ e($, {
96
+ /* @__PURE__ */ t(H, {
91
97
  sx: {
92
98
  justifyContent: "flex-end",
93
99
  gap: 16,
94
100
  p: 24,
95
101
  ">:not(style)~:not(style)": { ml: 0 }
96
102
  },
97
- children: [l && /* @__PURE__ */ o(c, {
103
+ children: [l && /* @__PURE__ */ o(f, {
98
104
  "aria-label": l,
99
- color: b,
100
- onClick: g,
105
+ color: v,
106
+ onClick: C,
101
107
  children: l
102
- }), /* @__PURE__ */ o(c, {
103
- "aria-label": m,
104
- color: x,
105
- disabled: t ? !C : !1,
106
- onClick: a,
108
+ }), /* @__PURE__ */ o(f, {
109
+ "aria-label": p,
110
+ color: D,
111
+ disabled: d,
112
+ onClick: s,
107
113
  variant: "contained",
108
- children: m
114
+ children: p
109
115
  })]
110
116
  })
111
117
  ]
@@ -113,7 +119,7 @@ var O = ({ closeAriaLabel: d, content: f, inputLabel: t, inputValue: i, isOpen:
113
119
  });
114
120
  };
115
121
  export {
116
- O as default
122
+ U as default
117
123
  };
118
124
 
119
125
  //# sourceMappingURL=CurrencyDialog.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyDialog.es.js","names":[],"sources":["../../../src/common/components/CurrencyDialog.tsx"],"sourcesContent":["import { 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 content?: string\n inputLabel?: string\n inputValue?: string\n isOpen: 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 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 inputLabel,\n inputValue,\n isOpen,\n handleInputChange,\n handleModalClose,\n handlePrimaryAction,\n handleSecondaryAction,\n primaryColor = 'primary',\n primaryText,\n secondaryColor = 'primary',\n secondaryText,\n title,\n}: CurrencyDialogProps) => {\n const numericValue = parseFloat(inputValue ?? '0')\n const isValid = inputValue !== '' && inputValue !== '0' && !isNaN(numericValue)\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 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 <H3 id=\"modal-title\">{title}</H3>\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 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 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={inputLabel ? !isValid : false}\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 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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"}
@@ -145,6 +145,25 @@ export interface RemainingCashCopy {
145
145
  other_expenses: string;
146
146
  other_expenses_one: string;
147
147
  }
148
+ /** Copy for one variant of `amount_input_modal_block` (strings with `_1{account_name}` take the account). */
149
+ export interface AmountInputModalVariantCopy {
150
+ /** Body copy; `_1{account_name}` renders bold. */
151
+ description: string;
152
+ /** Body copy once a value is already saved; falls back to `description`. */
153
+ description_edit?: string;
154
+ input_label: string;
155
+ title: string;
156
+ /** Title once a value is already saved; falls back to `title`. */
157
+ title_edit?: string;
158
+ }
159
+ export interface AmountInputModalCopy {
160
+ low_balance_threshold: AmountInputModalVariantCopy;
161
+ safety_net: AmountInputModalVariantCopy;
162
+ /** Validation message for anything but a positive whole-dollar amount. */
163
+ whole_number_error: string;
164
+ /** Shown in the dialog when the save request fails; the value stays so Save can be retried. */
165
+ save_error: string;
166
+ }
148
167
  export interface InsightsFeedCopy {
149
168
  general: GeneralInsightsFeedCopy;
150
169
  settings_menu: SettingsMenuCopy;
@@ -158,5 +177,7 @@ export interface InsightsFeedCopy {
158
177
  feedback: FeedbackCopy;
159
178
  feedback_bar: FeedbackBarCopy;
160
179
  dismiss: DismissInsightCopy;
180
+ /** Optional until the localized-content service ships the namespace. */
181
+ amount_input_modal?: AmountInputModalCopy;
161
182
  }
162
183
  export {};
@@ -13,7 +13,7 @@ export type { DebtsCopy } from './DebtsCopy';
13
13
  export type { FinstrongCopy } from './FinstrongCopy';
14
14
  export type { GoalsCopy } from './GoalsCopy';
15
15
  export type { HelpCopy, HelpByCategoryList } from './HelpCopy';
16
- export type { InsightsFeedCopy, MakeTransferCopy, RemainingCashCopy, OverflowMenuCopy, DismissInsightCopy, DismissInsightOptionCopy, HiddenConfirmationCopy, HideFeedbackCopy, FeedbackCopy, FeedbackBarCopy, } from './InsightsFeedCopy';
16
+ export type { AmountInputModalCopy, AmountInputModalVariantCopy, InsightsFeedCopy, MakeTransferCopy, RemainingCashCopy, OverflowMenuCopy, DismissInsightCopy, DismissInsightOptionCopy, HiddenConfirmationCopy, HideFeedbackCopy, FeedbackCopy, FeedbackBarCopy, } from './InsightsFeedCopy';
17
17
  export type { InvestmentsCopy } from './InvestmentsCopy';
18
18
  export type { MicroInsightsCopy } from './MicroInsightsCopy';
19
19
  export type { NetWorthCopy } from './NetWorthCopy';
@@ -1,37 +1,43 @@
1
- import { useGlobalCopyStore as k, useGlobalUiStore as C, useInsightsV2Store as h } from "../../common/context/hooks.es.js";
2
- import B from "../../common/components/drawer/Drawer.es.js";
3
- import _ from "../../common/components/Loader.es.js";
4
- import { getDynamicCtaBasedOnTriggers as b } from "./util.es.js";
5
- import { actionBlocks as v, hasDrawerBlock as w } from "../mappings/blocks.es.js";
6
- import { observer as D } from "mobx-react-lite";
7
- import L from "@mui/material/Stack";
8
- import { Text as x } from "@mxenabled/mxui";
1
+ import { useGlobalCopyStore as C, useGlobalUiStore as h, useInsightsV2Store as B } from "../../common/context/hooks.es.js";
2
+ import _ from "../../common/components/drawer/Drawer.es.js";
3
+ import b from "../../common/components/Loader.es.js";
4
+ import { getDynamicCtaBasedOnTriggers as v } from "./util.es.js";
5
+ import { hasDrawerBlock as w } from "../mappings/hasDrawerBlock.es.js";
6
+ import { actionBlocks as D } from "../mappings/blocks.es.js";
7
+ import { observer as L } from "mobx-react-lite";
8
+ import x from "@mui/material/Stack";
9
+ import { Text as A } from "@mxenabled/mxui";
9
10
  import { jsx as e } from "react/jsx-runtime";
10
- var A = ({ actionBlock: s, ...o }) => {
11
- const { Component: n, usePrepare: t } = s;
12
- return t(o), /* @__PURE__ */ e(n, { ...o });
13
- }, $ = D(function({ beat: o }) {
14
- const n = k(), t = n.common, { isCopyLoaded: m } = C(), { actionDrawer: c, closeActionDrawer: p } = h();
11
+ var m = ({ actionBlock: s, ...o }) => {
12
+ const { Component: i, usePrepare: n } = s;
13
+ return n(o), /* @__PURE__ */ e(i, { ...o });
14
+ }, q = L(function({ beat: o }) {
15
+ const i = C(), n = i.common, { isCopyLoaded: p } = h(), { actionDrawer: c, closeActionDrawer: u } = B();
15
16
  if (!o.insights_payload) return null;
16
- const { primary_cta: d, secondary_cta: u, guid: f } = o.insights_payload, l = c.isDrawerOpen && c.guid === f;
17
- if (!m) return l ? /* @__PURE__ */ e(_, {}) : null;
18
- const r = b(c, d, u), a = w(r?.block), i = a ? v[r?.block] : void 0, y = i?.title?.(r?.variant, n, o), g = i ? y || r?.label || "" : a ? t.error_heading : r?.label ?? "";
19
- return /* @__PURE__ */ e(B, {
20
- ariaLabelClose: t.close_aria,
21
- isOpen: l,
22
- onClose: p,
17
+ const { primary_cta: d, secondary_cta: f, guid: k } = o.insights_payload, a = c.isDrawerOpen && c.guid === k;
18
+ if (!p) return a ? /* @__PURE__ */ e(b, {}) : null;
19
+ const r = v(c, d, f), l = w(r?.block), t = l ? D[r?.block] : void 0;
20
+ if (t?.presentation === "modal") return a ? /* @__PURE__ */ e(m, {
21
+ actionBlock: t,
22
+ beat: o
23
+ }, r?.block) : null;
24
+ const y = t?.title?.(r?.variant, i, o), g = t ? y || r?.label || "" : l ? n.error_heading : r?.label ?? "";
25
+ return /* @__PURE__ */ e(_, {
26
+ ariaLabelClose: n.close_aria,
27
+ isOpen: a,
28
+ onClose: u,
23
29
  title: g,
24
- children: i ? /* @__PURE__ */ e(A, {
25
- actionBlock: i,
30
+ children: t ? /* @__PURE__ */ e(m, {
31
+ actionBlock: t,
26
32
  beat: o
27
- }, r?.block) : a ? /* @__PURE__ */ e(L, {
33
+ }, r?.block) : l ? /* @__PURE__ */ e(x, {
28
34
  sx: { p: 24 },
29
- children: /* @__PURE__ */ e(x, { children: t.error_message })
35
+ children: /* @__PURE__ */ e(A, { children: n.error_message })
30
36
  }) : null
31
37
  });
32
38
  });
33
39
  export {
34
- $ as ActionBlockLayer
40
+ q as ActionBlockLayer
35
41
  };
36
42
 
37
43
  //# sourceMappingURL=ActionBlockLayer.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBlockLayer.es.js","names":[],"sources":["../../../src/insights-v2/actionBlocks/ActionBlockLayer.tsx"],"sourcesContent":["import Stack from '@mui/material/Stack'\n\nimport { Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n Drawer,\n Loader,\n useGlobalCopyStore,\n useGlobalUiStore,\n useInsightsV2Store,\n} from '../../common'\nimport { type Beat } from '../../core'\nimport {\n actionBlocks,\n hasDrawerBlock,\n type ActionBlockEntry,\n type ActionBlockProps,\n} from '../mappings/blocks'\nimport { getDynamicCtaBasedOnTriggers } from './util'\n\ninterface ActionBlockLayerProps {\n beat: Beat\n}\n\n/**\n * Renders a registered action block. Callers MUST key this per block name — a\n * block with `usePrepare` and one without run a different number of hooks, so\n * reusing the same fiber across a block switch violates the rules of hooks.\n */\nconst ActionBlockRunner = ({\n actionBlock,\n ...blockProps\n}: ActionBlockProps & { actionBlock: ActionBlockEntry }) => {\n const { Component, usePrepare } = actionBlock\n\n usePrepare(blockProps)\n\n return <Component {...blockProps} />\n}\n\n// `observer` is required: this reads `isCopyLoaded` off GlobalUiStore and the\n// action drawer off InsightsV2Store. Without it the component never re-renders\n// and stays stuck on <Loader /> forever.\nexport const ActionBlockLayer = observer(function ActionBlockLayer({\n beat,\n}: ActionBlockLayerProps) {\n const copy = useGlobalCopyStore()\n const commonCopy = copy.common\n const { isCopyLoaded } = useGlobalUiStore()\n const { actionDrawer, closeActionDrawer } = useInsightsV2Store()\n\n // Mirrors BaseLayout's guard — the layer is exported, so callers are not\n // guaranteed to have narrowed this already.\n if (!beat.insights_payload) return null\n\n const { primary_cta, secondary_cta, guid } = beat.insights_payload\n\n // Every card in the feed renders its own layer against the shared store, so\n // this card's drawer only opens when its beat is the one that triggered it.\n const isCurrentBeatOpen = actionDrawer.isDrawerOpen && actionDrawer.guid === guid\n\n if (!isCopyLoaded) return isCurrentBeatOpen ? <Loader /> : null\n\n const currentCta = getDynamicCtaBasedOnTriggers(actionDrawer, primary_cta, secondary_cta)\n\n // A CTA without a real block never opens a drawer (BaseLayout won't trigger it);\n // a real-but-unknown block shows the error (\"oops\") content.\n const blockValid = hasDrawerBlock(currentCta?.block)\n const actionBlock = blockValid\n ? actionBlocks[currentCta?.block as keyof typeof actionBlocks]\n : undefined\n\n const resolvedTitle = actionBlock?.title?.(currentCta?.variant, copy, beat)\n const drawerTitle = actionBlock\n ? resolvedTitle || currentCta?.label || ''\n : blockValid\n ? commonCopy.error_heading\n : (currentCta?.label ?? '')\n\n return (\n <Drawer\n ariaLabelClose={commonCopy.close_aria}\n isOpen={isCurrentBeatOpen}\n onClose={closeActionDrawer}\n title={drawerTitle}\n >\n {actionBlock ? (\n <ActionBlockRunner actionBlock={actionBlock} beat={beat} key={currentCta?.block} />\n ) : blockValid ? (\n <Stack sx={{ p: 24 }}>\n <Text>{commonCopy.error_message}</Text>\n </Stack>\n ) : null}\n </Drawer>\n )\n})\n"],"mappings":";;;;;;;;;AA8BA,IAAM,IAAA,CAAqB,EACzB,aAAA,GACA,GAAG,EAAA,MACuD;AAC1D,QAAM,EAAE,WAAA,GAAW,YAAA,EAAA,IAAe;AAElC,SAAA,EAAW,CAAU,GAEd,gBAAA,EAAC,GAAD,EAAW,GAAI,EAAa,CAAA;AACrC,GAKa,IAAmB,EAAS,SAA0B,EACjE,MAAA,EAAA,GACwB;AACxB,QAAM,IAAO,EAAmB,GAC1B,IAAa,EAAK,QAClB,EAAE,cAAA,EAAA,IAAiB,EAAiB,GACpC,EAAE,cAAA,GAAc,mBAAA,EAAA,IAAsB,EAAmB;AAI/D,MAAI,CAAC,EAAK,iBAAkB,QAAO;AAEnC,QAAM,EAAE,aAAA,GAAa,eAAA,GAAe,MAAA,EAAA,IAAS,EAAK,kBAI5C,IAAoB,EAAa,gBAAgB,EAAa,SAAS;AAE7E,MAAI,CAAC,EAAc,QAAO,IAAoB,gBAAA,EAAC,GAAD,CAAS,CAAA,IAAI;AAE3D,QAAM,IAAa,EAA6B,GAAc,GAAa,CAAa,GAIlF,IAAa,EAAe,GAAY,KAAK,GAC7C,IAAc,IAChB,EAAa,GAAY,KAAA,IACzB,QAEE,IAAgB,GAAa,QAAQ,GAAY,SAAS,GAAM,CAAI,GACpE,IAAc,IAChB,KAAiB,GAAY,SAAS,KACtC,IACE,EAAW,gBACV,GAAY,SAAS;AAE5B,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,gBAAgB,EAAW;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,IAEN,UAAA,IACC,gBAAA,EAAC,GAAD;AAAA,MAAgC,aAAA;AAAA,MAAmB,MAAA;AAAA,IAA+B,GAApB,GAAY,KAAQ,IAChF,IACF,gBAAA,EAAC,GAAD;AAAA,MAAO,IAAI,EAAE,GAAG,GAAG;AAAA,MACjB,UAAA,gBAAA,EAAC,GAAD,EAAA,UAAO,EAAW,cAAoB,CAAA;AAAA,IACjC,CAAA,IACL;AAAA,EACE,CAAA;AAEZ,CAAC"}
1
+ {"version":3,"file":"ActionBlockLayer.es.js","names":[],"sources":["../../../src/insights-v2/actionBlocks/ActionBlockLayer.tsx"],"sourcesContent":["import Stack from '@mui/material/Stack'\n\nimport { Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n Drawer,\n Loader,\n useGlobalCopyStore,\n useGlobalUiStore,\n useInsightsV2Store,\n} from '../../common'\nimport { type Beat } from '../../core'\nimport {\n actionBlocks,\n hasDrawerBlock,\n type ActionBlockEntry,\n type ActionBlockProps,\n} from '../mappings/blocks'\nimport { getDynamicCtaBasedOnTriggers } from './util'\n\ninterface ActionBlockLayerProps {\n beat: Beat\n}\n\n/**\n * Renders a registered action block. Callers MUST key this per block name — a\n * block with `usePrepare` and one without run a different number of hooks, so\n * reusing the same fiber across a block switch violates the rules of hooks.\n */\nconst ActionBlockRunner = ({\n actionBlock,\n ...blockProps\n}: ActionBlockProps & { actionBlock: ActionBlockEntry }) => {\n const { Component, usePrepare } = actionBlock\n\n usePrepare(blockProps)\n\n return <Component {...blockProps} />\n}\n\n// `observer` is required: this reads `isCopyLoaded` off GlobalUiStore and the\n// action drawer off InsightsV2Store. Without it the component never re-renders\n// and stays stuck on <Loader /> forever.\nexport const ActionBlockLayer = observer(function ActionBlockLayer({\n beat,\n}: ActionBlockLayerProps) {\n const copy = useGlobalCopyStore()\n const commonCopy = copy.common\n const { isCopyLoaded } = useGlobalUiStore()\n const { actionDrawer, closeActionDrawer } = useInsightsV2Store()\n\n // Mirrors BaseLayout's guard — the layer is exported, so callers are not\n // guaranteed to have narrowed this already.\n if (!beat.insights_payload) return null\n\n const { primary_cta, secondary_cta, guid } = beat.insights_payload\n\n // Every card in the feed renders its own layer against the shared store, so\n // this card's drawer only opens when its beat is the one that triggered it.\n const isCurrentBeatOpen = actionDrawer.isDrawerOpen && actionDrawer.guid === guid\n\n if (!isCopyLoaded) return isCurrentBeatOpen ? <Loader /> : null\n\n const currentCta = getDynamicCtaBasedOnTriggers(actionDrawer, primary_cta, secondary_cta)\n\n // A CTA without a real block never opens a drawer (BaseLayout won't trigger it);\n // a real-but-unknown block shows the error (\"oops\") content.\n const blockValid = hasDrawerBlock(currentCta?.block)\n const actionBlock = blockValid\n ? actionBlocks[currentCta?.block as keyof typeof actionBlocks]\n : undefined\n\n // A modal block owns its dialog: no drawer behind it, and nothing mounted until it is opened.\n if (actionBlock?.presentation === 'modal') {\n return isCurrentBeatOpen ? (\n <ActionBlockRunner actionBlock={actionBlock} beat={beat} key={currentCta?.block} />\n ) : null\n }\n\n const resolvedTitle = actionBlock?.title?.(currentCta?.variant, copy, beat)\n const drawerTitle = actionBlock\n ? resolvedTitle || currentCta?.label || ''\n : blockValid\n ? commonCopy.error_heading\n : (currentCta?.label ?? '')\n\n return (\n <Drawer\n ariaLabelClose={commonCopy.close_aria}\n isOpen={isCurrentBeatOpen}\n onClose={closeActionDrawer}\n title={drawerTitle}\n >\n {actionBlock ? (\n <ActionBlockRunner actionBlock={actionBlock} beat={beat} key={currentCta?.block} />\n ) : blockValid ? (\n <Stack sx={{ p: 24 }}>\n <Text>{commonCopy.error_message}</Text>\n </Stack>\n ) : null}\n </Drawer>\n )\n})\n"],"mappings":";;;;;;;;;;AA8BA,IAAM,IAAA,CAAqB,EACzB,aAAA,GACA,GAAG,EAAA,MACuD;AAC1D,QAAM,EAAE,WAAA,GAAW,YAAA,EAAA,IAAe;AAElC,SAAA,EAAW,CAAU,GAEd,gBAAA,EAAC,GAAD,EAAW,GAAI,EAAa,CAAA;AACrC,GAKa,IAAmB,EAAS,SAA0B,EACjE,MAAA,EAAA,GACwB;AACxB,QAAM,IAAO,EAAmB,GAC1B,IAAa,EAAK,QAClB,EAAE,cAAA,EAAA,IAAiB,EAAiB,GACpC,EAAE,cAAA,GAAc,mBAAA,EAAA,IAAsB,EAAmB;AAI/D,MAAI,CAAC,EAAK,iBAAkB,QAAO;AAEnC,QAAM,EAAE,aAAA,GAAa,eAAA,GAAe,MAAA,EAAA,IAAS,EAAK,kBAI5C,IAAoB,EAAa,gBAAgB,EAAa,SAAS;AAE7E,MAAI,CAAC,EAAc,QAAO,IAAoB,gBAAA,EAAC,GAAD,CAAS,CAAA,IAAI;AAE3D,QAAM,IAAa,EAA6B,GAAc,GAAa,CAAa,GAIlF,IAAa,EAAe,GAAY,KAAK,GAC7C,IAAc,IAChB,EAAa,GAAY,KAAA,IACzB;AAGJ,MAAI,GAAa,iBAAiB,QAChC,QAAO,IACL,gBAAA,EAAC,GAAD;AAAA,IAAgC,aAAA;AAAA,IAAmB,MAAA;AAAA,EAA+B,GAApB,GAAY,KAAQ,IAChF;AAGN,QAAM,IAAgB,GAAa,QAAQ,GAAY,SAAS,GAAM,CAAI,GACpE,IAAc,IAChB,KAAiB,GAAY,SAAS,KACtC,IACE,EAAW,gBACV,GAAY,SAAS;AAE5B,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,gBAAgB,EAAW;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,IAEN,UAAA,IACC,gBAAA,EAAC,GAAD;AAAA,MAAgC,aAAA;AAAA,MAAmB,MAAA;AAAA,IAA+B,GAApB,GAAY,KAAQ,IAChF,IACF,gBAAA,EAAC,GAAD;AAAA,MAAO,IAAI,EAAE,GAAG,GAAG;AAAA,MACjB,UAAA,gBAAA,EAAC,GAAD,EAAA,UAAO,EAAW,cAAoB,CAAA;AAAA,IACjC,CAAA,IACL;AAAA,EACE,CAAA;AAEZ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { ActionBlockProps } from '../../mappings/blocks';
2
+ /**
3
+ * `amount_input_modal_block`: one whole-dollar setting edited in a dialog (Figma 441:24913 and
4
+ * 5402:34137). The CTA's variant picks what is edited:
5
+ * - `low_balance_threshold`: the `threshold` on the beat account's low-balance alert profile;
6
+ * - `safety_net`: `savings_opportunity_minimum_checking_account_balance` on the insight profile.
7
+ *
8
+ * Registered with `presentation: 'modal'`, so no drawer opens behind it; closing the dialog ends
9
+ * the action-drawer session. `observer` is required: the starting values are read from stores.
10
+ */
11
+ export declare const AmountInputModalBlock: (({ beat, }: ActionBlockProps) => import("react/jsx-runtime").JSX.Element) & {
12
+ displayName: string;
13
+ };
@@ -0,0 +1,128 @@
1
+ import { buildDynamicCopy as F } from "../../../core/utils/Localization.es.js";
2
+ import G from "../../../common/utils/AccountDisplayName.es.js";
3
+ import { useGlobalCopyStore as W, useInsightsV2Store as j, useSettingsStore as z, useUserStore as q } from "../../../common/context/hooks.es.js";
4
+ import J from "../../../common/components/Loader.es.js";
5
+ import K from "../../../common/components/CurrencyDialog.es.js";
6
+ import { useBeatActions as Q } from "../../beatActions.es.js";
7
+ import { getDynamicCtaBasedOnTriggers as X } from "../util.es.js";
8
+ import { useCtaHandler as Y } from "../../ctaActions/useCtaAction.es.js";
9
+ import { AMOUNT_INPUT_VARIANTS as Z, findLowBalanceProfile as oo, isAmountInputVariant as L, isWholeAmount as to, toInputValue as eo } from "./layout.es.js";
10
+ import r from "react";
11
+ import { observer as io } from "mobx-react-lite";
12
+ import T from "@mui/material/Stack";
13
+ import { H3 as no, InstitutionLogo as ro, Text as P } from "@mxenabled/mxui";
14
+ import { Icon as ao } from "@mxenabled/mx-icons";
15
+ import { jsx as e, jsxs as k } from "react/jsx-runtime";
16
+ import so from "@mui/material/Dialog";
17
+ import co from "@mui/material/IconButton";
18
+ var D = ({ children: y, closeAriaLabel: i, heading: o, onClose: l }) => {
19
+ const u = r.useId();
20
+ return /* @__PURE__ */ e(so, {
21
+ "aria-labelledby": o ? u : void 0,
22
+ maxWidth: "xs",
23
+ onClose: l,
24
+ open: !0,
25
+ children: /* @__PURE__ */ k(T, {
26
+ sx: {
27
+ gap: 12,
28
+ p: 24,
29
+ position: "relative"
30
+ },
31
+ children: [
32
+ o && /* @__PURE__ */ e(no, {
33
+ id: u,
34
+ children: o
35
+ }),
36
+ /* @__PURE__ */ e(co, {
37
+ "aria-label": i || "Close",
38
+ onClick: l,
39
+ sx: {
40
+ position: "absolute",
41
+ right: 8,
42
+ top: 18
43
+ },
44
+ children: /* @__PURE__ */ e(ao, {
45
+ name: "close",
46
+ sx: { color: "action.active" }
47
+ })
48
+ }),
49
+ y
50
+ ]
51
+ })
52
+ });
53
+ }, Lo = io(function({ beat: i }) {
54
+ const { common: o, insights_feed: l } = W(), { actionDrawer: u, closeActionDrawer: c } = j(), f = z(), { userInsightProfile: v } = q(), b = Y(i, Q()), { primary_cta: B, secondary_cta: N } = i.insights_payload ?? {}, s = X(u, B, N)?.variant, a = l?.amount_input_modal, t = s === Z.LOW_BALANCE_THRESHOLD, _ = t ? oo(f.notificationProfiles, i.primary_account_guid) : void 0, I = t ? _?.threshold : v?.savings_opportunity_minimum_checking_account_balance, [M, V] = r.useState(t), m = r.useRef(!0);
55
+ r.useEffect(() => (m.current = !0, () => {
56
+ m.current = !1;
57
+ }), []), r.useEffect(() => {
58
+ t && f.loadNotificationProfiles().finally(() => {
59
+ m.current && V(!1);
60
+ });
61
+ }, [t, f]);
62
+ const [E, H] = r.useState(null), [S, A] = r.useState(!1), [O, h] = r.useState(!1), d = E ?? eo(I);
63
+ if (M) return /* @__PURE__ */ e(D, {
64
+ closeAriaLabel: o?.close_aria,
65
+ onClose: c,
66
+ children: /* @__PURE__ */ e(J, {})
67
+ });
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, {
71
+ closeAriaLabel: o?.close_aria,
72
+ heading: o?.error_heading,
73
+ onClose: c,
74
+ children: /* @__PURE__ */ e(P, { children: o?.error_message })
75
+ });
76
+ const C = !t && !!I, w = i.primary_account ? G(i.primary_account) : "", g = to(d), R = d !== "" && !g, U = async () => {
77
+ if (!g || S) return;
78
+ const p = Number(d);
79
+ A(!0), h(!1);
80
+ const $ = t ? await b("updateNotificationProfileThreshold", {
81
+ profileGuid: _?.guid,
82
+ threshold: p
83
+ }) : await b("updateUserInsightProfile", { fields: { savings_opportunity_minimum_checking_account_balance: p } });
84
+ m.current && (A(!1), $ ? c() : h(!0));
85
+ };
86
+ return /* @__PURE__ */ e(K, {
87
+ closeAriaLabel: o.close_aria,
88
+ content: F(C && n.description_edit || n.description, /* @__PURE__ */ e("strong", { children: w }, "account")),
89
+ errorText: R ? a.whole_number_error : O ? a.save_error : void 0,
90
+ handleInputChange: (p) => {
91
+ H(p), h(!1);
92
+ },
93
+ handleModalClose: c,
94
+ handlePrimaryAction: () => {
95
+ U();
96
+ },
97
+ handleSecondaryAction: c,
98
+ inputLabel: n.input_label,
99
+ inputValue: d,
100
+ isOpen: !0,
101
+ isPrimaryDisabled: S || !g,
102
+ primaryText: o.save_button,
103
+ secondaryText: t ? o.cancel_button : void 0,
104
+ subtitle: t && i.primary_account ? /* @__PURE__ */ k(T, {
105
+ sx: {
106
+ alignItems: "center",
107
+ flexDirection: "row",
108
+ gap: 8
109
+ },
110
+ children: [/* @__PURE__ */ e(ro, {
111
+ alt: "",
112
+ institutionGuid: i.primary_account.institution_guid ?? "",
113
+ size: 16
114
+ }), /* @__PURE__ */ e(P, {
115
+ color: "text.secondary",
116
+ uppercase: !0,
117
+ variant: "body2",
118
+ children: w
119
+ })]
120
+ }) : void 0,
121
+ title: C && n.title_edit || n.title
122
+ });
123
+ });
124
+ export {
125
+ Lo as AmountInputModalBlock
126
+ };
127
+
128
+ //# sourceMappingURL=AmountInputModalBlock.es.js.map
@@ -0,0 +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 } = beat.insights_payload ?? {}\n const variant = getDynamicCtaBasedOnTriggers(actionDrawer, primary_cta, secondary_cta)?.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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"}
@@ -0,0 +1 @@
1
+ export { AmountInputModalBlock } from './AmountInputModalBlock';
@@ -0,0 +1,14 @@
1
+ import { NotificationProfile } from '../../../core';
2
+ /** CTA variants `amount_input_modal_block` serves (ditto sets one on the beat's CTA). */
3
+ export declare const AMOUNT_INPUT_VARIANTS: {
4
+ readonly LOW_BALANCE_THRESHOLD: "low_balance_threshold";
5
+ readonly SAFETY_NET: "safety_net";
6
+ };
7
+ export type AmountInputVariant = (typeof AMOUNT_INPUT_VARIANTS)[keyof typeof AMOUNT_INPUT_VARIANTS];
8
+ export declare const isAmountInputVariant: (variant?: string | null) => variant is AmountInputVariant;
9
+ /** Whole dollars from 1 to `MAX_AMOUNT`, as pulse v1 accepted for both settings. */
10
+ export declare const isWholeAmount: (value: string) => boolean;
11
+ /** A saved amount as the field's starting text; nothing saved (or zero) starts empty. */
12
+ export declare const toInputValue: (amount?: number | null) => string;
13
+ /** The account's low-balance alert profile, which holds the threshold (as pulse v1 looked it up). */
14
+ export declare const findLowBalanceProfile: (profiles: NotificationProfile[], accountGuid?: string | null) => NotificationProfile | undefined;
@@ -0,0 +1,14 @@
1
+ import { AccountNotificationTypes as o } from "../../../core/types/Notification.es.js";
2
+ var a = {
3
+ LOW_BALANCE_THRESHOLD: "low_balance_threshold",
4
+ SAFETY_NET: "safety_net"
5
+ }, A = (t) => Object.values(a).includes(t ?? ""), i = 1e9, _ = (t) => /^\d+$/.test(t) && Number(t) >= 1 && Number(t) <= i, c = (t) => t ? String(Math.round(t)) : "", N = (t, n) => n ? t.find((e) => e.entity_guid === n && e.notification_type === o.AccountBalanceLow) : void 0;
6
+ export {
7
+ a as AMOUNT_INPUT_VARIANTS,
8
+ N as findLowBalanceProfile,
9
+ A as isAmountInputVariant,
10
+ _ as isWholeAmount,
11
+ c as toInputValue
12
+ };
13
+
14
+ //# sourceMappingURL=layout.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/AmountInputModalBlock/layout.ts"],"sourcesContent":["import type { NotificationProfile } from '../../../core'\nimport { AccountNotificationTypes } from '../../../core/types/Notification'\n\n/** CTA variants `amount_input_modal_block` serves (ditto sets one on the beat's CTA). */\nexport const AMOUNT_INPUT_VARIANTS = {\n LOW_BALANCE_THRESHOLD: 'low_balance_threshold',\n SAFETY_NET: 'safety_net',\n} as const\n\nexport type AmountInputVariant = (typeof AMOUNT_INPUT_VARIANTS)[keyof typeof AMOUNT_INPUT_VARIANTS]\n\nexport const isAmountInputVariant = (variant?: string | null): variant is AmountInputVariant =>\n Object.values<string>(AMOUNT_INPUT_VARIANTS).includes(variant ?? '')\n\n/** Pulse v1's upper bound for a safety net; applied to both variants. */\nconst MAX_AMOUNT = 1_000_000_000\n\n/** Whole dollars from 1 to `MAX_AMOUNT`, as pulse v1 accepted for both settings. */\nexport const isWholeAmount = (value: string): boolean =>\n /^\\d+$/.test(value) && Number(value) >= 1 && Number(value) <= MAX_AMOUNT\n\n/** A saved amount as the field's starting text; nothing saved (or zero) starts empty. */\nexport const toInputValue = (amount?: number | null): string =>\n amount ? String(Math.round(amount)) : ''\n\n/** The account's low-balance alert profile, which holds the threshold (as pulse v1 looked it up). */\nexport const findLowBalanceProfile = (\n profiles: NotificationProfile[],\n accountGuid?: string | null,\n): NotificationProfile | undefined =>\n accountGuid\n ? profiles.find(\n (profile) =>\n profile.entity_guid === accountGuid &&\n profile.notification_type === AccountNotificationTypes.AccountBalanceLow,\n )\n : undefined\n"],"mappings":";AAIA,IAAa,IAAwB;AAAA,EACnC,uBAAuB;AAAA,EACvB,YAAY;AACd,GAIa,IAAA,CAAwB,MACnC,OAAO,OAAe,CAAqB,EAAE,SAAS,KAAW,EAAE,GAG/D,IAAa,KAGN,IAAA,CAAiB,MAC5B,QAAQ,KAAK,CAAK,KAAK,OAAO,CAAK,KAAK,KAAK,OAAO,CAAK,KAAK,GAGnD,IAAA,CAAgB,MAC3B,IAAS,OAAO,KAAK,MAAM,CAAM,CAAC,IAAI,IAG3B,IAAA,CACX,GACA,MAEA,IACI,EAAS,KAAA,CACN,MACC,EAAQ,gBAAgB,KACxB,EAAQ,sBAAsB,EAAyB,iBAC3D,IACA"}
@@ -4,17 +4,17 @@ import { isContactSupportCtaDisabled as H } from "../actionBlocks/ContactSupport
4
4
  import { logoFromRef as M } from "./shared/EntityLogo.es.js";
5
5
  import { actionBlocksKey as P } from "../mappings/actionBlocksKey.es.js";
6
6
  import { ErrorCard as U } from "./ErrorCard.es.js";
7
- import { blocks as j } from "../mappings/blocks.es.js";
8
- import z from "./Footer.es.js";
9
- import G from "./Header.es.js";
10
- import { FeedbackBar as K } from "./FeedbackBar.es.js";
11
- import X from "./FeedbackDialog.es.js";
12
- import { DismissInsightDialog as V } from "./DismissInsightDialog.es.js";
13
- import { HiddenConfirmationCard as W } from "./HiddenConfirmationCard.es.js";
14
- import { LoadingCard as Y } from "./LoadingCard.es.js";
15
- import { ObsolesceBanner as q } from "./ObsolesceBanner.es.js";
16
- import { ActionBlockLayer as J } from "../actionBlocks/ActionBlockLayer.es.js";
17
- import { useBeatCtaActions as Q } from "../ctaActions/useCtaAction.es.js";
7
+ import { useBeatCtaActions as j } from "../ctaActions/useCtaAction.es.js";
8
+ import { blocks as z } from "../mappings/blocks.es.js";
9
+ import G from "./Footer.es.js";
10
+ import K from "./Header.es.js";
11
+ import { FeedbackBar as X } from "./FeedbackBar.es.js";
12
+ import V from "./FeedbackDialog.es.js";
13
+ import { DismissInsightDialog as W } from "./DismissInsightDialog.es.js";
14
+ import { HiddenConfirmationCard as Y } from "./HiddenConfirmationCard.es.js";
15
+ import { LoadingCard as q } from "./LoadingCard.es.js";
16
+ import { ObsolesceBanner as J } from "./ObsolesceBanner.es.js";
17
+ import { ActionBlockLayer as Q } from "../actionBlocks/ActionBlockLayer.es.js";
18
18
  import { useBeatFeedbackFlow as Z } from "../hooks/useBeatFeedbackFlow.es.js";
19
19
  import { observer as $ } from "mobx-react-lite";
20
20
  import b from "@mui/material/Stack";
@@ -42,7 +42,7 @@ var so = {}, no = {
42
42
  px: 2,
43
43
  textTransform: "none"
44
44
  }, Ro = $(({ beat: t, beatActions: s = so, obsolete: c, isLoading: x = !1, isError: _ = !1 }) => {
45
- const v = N(), { config: S } = E(), o = Z(t, s), { runCta: T, primary: C, secondary: g } = Q(t, s, t.insights_payload?.primary_cta, t.insights_payload?.secondary_cta);
45
+ const v = N(), { config: S } = E(), o = Z(t, s), { runCta: T, primary: C, secondary: g } = j(t, s, t.insights_payload?.primary_cta, t.insights_payload?.secondary_cta);
46
46
  if (_) return /* @__PURE__ */ i(U, {});
47
47
  const e = t.insights_payload;
48
48
  if (!e || o.cardState === "dismissed") return null;
@@ -57,17 +57,17 @@ var so = {}, no = {
57
57
  }
58
58
  await T(f, u);
59
59
  }
60
- }, k = j[A], d = L === "mini", m = v.insights_feed?.general, p = c?.state === "not_refreshable" && !F && !!m;
60
+ }, k = z[A], d = L === "mini", m = v.insights_feed?.general, p = c?.state === "not_refreshable" && !F && !!m;
61
61
  return /* @__PURE__ */ i(R, {
62
62
  value: o.wrappedActions,
63
63
  children: /* @__PURE__ */ a(b, {
64
64
  sx: { mb: 16 },
65
65
  children: [
66
- o.cardState === "confirming" ? /* @__PURE__ */ i(W, {
66
+ o.cardState === "confirming" ? /* @__PURE__ */ i(Y, {
67
67
  onClose: o.collapseConfirmation,
68
68
  onLeaveFeedback: o.openHideSurvey,
69
69
  onUndo: o.undoDismiss
70
- }) : /* @__PURE__ */ a(to, { children: [c && m && /* @__PURE__ */ i(q, {
70
+ }) : /* @__PURE__ */ a(to, { children: [c && m && /* @__PURE__ */ i(J, {
71
71
  ...c,
72
72
  copy: m
73
73
  }), /* @__PURE__ */ a(ao, {
@@ -79,12 +79,12 @@ var so = {}, no = {
79
79
  } },
80
80
  children: [
81
81
  p && /* @__PURE__ */ i(ro, { sx: no }),
82
- /* @__PURE__ */ i(G, {
82
+ /* @__PURE__ */ i(K, {
83
83
  isMini: d,
84
84
  subtitle: w,
85
85
  title: D ?? ""
86
86
  }),
87
- x ? /* @__PURE__ */ i(Y, {}) : k && /* @__PURE__ */ i(k, {
87
+ x ? /* @__PURE__ */ i(q, {}) : k && /* @__PURE__ */ i(k, {
88
88
  beatGuid: I,
89
89
  payload: e
90
90
  }),
@@ -136,19 +136,19 @@ var so = {}, no = {
136
136
  children: n.label
137
137
  })]
138
138
  }),
139
- /* @__PURE__ */ i(K, {
139
+ /* @__PURE__ */ i(X, {
140
140
  onTellUsMore: o.openRatingSurvey,
141
141
  show: o.isFeedbackBarShown
142
142
  })
143
143
  ]
144
144
  })] }),
145
- /* @__PURE__ */ i(J, { beat: t }),
146
- /* @__PURE__ */ i(V, {
145
+ /* @__PURE__ */ i(Q, { beat: t }),
146
+ /* @__PURE__ */ i(W, {
147
147
  isOpen: o.isDismissOpen,
148
148
  onClose: o.closeDismissDialog,
149
149
  onDismiss: o.confirmDismiss
150
150
  }),
151
- o.isSurveyOpen && /* @__PURE__ */ i(X, {
151
+ o.isSurveyOpen && /* @__PURE__ */ i(V, {
152
152
  characterLimitLabel: o.survey.characterLimitLabel,
153
153
  closeAriaLabel: o.survey.closeAriaLabel,
154
154
  isOpen: !0,
@@ -161,7 +161,7 @@ var so = {}, no = {
161
161
  secondaryText: o.survey.backText,
162
162
  title: o.survey.title
163
163
  }),
164
- o.cardState === "visible" && e.footer && /* @__PURE__ */ i(z, { footer: e.footer })
164
+ o.cardState === "visible" && e.footer && /* @__PURE__ */ i(G, { footer: e.footer })
165
165
  ]
166
166
  })
167
167
  });
@@ -1,10 +1,10 @@
1
1
  import { sendBeatCta as l } from "../../common/utils/PostMessage.es.js";
2
- import { hasDrawerBlock as f } from "../mappings/blocks.es.js";
3
- import { HOST_ACTIONS as m } from "./hostActions.es.js";
4
- import { ctaActions as C } from "./ctaConfig.es.js";
5
- import { ctaHandlers as v } from "./ctaHandlers.es.js";
6
- import { resolveCtaMetadata as g } from "./ctaPostMessages.es.js";
7
- var w = {}, o = ({ cta: t }) => t.action_key && C[t.action_key] || w, H = (t) => {
2
+ import { HOST_ACTIONS as f } from "./hostActions.es.js";
3
+ import { ctaActions as m } from "./ctaConfig.es.js";
4
+ import { ctaHandlers as C } from "./ctaHandlers.es.js";
5
+ import { resolveCtaMetadata as v } from "./ctaPostMessages.es.js";
6
+ import { hasDrawerBlock as g } from "../mappings/hasDrawerBlock.es.js";
7
+ var w = {}, o = ({ cta: t }) => t.action_key && m[t.action_key] || w, H = (t) => {
8
8
  const a = o(t);
9
9
  return { disabled: t.stores.insightsV2Store.ctaStatus[t.beat.guid] === "pending" || (a.disabledWhen?.(t) ?? !1) };
10
10
  }, n = ({ beat: t, slot: a, stores: r }) => {
@@ -20,17 +20,17 @@ var w = {}, o = ({ cta: t }) => t.action_key && C[t.action_key] || w, H = (t) =>
20
20
  userGuid: t.insights_payload?.user_guid ?? t.user_guid
21
21
  });
22
22
  }, S = (t) => {
23
- f(t.cta.block) && n(t);
23
+ g(t.cta.block) && n(t);
24
24
  }, _ = (t, { beat: a, stores: r }) => {
25
25
  t === "refreshBeat" && r.emitUpdate({ staleBeatGuid: a.guid });
26
26
  }, y = ({ beat: t, slot: a, hostActions: r }) => {
27
27
  a !== "primary" || t.cta_clicked_at || Promise.resolve().then(() => r.onCtaAction?.({
28
28
  beatGuid: t.guid,
29
- name: m.markCtaClicked
29
+ name: f.markCtaClicked
30
30
  })).catch(() => {
31
31
  });
32
32
  }, c = async (t, a, r) => {
33
- const e = v[a];
33
+ const e = C[a];
34
34
  if (!e) throw new Error(`Unknown CTA handler "${a}"`);
35
35
  await e(t);
36
36
  for (const s of r) _(s, t);
@@ -56,7 +56,7 @@ var w = {}, o = ({ cta: t }) => t.action_key && C[t.action_key] || w, H = (t) =>
56
56
  };
57
57
  a.skipCtaClicked || y(e), await u(e, async () => {
58
58
  a.run && await c(e, a.run, a.onSuccess ?? []);
59
- const s = g(e);
59
+ const s = v(e);
60
60
  s && h(e, s);
61
61
  const i = a.next ?? (a.run ? "none" : "default");
62
62
  i === "default" ? S(e) : i === "drawer" && n(e);
@@ -1 +1 @@
1
- {"version":3,"file":"runCtaAction.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/runCtaAction.ts"],"sourcesContent":["import { sendBeatCta } from '../../common/utils/PostMessage'\nimport { hasDrawerBlock } from '../mappings/blocks'\nimport { ctaActions } from './ctaConfig'\nimport { resolveCtaMetadata } from './ctaPostMessages'\nimport { ctaHandlers, type CtaHandlerName } from './ctaHandlers'\nimport { HOST_ACTIONS } from './hostActions'\nimport type { CtaPostMessageMetadata } from '../../core'\nimport type {\n CtaContext,\n CtaEffect,\n CtaHandler,\n CtaHandlerContext,\n CtaState,\n CtaStateContext,\n CtaStep,\n RunHandlerOptions,\n} from './types'\n\nconst DEFAULT_STEP: CtaStep = {}\n\n// ─── Resolution ─────────────────────────────────────────────────────────────\n\n/** The beat's own context (input a block collected for it), `{}` until something is written. */\nexport const beatContextOf = ({ beat, stores }: Pick<CtaStateContext, 'beat' | 'stores'>) =>\n stores.insightsV2Store.beatContext[beat.guid] ?? {}\n\n/** The step named by the CTA's `action_key`, or the empty default (no key / unknown key). */\nexport const resolveStep = ({ cta }: Pick<CtaStateContext, 'cta'>): CtaStep =>\n (cta.action_key && ctaActions[cta.action_key]) || DEFAULT_STEP\n\n/** Pending always disables; the step's `disabledWhen` may add to that. */\nexport const resolveCtaState = (ctx: CtaStateContext): CtaState => {\n const step = resolveStep(ctx)\n const pending = ctx.stores.insightsV2Store.ctaStatus[ctx.beat.guid] === 'pending'\n\n return { disabled: pending || (step.disabledWhen?.(ctx) ?? false) }\n}\n\n// ─── Targets and effects ────────────────────────────────────────────────────\n\n/** Opens the beat's drawer for whichever slot triggered the current step. */\nconst openDrawerForSlot = ({\n beat,\n slot,\n stores,\n}: Pick<CtaContext, 'beat' | 'slot' | 'stores'>) => {\n stores.insightsV2Store.openCtaDrawer(\n beat.guid,\n slot === 'primary' ? 'primaryCta' : 'secondaryCta',\n )\n}\n\n/**\n * Posts this CTA's resolved message (event, op and beat-specific payload, from the backend's\n * `cta.metadata` or the template's `ctaPostMessages` entry) to the host.\n */\nconst postCtaMessage = ({ beat, stores }: CtaContext, metadata: CtaPostMessageMetadata) => {\n const { is_mobile_webview, clientConfig } = stores.appDataStore.appConfig\n const { event, action, payload } = metadata\n\n sendBeatCta(beat, {\n event: event ?? undefined,\n op: action ?? undefined,\n extras: payload ?? undefined,\n isMobileWebview: is_mobile_webview,\n webviewScheme: clientConfig?.master?.ui_message_webview_url_scheme,\n userGuid: beat.insights_payload?.user_guid ?? beat.user_guid,\n })\n}\n\n/**\n * Default target: open the drawer for a real block, otherwise nothing. A post message is never\n * implied by a missing block; it is sent only when one resolves (see runCtaAction).\n */\nconst runDefaultTarget = (ctx: CtaContext) => {\n if (hasDrawerBlock(ctx.cta.block)) openDrawerForSlot(ctx)\n}\n\nconst runEffect = (effect: CtaEffect, { beat, stores }: CtaContext) => {\n switch (effect) {\n case 'refreshBeat':\n stores.emitUpdate({ staleBeatGuid: beat.guid })\n break\n }\n}\n\n/**\n * First primary click records `cta_clicked_at` through the host. Fire-and-forget: whatever the\n * host does (reject, throw, return nothing) must never fail the user's action.\n */\nconst markCtaClicked = ({ beat, slot, hostActions }: CtaContext) => {\n if (slot !== 'primary' || beat.cta_clicked_at) return\n\n // Wrapping the call in `.then` turns a synchronous throw into a rejection, and the `.catch`\n // swallows both: a failed cta_clicked_at write is not the user's action.\n Promise.resolve()\n .then(() =>\n hostActions.onCtaAction?.({ beatGuid: beat.guid, name: HOST_ACTIONS.markCtaClicked }),\n )\n .catch(() => {})\n}\n\n// ─── Shared bookkeeping ─────────────────────────────────────────────────────\n\n/** Runs a named handler, then its `onSuccess` effects. Shared by card CTAs and drawer saves. */\nconst runHandlerWithEffects = async (\n ctx: CtaContext,\n handlerName: CtaHandlerName,\n onSuccess: CtaEffect[],\n): Promise<void> => {\n // Names are typed, but the map is still indexed defensively: config can come from anywhere.\n const handler = (ctaHandlers as Record<string, CtaHandler>)[handlerName]\n\n if (!handler) throw new Error(`Unknown CTA handler \"${handlerName}\"`)\n\n await handler(ctx)\n\n for (const effect of onSuccess) runEffect(effect, ctx)\n}\n\n/** Per-beat status around `work`: `pending` -> `done` | `error` (+ captureException). */\nconst withCtaStatus = async (\n { beat, stores }: Pick<CtaContext, 'beat' | 'stores'>,\n work: () => Promise<void>,\n captureContext: Record<string, string> = {},\n): Promise<boolean> => {\n const { insightsV2Store } = stores\n\n insightsV2Store.setCtaStatus(beat.guid, 'pending')\n\n try {\n await work()\n insightsV2Store.setCtaStatus(beat.guid, 'done')\n\n return true\n } catch (error) {\n insightsV2Store.setCtaStatus(beat.guid, 'error')\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n ...captureContext,\n })\n\n return false\n }\n}\n\n// ─── Entry points ───────────────────────────────────────────────────────────\n\n/**\n * Runs the CTA's step: `run` handler, `onSuccess` effects, the CTA's post message if one resolves\n * (backend `cta.metadata` or the template's `ctaPostMessages` entry; skipped when the handler\n * throws), then the `next` target. No resolved message = no post.\n */\nexport const runCtaAction = async (ctx: CtaContext): Promise<void> => {\n const step = resolveStep(ctx)\n const stepParams = typeof step.params === 'function' ? step.params(ctx) : step.params\n const stepCtx: CtaContext = { ...ctx, params: { ...stepParams, ...ctx.params } }\n\n if (!step.skipCtaClicked) markCtaClicked(stepCtx)\n\n await withCtaStatus(stepCtx, async () => {\n if (step.run) await runHandlerWithEffects(stepCtx, step.run, step.onSuccess ?? [])\n\n const metadata = resolveCtaMetadata(stepCtx)\n\n if (metadata) postCtaMessage(stepCtx, metadata)\n\n const target = step.next ?? (step.run ? 'none' : 'default')\n\n if (target === 'default') runDefaultTarget(stepCtx)\n else if (target === 'drawer') openDrawerForSlot(stepCtx)\n })\n}\n\n/** Placeholder CTA for handlers run from inside a drawer, where no card slot is involved. */\nconst DIRECT_CTA = { label: '', block: '' }\n\n/** Runs a handler directly (drawer Save buttons): same bookkeeping as a card CTA. True on success. */\nexport const runCtaHandler = (\n ctx: CtaHandlerContext,\n handlerName: CtaHandlerName,\n { onSuccess = [] }: RunHandlerOptions = {},\n): Promise<boolean> => {\n const fullCtx: CtaContext = { ...ctx, cta: DIRECT_CTA, slot: 'primary' }\n\n return withCtaStatus(fullCtx, () => runHandlerWithEffects(fullCtx, handlerName, onSuccess), {\n handler: handlerName,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runCtaAction.es.js","names":[],"sources":["../../../src/insights-v2/ctaActions/runCtaAction.ts"],"sourcesContent":["import { sendBeatCta } from '../../common/utils/PostMessage'\nimport { hasDrawerBlock } from '../mappings/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} from './types'\n\nconst DEFAULT_STEP: CtaStep = {}\n\n// ─── Resolution ─────────────────────────────────────────────────────────────\n\n/** The beat's own context (input a block collected for it), `{}` until something is written. */\nexport const beatContextOf = ({ beat, stores }: Pick<CtaStateContext, 'beat' | 'stores'>) =>\n stores.insightsV2Store.beatContext[beat.guid] ?? {}\n\n/** The step named by the CTA's `action_key`, or the empty default (no key / unknown key). */\nexport const resolveStep = ({ cta }: Pick<CtaStateContext, 'cta'>): CtaStep =>\n (cta.action_key && ctaActions[cta.action_key]) || DEFAULT_STEP\n\n/** Pending always disables; the step's `disabledWhen` may add to that. */\nexport const resolveCtaState = (ctx: CtaStateContext): CtaState => {\n const step = resolveStep(ctx)\n const pending = ctx.stores.insightsV2Store.ctaStatus[ctx.beat.guid] === 'pending'\n\n return { disabled: pending || (step.disabledWhen?.(ctx) ?? false) }\n}\n\n// ─── Targets and effects ────────────────────────────────────────────────────\n\n/** Opens the beat's drawer for whichever slot triggered the current step. */\nconst openDrawerForSlot = ({\n beat,\n slot,\n stores,\n}: Pick<CtaContext, 'beat' | 'slot' | 'stores'>) => {\n stores.insightsV2Store.openCtaDrawer(\n beat.guid,\n slot === 'primary' ? 'primaryCta' : 'secondaryCta',\n )\n}\n\n/**\n * Posts this CTA's resolved message (event, op and beat-specific payload, from the backend's\n * `cta.metadata` or the template's `ctaPostMessages` entry) to the host.\n */\nconst postCtaMessage = ({ beat, stores }: CtaContext, metadata: CtaPostMessageMetadata) => {\n const { is_mobile_webview, clientConfig } = stores.appDataStore.appConfig\n const { event, action, payload } = metadata\n\n sendBeatCta(beat, {\n event: event ?? undefined,\n op: action ?? undefined,\n extras: payload ?? undefined,\n isMobileWebview: is_mobile_webview,\n webviewScheme: clientConfig?.master?.ui_message_webview_url_scheme,\n userGuid: beat.insights_payload?.user_guid ?? beat.user_guid,\n })\n}\n\n/**\n * Default target: open the drawer for a real block, otherwise nothing. A post message is never\n * implied by a missing block; it is sent only when one resolves (see runCtaAction).\n */\nconst runDefaultTarget = (ctx: CtaContext) => {\n if (hasDrawerBlock(ctx.cta.block)) openDrawerForSlot(ctx)\n}\n\nconst runEffect = (effect: CtaEffect, { beat, stores }: CtaContext) => {\n switch (effect) {\n case 'refreshBeat':\n stores.emitUpdate({ staleBeatGuid: beat.guid })\n break\n }\n}\n\n/**\n * First primary click records `cta_clicked_at` through the host. Fire-and-forget: whatever the\n * host does (reject, throw, return nothing) must never fail the user's action.\n */\nconst markCtaClicked = ({ beat, slot, hostActions }: CtaContext) => {\n if (slot !== 'primary' || beat.cta_clicked_at) return\n\n // Wrapping the call in `.then` turns a synchronous throw into a rejection, and the `.catch`\n // swallows both: a failed cta_clicked_at write is not the user's action.\n Promise.resolve()\n .then(() =>\n hostActions.onCtaAction?.({ beatGuid: beat.guid, name: HOST_ACTIONS.markCtaClicked }),\n )\n .catch(() => {})\n}\n\n// ─── Shared bookkeeping ─────────────────────────────────────────────────────\n\n/** Runs a named handler, then its `onSuccess` effects. Shared by card CTAs and drawer saves. */\nconst runHandlerWithEffects = async (\n ctx: CtaContext,\n handlerName: CtaHandlerName,\n onSuccess: CtaEffect[],\n): Promise<void> => {\n // Names are typed, but the map is still indexed defensively: config can come from anywhere.\n const handler = (ctaHandlers as Record<string, CtaHandler>)[handlerName]\n\n if (!handler) throw new Error(`Unknown CTA handler \"${handlerName}\"`)\n\n await handler(ctx)\n\n for (const effect of onSuccess) runEffect(effect, ctx)\n}\n\n/** Per-beat status around `work`: `pending` -> `done` | `error` (+ captureException). */\nconst withCtaStatus = async (\n { beat, stores }: Pick<CtaContext, 'beat' | 'stores'>,\n work: () => Promise<void>,\n captureContext: Record<string, string> = {},\n): Promise<boolean> => {\n const { insightsV2Store } = stores\n\n insightsV2Store.setCtaStatus(beat.guid, 'pending')\n\n try {\n await work()\n insightsV2Store.setCtaStatus(beat.guid, 'done')\n\n return true\n } catch (error) {\n insightsV2Store.setCtaStatus(beat.guid, 'error')\n stores.captureException(error, {\n store: 'ctaActions',\n template: beat.template,\n ...captureContext,\n })\n\n return false\n }\n}\n\n// ─── Entry points ───────────────────────────────────────────────────────────\n\n/**\n * Runs the CTA's step: `run` handler, `onSuccess` effects, the CTA's post message if one resolves\n * (backend `cta.metadata` or the template's `ctaPostMessages` entry; skipped when the handler\n * throws), then the `next` target. No resolved message = no post.\n */\nexport const runCtaAction = async (ctx: CtaContext): Promise<void> => {\n const step = resolveStep(ctx)\n const stepParams = typeof step.params === 'function' ? step.params(ctx) : step.params\n const stepCtx: CtaContext = { ...ctx, params: { ...stepParams, ...ctx.params } }\n\n if (!step.skipCtaClicked) markCtaClicked(stepCtx)\n\n await withCtaStatus(stepCtx, async () => {\n if (step.run) await runHandlerWithEffects(stepCtx, step.run, step.onSuccess ?? [])\n\n const metadata = resolveCtaMetadata(stepCtx)\n\n if (metadata) postCtaMessage(stepCtx, metadata)\n\n const target = step.next ?? (step.run ? 'none' : 'default')\n\n if (target === 'default') runDefaultTarget(stepCtx)\n else if (target === 'drawer') openDrawerForSlot(stepCtx)\n })\n}\n\n/** Placeholder CTA for handlers run from inside a drawer, where no card slot is involved. */\nconst DIRECT_CTA = { label: '', block: '' }\n\n/** Runs a handler directly (drawer Save buttons): same bookkeeping as a card CTA. True on success. */\nexport const runCtaHandler = (\n ctx: CtaHandlerContext,\n handlerName: CtaHandlerName,\n { onSuccess = [] }: RunHandlerOptions = {},\n): Promise<boolean> => {\n const fullCtx: CtaContext = { ...ctx, cta: DIRECT_CTA, slot: 'primary' }\n\n return withCtaStatus(fullCtx, () => runHandlerWithEffects(fullCtx, handlerName, onSuccess), {\n handler: handlerName,\n })\n}\n"],"mappings":"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"}
@@ -32,14 +32,14 @@ import { SVGImageBlock as lr } from "./blocks/ImageBlocks/SVGImageBlock/SVGImage
32
32
  import Cr from "./components/AmountPill.es.js";
33
33
  import Br from "./components/DesignateEmergencySavingsCard.es.js";
34
34
  import { ErrorCard as hr } from "./components/ErrorCard.es.js";
35
- import { FeedbackBar as Sr } from "./components/FeedbackBar.es.js";
36
- import Ar from "./components/FeedbackDialog.es.js";
37
- import { DismissInsightDialog as br } from "./components/DismissInsightDialog.es.js";
38
- import { HiddenConfirmationCard as Lr } from "./components/HiddenConfirmationCard.es.js";
39
- import { LoadingCard as vr } from "./components/LoadingCard.es.js";
40
- import { ObsolesceBanner as wr } from "./components/ObsolesceBanner.es.js";
41
- import { HOST_ACTIONS as Pr } from "./ctaActions/hostActions.es.js";
42
- import { useBeatCtaActions as Mr, useCtaHandler as Vr } from "./ctaActions/useCtaAction.es.js";
35
+ import { HOST_ACTIONS as Sr } from "./ctaActions/hostActions.es.js";
36
+ import { useBeatCtaActions as Ar, useCtaHandler as Tr } from "./ctaActions/useCtaAction.es.js";
37
+ import { FeedbackBar as Ir } from "./components/FeedbackBar.es.js";
38
+ import Hr from "./components/FeedbackDialog.es.js";
39
+ import { DismissInsightDialog as Or } from "./components/DismissInsightDialog.es.js";
40
+ import { HiddenConfirmationCard as Er } from "./components/HiddenConfirmationCard.es.js";
41
+ import { LoadingCard as Rr } from "./components/LoadingCard.es.js";
42
+ import { ObsolesceBanner as Vr } from "./components/ObsolesceBanner.es.js";
43
43
  import yr from "./components/BaseLayout.es.js";
44
44
  import Gr from "./components/StatusMessageCard.es.js";
45
45
  import Nr from "./components/LogoTile.es.js";
@@ -63,24 +63,24 @@ export {
63
63
  Br as DesignateEmergencySavingsCard,
64
64
  J as DirectDepositBlock,
65
65
  q as DirectDepositCard,
66
- br as DismissInsightDialog,
66
+ Or as DismissInsightDialog,
67
67
  R as DonutChartBlock,
68
68
  O as DoubleBarChartBlock,
69
69
  V as DoubleProgressBarBlock,
70
70
  hr as ErrorCard,
71
- Sr as FeedbackBar,
72
- Ar as FeedbackDialog,
73
- Pr as HOST_ACTIONS,
74
- Lr as HiddenConfirmationCard,
71
+ Ir as FeedbackBar,
72
+ Hr as FeedbackDialog,
73
+ Sr as HOST_ACTIONS,
74
+ Er as HiddenConfirmationCard,
75
75
  S as InsightErrorCard,
76
76
  c as InsightOptionsMenu,
77
77
  or as InsightTransactionRow,
78
- vr as LoadingCard,
78
+ Rr as LoadingCard,
79
79
  Ur as LogoClusterCard,
80
80
  d as LogoFromGuid,
81
81
  Nr as LogoTile,
82
82
  qr as MerchantHighlightCard,
83
- wr as ObsolesceBanner,
83
+ Vr as ObsolesceBanner,
84
84
  n as OverflowMenu,
85
85
  Jr as P2PLogoStack,
86
86
  lr as SVGImageBlock,
@@ -105,6 +105,6 @@ export {
105
105
  C as isLogoGuid,
106
106
  m as resolveInsightRender,
107
107
  a as useBeatActions,
108
- Mr as useBeatCtaActions,
109
- Vr as useCtaHandler
108
+ Ar as useBeatCtaActions,
109
+ Tr as useCtaHandler
110
110
  };
@@ -7,4 +7,5 @@ export declare const actionBlocksKey: {
7
7
  RECURRING_TRANSACTION_ACTION_BLOCK: string;
8
8
  TRANSACTION_LIST_ACTION_BLOCK: string;
9
9
  REMAINING_CASH_ACTION_BLOCK: string;
10
+ AMOUNT_INPUT_MODAL_ACTION_BLOCK: string;
10
11
  };
@@ -6,7 +6,8 @@ var _ = {
6
6
  CONTACT_SUPPORT_ACTION_BLOCK: "contact_support_block",
7
7
  RECURRING_TRANSACTION_ACTION_BLOCK: "recurring_transaction_block",
8
8
  TRANSACTION_LIST_ACTION_BLOCK: "transaction_list_block",
9
- REMAINING_CASH_ACTION_BLOCK: "remaining_cash_block"
9
+ REMAINING_CASH_ACTION_BLOCK: "remaining_cash_block",
10
+ AMOUNT_INPUT_MODAL_ACTION_BLOCK: "amount_input_modal_block"
10
11
  };
11
12
  export {
12
13
  _ as actionBlocksKey
@@ -1 +1 @@
1
- {"version":3,"file":"actionBlocksKey.es.js","names":[],"sources":["../../../src/insights-v2/mappings/actionBlocksKey.ts"],"sourcesContent":["// Keys for the drawer drilldown registry in `blocks.ts`, extracted into their own module.\n//\n// Why: chart blocks (e.g. BarchartBlock, DoubleBarChartBlock) need\n// `TRANSCATION_HISTORY_ACTION_BLOCK` to tag a chart bar's drilldown context, but they are\n// themselves re-exported through `../blocks` -> `mappings/blocks.ts` imports from that same\n// barrel to build its `blocks` registry. Importing the key straight from `mappings/blocks.ts`\n// closed that loop into a circular import: evaluating `mappings/blocks.ts` pulled in the\n// `blocks` barrel, which (before it got to `ComponentBlocks`/`AccountBlock`) loaded\n// `BarchartBlock`, which imported back into the still-initializing `mappings/blocks.ts` —\n// tripping a \"Cannot access 'AccountBlock' before initialization\" TDZ error in Storybook/Vite.\n//\n// Keeping the keys here (no dependency on `mappings/blocks.ts`) lets chart blocks reference\n// them without re-entering that module.\n//\n// Names follow ditto: its registry ends every CTA block in `_block` and the V2 envelope copies those\n// names verbatim (`transaction_details_block`, `contact_support_block`). `make_transfer` is a legacy\n// name that stays as an alias until the last legacy payload is gone.\nexport const actionBlocksKey = {\n PLACEHOLDER_ACTION_BLOCK: 'PlaceholderActionBlock',\n MAKE_TRANSFER_ACTION_BLOCK: 'make_transfer',\n TRANSCATION_HISTORY_ACTION_BLOCK: 'transaction_history_block',\n TRANSACTION_DETAILS_ACTION_BLOCK: 'transaction_details_block',\n CONTACT_SUPPORT_ACTION_BLOCK: 'contact_support_block',\n RECURRING_TRANSACTION_ACTION_BLOCK: 'recurring_transaction_block',\n TRANSACTION_LIST_ACTION_BLOCK: 'transaction_list_block',\n REMAINING_CASH_ACTION_BLOCK: 'remaining_cash_block',\n}\n"],"mappings":"AAiBA,IAAa,IAAkB;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,4BAA4B;AAAA,EAC5B,kCAAkC;AAAA,EAClC,kCAAkC;AAAA,EAClC,8BAA8B;AAAA,EAC9B,oCAAoC;AAAA,EACpC,+BAA+B;AAAA,EAC/B,6BAA6B;AAC/B"}
1
+ {"version":3,"file":"actionBlocksKey.es.js","names":[],"sources":["../../../src/insights-v2/mappings/actionBlocksKey.ts"],"sourcesContent":["// Keys for the drawer drilldown registry in `blocks.ts`, extracted into their own module.\n//\n// Why: chart blocks (e.g. BarchartBlock, DoubleBarChartBlock) need\n// `TRANSCATION_HISTORY_ACTION_BLOCK` to tag a chart bar's drilldown context, but they are\n// themselves re-exported through `../blocks` -> `mappings/blocks.ts` imports from that same\n// barrel to build its `blocks` registry. Importing the key straight from `mappings/blocks.ts`\n// closed that loop into a circular import: evaluating `mappings/blocks.ts` pulled in the\n// `blocks` barrel, which (before it got to `ComponentBlocks`/`AccountBlock`) loaded\n// `BarchartBlock`, which imported back into the still-initializing `mappings/blocks.ts` —\n// tripping a \"Cannot access 'AccountBlock' before initialization\" TDZ error in Storybook/Vite.\n//\n// Keeping the keys here (no dependency on `mappings/blocks.ts`) lets chart blocks reference\n// them without re-entering that module.\n//\n// Names follow ditto: its registry ends every CTA block in `_block` and the V2 envelope copies those\n// names verbatim (`transaction_details_block`, `contact_support_block`). `make_transfer` is a legacy\n// name that stays as an alias until the last legacy payload is gone.\nexport const actionBlocksKey = {\n PLACEHOLDER_ACTION_BLOCK: 'PlaceholderActionBlock',\n MAKE_TRANSFER_ACTION_BLOCK: 'make_transfer',\n TRANSCATION_HISTORY_ACTION_BLOCK: 'transaction_history_block',\n TRANSACTION_DETAILS_ACTION_BLOCK: 'transaction_details_block',\n CONTACT_SUPPORT_ACTION_BLOCK: 'contact_support_block',\n RECURRING_TRANSACTION_ACTION_BLOCK: 'recurring_transaction_block',\n TRANSACTION_LIST_ACTION_BLOCK: 'transaction_list_block',\n REMAINING_CASH_ACTION_BLOCK: 'remaining_cash_block',\n AMOUNT_INPUT_MODAL_ACTION_BLOCK: 'amount_input_modal_block',\n}\n"],"mappings":"AAiBA,IAAa,IAAkB;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,4BAA4B;AAAA,EAC5B,kCAAkC;AAAA,EAClC,kCAAkC;AAAA,EAClC,8BAA8B;AAAA,EAC9B,oCAAoC;AAAA,EACpC,+BAA+B;AAAA,EAC/B,6BAA6B;AAAA,EAC7B,iCAAiC;AACnC"}
@@ -2,11 +2,12 @@ import { Beat, GlobalCopy, InsightsPayload } from '../../core';
2
2
  export interface ActionBlockProps {
3
3
  beat: Beat;
4
4
  }
5
- export declare const hasDrawerBlock: (block?: string | null) => boolean;
5
+ export { hasDrawerBlock } from './hasDrawerBlock';
6
6
  export interface ActionBlockEntry {
7
7
  Component: React.ComponentType<ActionBlockProps>;
8
8
  usePrepare: (props: ActionBlockProps) => void;
9
9
  title?: (variant: string | null | undefined, copy: GlobalCopy, beat?: Beat) => string;
10
+ presentation?: 'drawer' | 'modal';
10
11
  }
11
12
  export declare const actionBlocks: Record<string, ActionBlockEntry>;
12
13
  export declare const blocks: Record<string, React.ComponentType<{
@@ -1,99 +1,99 @@
1
- import { ContactSupportActionBlock as l } from "../actionBlocks/ContactSupportActionBlock.es.js";
2
- import { actionBlocksKey as r } from "./actionBlocksKey.es.js";
3
- import { BarchartBlock as n } from "../blocks/ChartBlocks/BarchartBlock/BarchartBlock.es.js";
4
- import { DoubleBarChartBlock as m } from "../blocks/ChartBlocks/DoubleBarChartBlock/DoubleBarChartBlock.es.js";
5
- import _ from "../blocks/ChartBlocks/StepChartBlock/StepChartBlock.es.js";
6
- import p from "../blocks/ChartBlocks/DonutChartBlock/DonutChartBlock.es.js";
1
+ import { ContactSupportActionBlock as m } from "../actionBlocks/ContactSupportActionBlock.es.js";
2
+ import { actionBlocksKey as o } from "./actionBlocksKey.es.js";
3
+ import { BarchartBlock as l } from "../blocks/ChartBlocks/BarchartBlock/BarchartBlock.es.js";
4
+ import { DoubleBarChartBlock as n } from "../blocks/ChartBlocks/DoubleBarChartBlock/DoubleBarChartBlock.es.js";
5
+ import p from "../blocks/ChartBlocks/StepChartBlock/StepChartBlock.es.js";
6
+ import _ from "../blocks/ChartBlocks/DonutChartBlock/DonutChartBlock.es.js";
7
7
  import s from "../blocks/ChartBlocks/DoubleProgressBarBlock/DoubleProgressBarBlock.es.js";
8
8
  import k from "../blocks/ComponentBlocks/AccountBlock/AccountBlock.es.js";
9
9
  import C from "../blocks/ComponentBlocks/AccountSwitchBlock/AccountSwitchBlock.es.js";
10
10
  import B from "../blocks/ComponentBlocks/DirectDepositBlock/DirectDepositBlock.es.js";
11
11
  import { BudgetBlock as T } from "../blocks/ComponentBlocks/BudgetBlock/BudgetBlock.es.js";
12
- import f from "../blocks/ComponentBlocks/SaveExtraBlock/SaveExtraBlock.es.js";
13
- import { TransactionBlock as u } from "../blocks/ComponentBlocks/TransactionBlock/TransactionBlock.es.js";
14
- import A from "../blocks/ComponentBlocks/WithdrawalBlock/WithdrawalBlock.es.js";
12
+ import A from "../blocks/ComponentBlocks/SaveExtraBlock/SaveExtraBlock.es.js";
13
+ import { TransactionBlock as f } from "../blocks/ComponentBlocks/TransactionBlock/TransactionBlock.es.js";
14
+ import u from "../blocks/ComponentBlocks/WithdrawalBlock/WithdrawalBlock.es.js";
15
15
  import O from "../blocks/ImageBlocks/ImageBlock.es.js";
16
- import { SVGImageBlock as b } from "../blocks/ImageBlocks/SVGImageBlock/SVGImageBlock.es.js";
17
- import { PlaceholderActionBlock as N } from "../actionBlocks/PlaceholderActionBlock.es.js";
18
- import { getMakeTransferDrawerTitle as h } from "../actionBlocks/MakeTransferActionBlock/layout.es.js";
19
- import { MakeTransferActionBlockLayer as I } from "../actionBlocks/MakeTransferActionBlock/MakeTransferActionBlockLayer.es.js";
20
- import { getDrawerHeaderTitle as d } from "../actionBlocks/TransactionHistoryBlock/utils.es.js";
21
- import { TransactionDetailsBlock as g } from "../actionBlocks/TransactionDetailsBlock/TransactionDetailsBlock.es.js";
16
+ import { SVGImageBlock as N } from "../blocks/ImageBlocks/SVGImageBlock/SVGImageBlock.es.js";
17
+ import { PlaceholderActionBlock as b } from "../actionBlocks/PlaceholderActionBlock.es.js";
18
+ import { getMakeTransferDrawerTitle as I } from "../actionBlocks/MakeTransferActionBlock/layout.es.js";
19
+ import { MakeTransferActionBlockLayer as h } from "../actionBlocks/MakeTransferActionBlock/MakeTransferActionBlockLayer.es.js";
20
+ import { getDrawerHeaderTitle as g } from "../actionBlocks/TransactionHistoryBlock/utils.es.js";
21
+ import { TransactionDetailsBlock as d } from "../actionBlocks/TransactionDetailsBlock/TransactionDetailsBlock.es.js";
22
22
  import { useTransactionDetailsPrepare as a } from "../actionBlocks/TransactionDetailsBlock/useTransactionDetailsPrepare.es.js";
23
23
  import { TransactionHistoryBlock as L } from "../actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js";
24
24
  import { useRecurringTransactionPrepare as i } from "../actionBlocks/RecurringTransactionBlock/useRecurringTransactionPrepare.es.js";
25
- import { RecurringTransactionBlock as S } from "../actionBlocks/RecurringTransactionBlock/RecurringTransactionBlock.es.js";
25
+ import { RecurringTransactionBlock as P } from "../actionBlocks/RecurringTransactionBlock/RecurringTransactionBlock.es.js";
26
26
  import { TransactionListBlock as R } from "../actionBlocks/TransactionListBlock/TransactionListBlock.es.js";
27
- import { RemainingCashBlock as P } from "../actionBlocks/RemainingCashBlock/RemainingCashBlock.es.js";
28
- var e = (t) => {
29
- }, D = /* @__PURE__ */ new Set([
30
- "",
31
- "null",
32
- "undefined"
33
- ]), io = (t) => {
34
- const o = t?.trim().toLowerCase();
35
- return !!o && !D.has(o);
36
- }, co = {
37
- [r.PLACEHOLDER_ACTION_BLOCK]: {
38
- Component: N,
39
- usePrepare: e,
27
+ import { RemainingCashBlock as D } from "../actionBlocks/RemainingCashBlock/RemainingCashBlock.es.js";
28
+ import { hasDrawerBlock as co } from "./hasDrawerBlock.es.js";
29
+ import { AmountInputModalBlock as S } from "../actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js";
30
+ var t = (e) => {
31
+ }, lo = {
32
+ [o.PLACEHOLDER_ACTION_BLOCK]: {
33
+ Component: b,
34
+ usePrepare: t,
40
35
  title: () => "Action Block Layer Drilldown"
41
36
  },
42
- [r.MAKE_TRANSFER_ACTION_BLOCK]: {
43
- Component: I,
44
- usePrepare: e,
45
- title: h
37
+ [o.MAKE_TRANSFER_ACTION_BLOCK]: {
38
+ Component: h,
39
+ usePrepare: t,
40
+ title: I
46
41
  },
47
- [r.TRANSCATION_HISTORY_ACTION_BLOCK]: {
42
+ [o.TRANSCATION_HISTORY_ACTION_BLOCK]: {
48
43
  Component: L,
49
- usePrepare: e,
50
- title: d
44
+ usePrepare: t,
45
+ title: g
51
46
  },
52
- [r.TRANSACTION_DETAILS_ACTION_BLOCK]: {
53
- Component: g,
47
+ [o.TRANSACTION_DETAILS_ACTION_BLOCK]: {
48
+ Component: d,
54
49
  usePrepare: a,
55
50
  title: () => "Transaction Details"
56
51
  },
57
- [r.RECURRING_TRANSACTION_ACTION_BLOCK]: {
58
- Component: S,
52
+ [o.RECURRING_TRANSACTION_ACTION_BLOCK]: {
53
+ Component: P,
59
54
  usePrepare: i,
60
- title: (t, o) => o.recurring?.expense_details ?? ""
55
+ title: (e, r) => r.recurring?.expense_details ?? ""
61
56
  },
62
- [r.REMAINING_CASH_ACTION_BLOCK]: {
63
- Component: P,
57
+ [o.REMAINING_CASH_ACTION_BLOCK]: {
58
+ Component: D,
64
59
  usePrepare: i,
65
- title: (t, o) => o.insights_feed?.remaining_cash?.title ?? ""
60
+ title: (e, r) => r.insights_feed?.remaining_cash?.title ?? ""
66
61
  },
67
- [r.CONTACT_SUPPORT_ACTION_BLOCK]: {
68
- Component: l,
69
- usePrepare: e,
70
- title: (t, o) => o.help?.request_support ?? ""
62
+ [o.CONTACT_SUPPORT_ACTION_BLOCK]: {
63
+ Component: m,
64
+ usePrepare: t,
65
+ title: (e, r) => r.help?.request_support ?? ""
71
66
  },
72
- [r.TRANSACTION_LIST_ACTION_BLOCK]: {
67
+ [o.TRANSACTION_LIST_ACTION_BLOCK]: {
73
68
  Component: R,
74
69
  usePrepare: a,
75
- title: (t, o, c) => c?.insights_payload?.title ?? ""
70
+ title: (e, r, c) => c?.insights_payload?.title ?? ""
71
+ },
72
+ [o.AMOUNT_INPUT_MODAL_ACTION_BLOCK]: {
73
+ Component: S,
74
+ presentation: "modal",
75
+ usePrepare: t
76
76
  }
77
- }, lo = {
77
+ }, no = {
78
78
  account_block: k,
79
79
  account_switch_block: C,
80
80
  direct_deposit_block: B,
81
- bar_chart_block: n,
81
+ bar_chart_block: l,
82
82
  budget_block: T,
83
- donut_chart_block: p,
84
- double_bar_chart_block: m,
83
+ donut_chart_block: _,
84
+ double_bar_chart_block: n,
85
85
  double_progress_bar_block: s,
86
86
  image_block: O,
87
- save_extra_block: f,
88
- step_chart_block: _,
89
- svg_image_block: b,
90
- withdrawal_block: A,
91
- transaction_block: u
87
+ save_extra_block: A,
88
+ step_chart_block: p,
89
+ svg_image_block: N,
90
+ withdrawal_block: u,
91
+ transaction_block: f
92
92
  };
93
93
  export {
94
- co as actionBlocks,
95
- lo as blocks,
96
- io as hasDrawerBlock
94
+ lo as actionBlocks,
95
+ no as blocks,
96
+ co as hasDrawerBlock
97
97
  };
98
98
 
99
99
  //# sourceMappingURL=blocks.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.es.js","names":[],"sources":["../../../src/insights-v2/mappings/blocks.ts"],"sourcesContent":["import {\n AccountBlock,\n AccountSwitchBlock,\n DirectDepositBlock,\n BarchartBlock,\n BudgetBlock,\n DonutChartBlock,\n DoubleBarChartBlock,\n DoubleProgressBarBlock,\n SaveExtraBlock,\n SVGImageBlock,\n WithdrawalBlock,\n TransactionBlock,\n} from '../blocks'\nimport StepChartBlock from '../blocks/ChartBlocks/StepChartBlock'\nimport ImageBlock from '../blocks/ImageBlocks/ImageBlock'\n\nimport type { Beat, GlobalCopy, InsightsPayload } from '../../core'\nimport { ContactSupportActionBlock } from '../actionBlocks/ContactSupportActionBlock'\nimport { PlaceholderActionBlock } from '../actionBlocks/PlaceholderActionBlock'\nimport { MakeTransferActionBlockLayer } from '../actionBlocks/MakeTransferActionBlock/MakeTransferActionBlockLayer'\nimport { getMakeTransferDrawerTitle } from '../actionBlocks/MakeTransferActionBlock/layout'\nimport { TransactionHistoryBlock } from '../actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock'\nimport { getDrawerHeaderTitle } from '../actionBlocks/TransactionHistoryBlock/utils'\nimport {\n TransactionDetailsBlock,\n useTransactionDetailsPrepare,\n} from '../actionBlocks/TransactionDetailsBlock'\nimport {\n RecurringTransactionBlock,\n useRecurringTransactionPrepare,\n} from '../actionBlocks/RecurringTransactionBlock'\nimport { TransactionListBlock } from '../actionBlocks/TransactionListBlock'\nimport { RemainingCashBlock } from '../actionBlocks/RemainingCashBlock'\n// Keys live in ./actionBlocksKey (imported directly by chart blocks/tests too) to avoid a\n// circular import between this module and the chart blocks — see that file for details.\nimport { actionBlocksKey } from './actionBlocksKey'\n\n// Drilldown indexes are not passed down: action blocks read them from\n// InsightsV2Store (useInsightsV2Store().actionDrawer).\nexport interface ActionBlockProps {\n beat: Beat\n}\n\nconst noop = (_: ActionBlockProps) => {}\n\n// A CTA opens a drawer only when it carries a real `block`. The backend may send\n// an empty/absent block (or the literal strings \"null\"/\"undefined\") for CTAs\n// that trigger something else (e.g. a postMessage) instead of a drilldown.\nconst NON_BLOCK_VALUES = new Set(['', 'null', 'undefined'])\nexport const hasDrawerBlock = (block?: string | null): boolean => {\n const normalized = block?.trim().toLowerCase()\n return !!normalized && !NON_BLOCK_VALUES.has(normalized)\n}\n\nexport interface ActionBlockEntry {\n Component: React.ComponentType<ActionBlockProps>\n usePrepare: (props: ActionBlockProps) => void // required; default to noop in each entry\n // Resolves the drawer title from the clicked CTA's variant + copy, with the beat available\n // for blocks that title from beat fields (e.g. transaction-list reads insights_payload.title).\n // Falls back to the CTA label when the resolver returns an empty string / is absent.\n title?: (variant: string | null | undefined, copy: GlobalCopy, beat?: Beat) => string\n}\n\n// Registry of drawer drilldowns, keyed by the `block` name the backend sends\n// on a beat's CTA. To add one: create a component taking ActionBlockProps,\n// read the clicked indexes from useInsightsV2Store().actionDrawer, and\n// register it here. Add\n// `usePrepare` when the block needs to load data before rendering.\n\nexport const actionBlocks = {\n [actionBlocksKey.PLACEHOLDER_ACTION_BLOCK]: {\n Component: PlaceholderActionBlock,\n usePrepare: noop,\n title: () => 'Action Block Layer Drilldown',\n },\n [actionBlocksKey.MAKE_TRANSFER_ACTION_BLOCK]: {\n Component: MakeTransferActionBlockLayer,\n usePrepare: noop,\n title: getMakeTransferDrawerTitle,\n },\n [actionBlocksKey.TRANSCATION_HISTORY_ACTION_BLOCK]: {\n Component: TransactionHistoryBlock,\n usePrepare: noop,\n title: getDrawerHeaderTitle,\n },\n [actionBlocksKey.TRANSACTION_DETAILS_ACTION_BLOCK]: {\n Component: TransactionDetailsBlock,\n usePrepare: useTransactionDetailsPrepare,\n title: () => 'Transaction Details',\n },\n // Expense Details for one recurring subscription. Which subscription a beat names is resolved in\n // `RecurringTransactionBlock/subscriptionBeats.ts`, not through this registry.\n [actionBlocksKey.RECURRING_TRANSACTION_ACTION_BLOCK]: {\n Component: RecurringTransactionBlock,\n usePrepare: useRecurringTransactionPrepare,\n title: (_variant, copy) => copy.recurring?.expense_details ?? '',\n },\n // Remaining Cash for the Payday Cash Left Over Status beat. Store-driven: reuses the recurring\n // loader, which also loads accounts and categories, so CashflowStore's computeds are ready.\n [actionBlocksKey.REMAINING_CASH_ACTION_BLOCK]: {\n Component: RemainingCashBlock,\n usePrepare: useRecurringTransactionPrepare,\n title: (_variant, copy) => copy.insights_feed?.remaining_cash?.title ?? '',\n },\n // The block renders its own drawer (the help module's RequestSupport) on top of the action\n // drawer, so the action drawer's title is the CTA label.\n [actionBlocksKey.CONTACT_SUPPORT_ACTION_BLOCK]: {\n Component: ContactSupportActionBlock,\n usePrepare: noop,\n title: (_variant, copy) => copy.help?.request_support ?? '',\n },\n [actionBlocksKey.TRANSACTION_LIST_ACTION_BLOCK]: {\n Component: TransactionListBlock,\n usePrepare: useTransactionDetailsPrepare,\n title: (_variant, _copy, beat) => beat?.insights_payload?.title ?? '',\n },\n} as Record<string, ActionBlockEntry>\n\nexport const blocks = {\n account_block: AccountBlock,\n account_switch_block: AccountSwitchBlock,\n direct_deposit_block: DirectDepositBlock,\n bar_chart_block: BarchartBlock,\n budget_block: BudgetBlock,\n donut_chart_block: DonutChartBlock,\n double_bar_chart_block: DoubleBarChartBlock,\n double_progress_bar_block: DoubleProgressBarBlock,\n image_block: ImageBlock,\n save_extra_block: SaveExtraBlock,\n step_chart_block: StepChartBlock,\n svg_image_block: SVGImageBlock,\n withdrawal_block: WithdrawalBlock,\n transaction_block: TransactionBlock,\n} as Record<string, React.ComponentType<{ payload: InsightsPayload; beatGuid?: string }>>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAM,IAAA,CAAQ,MAAwB;AAAC,GAKjC,IAAmB,oBAAI,IAAI;AAAA,EAAC;AAAA,EAAI;AAAA,EAAQ;AAAW,CAAC,GAC7C,KAAA,CAAkB,MAAmC;AAChE,QAAM,IAAa,GAAO,KAAK,EAAE,YAAY;AAC7C,SAAO,CAAC,CAAC,KAAc,CAAC,EAAiB,IAAI,CAAU;AACzD,GAiBa,KAAe;AAAA,EACzB,CAAA,EAAgB,wBAAA,GAA2B;AAAA,IAC1C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,MAAa;AAAA,EACf;AAAA,EACC,CAAA,EAAgB,0BAAA,GAA6B;AAAA,IAC5C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACC,CAAA,EAAgB,gCAAA,GAAmC;AAAA,IAClD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACC,CAAA,EAAgB,gCAAA,GAAmC;AAAA,IAClD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,MAAa;AAAA,EACf;AAAA,EAGC,CAAA,EAAgB,kCAAA,GAAqC;AAAA,IACpD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,MAAS,EAAK,WAAW,mBAAmB;AAAA,EAChE;AAAA,EAGC,CAAA,EAAgB,2BAAA,GAA8B;AAAA,IAC7C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,MAAS,EAAK,eAAe,gBAAgB,SAAS;AAAA,EAC1E;AAAA,EAGC,CAAA,EAAgB,4BAAA,GAA+B;AAAA,IAC9C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,MAAS,EAAK,MAAM,mBAAmB;AAAA,EAC3D;AAAA,EACC,CAAA,EAAgB,6BAAA,GAAgC;AAAA,IAC/C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,GAAO,MAAS,GAAM,kBAAkB,SAAS;AAAA,EACrE;AACF,GAEa,KAAS;AAAA,EACpB,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA,EAC3B,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,mBAAmB;AACrB"}
1
+ {"version":3,"file":"blocks.es.js","names":[],"sources":["../../../src/insights-v2/mappings/blocks.ts"],"sourcesContent":["import {\n AccountBlock,\n AccountSwitchBlock,\n DirectDepositBlock,\n BarchartBlock,\n BudgetBlock,\n DonutChartBlock,\n DoubleBarChartBlock,\n DoubleProgressBarBlock,\n SaveExtraBlock,\n SVGImageBlock,\n WithdrawalBlock,\n TransactionBlock,\n} from '../blocks'\nimport StepChartBlock from '../blocks/ChartBlocks/StepChartBlock'\nimport ImageBlock from '../blocks/ImageBlocks/ImageBlock'\n\nimport type { Beat, GlobalCopy, InsightsPayload } from '../../core'\nimport { ContactSupportActionBlock } from '../actionBlocks/ContactSupportActionBlock'\nimport { PlaceholderActionBlock } from '../actionBlocks/PlaceholderActionBlock'\nimport { MakeTransferActionBlockLayer } from '../actionBlocks/MakeTransferActionBlock/MakeTransferActionBlockLayer'\nimport { getMakeTransferDrawerTitle } from '../actionBlocks/MakeTransferActionBlock/layout'\nimport { TransactionHistoryBlock } from '../actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock'\nimport { getDrawerHeaderTitle } from '../actionBlocks/TransactionHistoryBlock/utils'\nimport {\n TransactionDetailsBlock,\n useTransactionDetailsPrepare,\n} from '../actionBlocks/TransactionDetailsBlock'\nimport {\n RecurringTransactionBlock,\n useRecurringTransactionPrepare,\n} from '../actionBlocks/RecurringTransactionBlock'\nimport { TransactionListBlock } from '../actionBlocks/TransactionListBlock'\nimport { RemainingCashBlock } from '../actionBlocks/RemainingCashBlock'\nimport { AmountInputModalBlock } from '../actionBlocks/AmountInputModalBlock'\n// Keys live in ./actionBlocksKey (imported directly by chart blocks/tests too) to avoid a\n// circular import between this module and the chart blocks — see that file for details.\nimport { actionBlocksKey } from './actionBlocksKey'\n\n// Drilldown indexes are not passed down: action blocks read them from\n// InsightsV2Store (useInsightsV2Store().actionDrawer).\nexport interface ActionBlockProps {\n beat: Beat\n}\n\nconst noop = (_: ActionBlockProps) => {}\n\n// Re-exported so existing `hasDrawerBlock` imports from this module keep working.\nexport { hasDrawerBlock } from './hasDrawerBlock'\n\nexport interface ActionBlockEntry {\n Component: React.ComponentType<ActionBlockProps>\n usePrepare: (props: ActionBlockProps) => void // required; default to noop in each entry\n // Resolves the drawer title from the clicked CTA's variant + copy, with the beat available\n // for blocks that title from beat fields (e.g. transaction-list reads insights_payload.title).\n // Falls back to the CTA label when the resolver returns an empty string / is absent.\n title?: (variant: string | null | undefined, copy: GlobalCopy, beat?: Beat) => string\n // 'modal' blocks render their own dialog, so the layer skips the Drawer (and its title) and mounts\n // the block only while it is open for this beat. Defaults to 'drawer'.\n presentation?: 'drawer' | 'modal'\n}\n\n// Registry of drawer drilldowns, keyed by the `block` name the backend sends\n// on a beat's CTA. To add one: create a component taking ActionBlockProps,\n// read the clicked indexes from useInsightsV2Store().actionDrawer, and\n// register it here. Add\n// `usePrepare` when the block needs to load data before rendering.\n\nexport const actionBlocks = {\n [actionBlocksKey.PLACEHOLDER_ACTION_BLOCK]: {\n Component: PlaceholderActionBlock,\n usePrepare: noop,\n title: () => 'Action Block Layer Drilldown',\n },\n [actionBlocksKey.MAKE_TRANSFER_ACTION_BLOCK]: {\n Component: MakeTransferActionBlockLayer,\n usePrepare: noop,\n title: getMakeTransferDrawerTitle,\n },\n [actionBlocksKey.TRANSCATION_HISTORY_ACTION_BLOCK]: {\n Component: TransactionHistoryBlock,\n usePrepare: noop,\n title: getDrawerHeaderTitle,\n },\n [actionBlocksKey.TRANSACTION_DETAILS_ACTION_BLOCK]: {\n Component: TransactionDetailsBlock,\n usePrepare: useTransactionDetailsPrepare,\n title: () => 'Transaction Details',\n },\n // Expense Details for one recurring subscription. Which subscription a beat names is resolved in\n // `RecurringTransactionBlock/subscriptionBeats.ts`, not through this registry.\n [actionBlocksKey.RECURRING_TRANSACTION_ACTION_BLOCK]: {\n Component: RecurringTransactionBlock,\n usePrepare: useRecurringTransactionPrepare,\n title: (_variant, copy) => copy.recurring?.expense_details ?? '',\n },\n // Remaining Cash for the Payday Cash Left Over Status beat. Store-driven: reuses the recurring\n // loader, which also loads accounts and categories, so CashflowStore's computeds are ready.\n [actionBlocksKey.REMAINING_CASH_ACTION_BLOCK]: {\n Component: RemainingCashBlock,\n usePrepare: useRecurringTransactionPrepare,\n title: (_variant, copy) => copy.insights_feed?.remaining_cash?.title ?? '',\n },\n // The block renders its own drawer (the help module's RequestSupport) on top of the action\n // drawer, so the action drawer's title is the CTA label.\n [actionBlocksKey.CONTACT_SUPPORT_ACTION_BLOCK]: {\n Component: ContactSupportActionBlock,\n usePrepare: noop,\n title: (_variant, copy) => copy.help?.request_support ?? '',\n },\n [actionBlocksKey.TRANSACTION_LIST_ACTION_BLOCK]: {\n Component: TransactionListBlock,\n usePrepare: useTransactionDetailsPrepare,\n title: (_variant, _copy, beat) => beat?.insights_payload?.title ?? '',\n },\n // One whole-dollar setting in a dialog; the CTA variant picks which (low-balance threshold or\n // safety net). A modal: no drawer, so no drawer title.\n [actionBlocksKey.AMOUNT_INPUT_MODAL_ACTION_BLOCK]: {\n Component: AmountInputModalBlock,\n presentation: 'modal',\n // Loads inside the block: it mounts only while open, so mounting is the open-time load.\n usePrepare: noop,\n },\n} as Record<string, ActionBlockEntry>\n\nexport const blocks = {\n account_block: AccountBlock,\n account_switch_block: AccountSwitchBlock,\n direct_deposit_block: DirectDepositBlock,\n bar_chart_block: BarchartBlock,\n budget_block: BudgetBlock,\n donut_chart_block: DonutChartBlock,\n double_bar_chart_block: DoubleBarChartBlock,\n double_progress_bar_block: DoubleProgressBarBlock,\n image_block: ImageBlock,\n save_extra_block: SaveExtraBlock,\n step_chart_block: StepChartBlock,\n svg_image_block: SVGImageBlock,\n withdrawal_block: WithdrawalBlock,\n transaction_block: TransactionBlock,\n} as Record<string, React.ComponentType<{ payload: InsightsPayload; beatGuid?: string }>>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAM,IAAA,CAAQ,MAAwB;AAAC,GAuB1B,KAAe;AAAA,EACzB,CAAA,EAAgB,wBAAA,GAA2B;AAAA,IAC1C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,MAAa;AAAA,EACf;AAAA,EACC,CAAA,EAAgB,0BAAA,GAA6B;AAAA,IAC5C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACC,CAAA,EAAgB,gCAAA,GAAmC;AAAA,IAClD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAO;AAAA,EACT;AAAA,EACC,CAAA,EAAgB,gCAAA,GAAmC;AAAA,IAClD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,MAAa;AAAA,EACf;AAAA,EAGC,CAAA,EAAgB,kCAAA,GAAqC;AAAA,IACpD,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,MAAS,EAAK,WAAW,mBAAmB;AAAA,EAChE;AAAA,EAGC,CAAA,EAAgB,2BAAA,GAA8B;AAAA,IAC7C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,MAAS,EAAK,eAAe,gBAAgB,SAAS;AAAA,EAC1E;AAAA,EAGC,CAAA,EAAgB,4BAAA,GAA+B;AAAA,IAC9C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,MAAS,EAAK,MAAM,mBAAmB;AAAA,EAC3D;AAAA,EACC,CAAA,EAAgB,6BAAA,GAAgC;AAAA,IAC/C,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAA,CAAQ,GAAU,GAAO,MAAS,GAAM,kBAAkB,SAAS;AAAA,EACrE;AAAA,EAGC,CAAA,EAAgB,+BAAA,GAAkC;AAAA,IACjD,WAAW;AAAA,IACX,cAAc;AAAA,IAEd,YAAY;AAAA,EACd;AACF,GAEa,KAAS;AAAA,EACpB,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA,EAC3B,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,mBAAmB;AACrB"}
@@ -0,0 +1 @@
1
+ export declare const hasDrawerBlock: (block?: string | null) => boolean;
@@ -0,0 +1,13 @@
1
+ var a = /* @__PURE__ */ new Set([
2
+ "",
3
+ "null",
4
+ "undefined"
5
+ ]), n = (r) => {
6
+ const e = r?.trim().toLowerCase();
7
+ return !!e && !a.has(e);
8
+ };
9
+ export {
10
+ n as hasDrawerBlock
11
+ };
12
+
13
+ //# sourceMappingURL=hasDrawerBlock.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasDrawerBlock.es.js","names":[],"sources":["../../../src/insights-v2/mappings/hasDrawerBlock.ts"],"sourcesContent":["// Kept out of `blocks.ts` for the same reason as `actionBlocksKey.ts`: `ctaActions` needs it, and\n// action blocks import `ctaActions` (for in-drawer saves), so importing it from the registry module\n// would close a cycle back into `blocks.ts` while it is still initialising.\n\n// A CTA opens a drawer only when it carries a real `block`. The backend may send\n// an empty/absent block (or the literal strings \"null\"/\"undefined\") for CTAs\n// that trigger something else (e.g. a postMessage) instead of a drilldown.\nconst NON_BLOCK_VALUES = new Set(['', 'null', 'undefined'])\nexport const hasDrawerBlock = (block?: string | null): boolean => {\n const normalized = block?.trim().toLowerCase()\n return !!normalized && !NON_BLOCK_VALUES.has(normalized)\n}\n"],"mappings":"AAOA,IAAM,IAAmB,oBAAI,IAAI;AAAA,EAAC;AAAA,EAAI;AAAA,EAAQ;AAAW,CAAC,GAC7C,IAAA,CAAkB,MAAmC;AAChE,QAAM,IAAa,GAAO,KAAK,EAAE,YAAY;AAC7C,SAAO,CAAC,CAAC,KAAc,CAAC,EAAiB,IAAI,CAAU;AACzD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mx-cartographer/experiences",
3
- "version": "9.5.3",
3
+ "version": "9.5.4",
4
4
  "description": "Library containing experience widgets",
5
5
  "author": "MX",
6
6
  "license": "MIT",