@mx-cartographer/experiences 9.6.13 → 9.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/common/components/CurrencyDialog.d.ts +6 -1
- package/dist/common/components/CurrencyDialog.es.js +24 -23
- package/dist/common/components/CurrencyDialog.es.js.map +1 -1
- package/dist/insights-v2/actionBlocks/ActionBlockLayer.es.js +3 -3
- package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js +64 -59
- package/dist/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.es.js.map +1 -1
- package/dist/insights-v2/actionBlocks/ConnectWidgetActionBlock.es.js +17 -11
- package/dist/insights-v2/actionBlocks/ConnectWidgetActionBlock.es.js.map +1 -1
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js +63 -57
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js.map +1 -1
- package/dist/insights-v2/analytics/analyticsEventsConfig.d.ts +67 -0
- package/dist/insights-v2/analytics/analyticsEventsConfig.es.js +59 -0
- package/dist/insights-v2/analytics/analyticsEventsConfig.es.js.map +1 -0
- package/dist/insights-v2/analytics/index.d.ts +3 -0
- package/dist/insights-v2/analytics/resolveAnalyticsEvent.d.ts +5 -0
- package/dist/insights-v2/analytics/resolveAnalyticsEvent.es.js +19 -0
- package/dist/insights-v2/analytics/resolveAnalyticsEvent.es.js.map +1 -0
- package/dist/insights-v2/analytics/useAnalyticsEvent.d.ts +3 -0
- package/dist/insights-v2/analytics/useAnalyticsEvent.es.js +20 -0
- package/dist/insights-v2/analytics/useAnalyticsEvent.es.js.map +1 -0
- package/dist/insights-v2/beatActions.d.ts +18 -13
- package/dist/insights-v2/beatActions.es.js.map +1 -1
- package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/BarchartBlock.es.js +24 -22
- package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/BarchartBlock.es.js.map +1 -1
- package/dist/insights-v2/components/BaseLayout.es.js +89 -79
- package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/ctaHandlers.d.ts +1 -6
- package/dist/insights-v2/ctaActions/ctaHandlers.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/index.d.ts +1 -1
- package/dist/insights-v2/ctaActions/runCtaAction.d.ts +20 -5
- package/dist/insights-v2/ctaActions/runCtaAction.es.js +63 -41
- package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/types.d.ts +6 -5
- package/dist/insights-v2/index.es.js +84 -84
- package/dist/insights-v2/mappings/blocks.es.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [9.6.14] - 10-15-2026
|
|
2
|
+
|
|
3
|
+
- **ADDED** - `onAnalyticsEvent` callback on `BeatActions` (WEC-1849)
|
|
4
|
+
- **ADDED** - `src/insights-v2/analytics/` module: events config, resolver, `useAnalyticsEvent()` hook (WEC-1849)
|
|
5
|
+
- **ADDED** - analytics routed through ctaActions via `sendCtaAnalytics` (WEC-1849)
|
|
6
|
+
- **ADDED** - `PRIMARY_ACTION_SELECTED` / `SECONDARY_ACTION_SELECTED` / `FIRST_TIME_PRIMARY_ACTION_SELECTED` CTA press events (WEC-1849)
|
|
7
|
+
- **ADDED** - `CHART_BAR_CLICKED` / `DRAWER_CHART_BAR_CLICKED` / `TAB_SELECTED` chart analytics events (WEC-1849)
|
|
8
|
+
- **ADDED** - Declarative analytics trigger: `analytics` on `RunHandlerOptions` (WEC-1849)
|
|
9
|
+
- **ADDED** - LowAccountBalance + SavingsOpportunityV2 dialog analytics in `AmountInputModalBlock` (WEC-1849)
|
|
10
|
+
- **ADDED** - `OPENED_CONNECT_DRAWER` event in `ConnectWidgetActionBlock` (WEC-1849)
|
|
11
|
+
- **NOTE** - Scoped to Firefly analytics only (WEC-1849)
|
|
12
|
+
- **REFACTOR** - Collapsed the `sendCtaAnalytics` → fake-CTA → `ctaHandlers.sendAnalyticsEvent` chain into one function that resolves the payload and calls `onAnalyticsEvent` directly; removed the now-redundant runtime eventKey check (a typo'd key already fails typecheck). Kept `CtaStep.analytics` and the runner's `step.analytics` check for future `ctaConfig` entries (none use it yet; `RunHandlerOptions.analytics` has real callers today). Removed the `AnalyticsEvents` self-mapping constant — `sendCtaAnalytics`/`resolveAnalyticsEvent`/`useAnalyticsEvent()`/`RunHandlerOptions.analytics` now take the catalogue entry itself (e.g. `ANALYTICS_EVENTS_CONFIG.PRIMARY_ACTION_SELECTED`) instead of a lookup key, so there is nothing to look up and a typo'd property name fails typecheck at the call site. Dropped the unused `stores` parameter from `resolveAnalyticsEvent`/`postHog.properties`. Added `reportCtaClick` (press event + first-time bookkeeping together) and routed the `contact_support_block` bypass through it, so that CTA now also fires `FIRST_TIME_PRIMARY_ACTION_SELECTED` and records `cta_clicked_at` on its first press (WEC-1849)
|
|
13
|
+
|
|
1
14
|
## [9.6.13] - 10-15-2026
|
|
2
15
|
|
|
3
16
|
- **ADDED** - `ICO-<name>` logo guids: `logoFromEntityGuid` / `logoFromRef` map them to a filled mx-icons status icon in the logo's `color`, `LogoFromGuid` draws it and `isLogoGuid` accepts the prefix; `SubHeader` draws `logo_fill` icons filled. Fee Detected's header sends `ICO-error` (WEC-1885)
|
|
@@ -12,6 +12,11 @@ interface CurrencyDialogProps {
|
|
|
12
12
|
/** Disables the primary button on top of the built-in input check (e.g. while saving). */
|
|
13
13
|
isPrimaryDisabled?: boolean;
|
|
14
14
|
handleInputChange?: (value: string) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Click on the amount input (e.g. analytics). Click, not focus: the input autofocuses on open,
|
|
17
|
+
* so a focus callback would fire per open rather than per user interaction.
|
|
18
|
+
*/
|
|
19
|
+
handleInputClick?: () => void;
|
|
15
20
|
handleModalClose: () => void;
|
|
16
21
|
handlePrimaryAction: () => void;
|
|
17
22
|
handleSecondaryAction?: () => void;
|
|
@@ -23,5 +28,5 @@ interface CurrencyDialogProps {
|
|
|
23
28
|
subtitle?: ReactNode;
|
|
24
29
|
title: string;
|
|
25
30
|
}
|
|
26
|
-
declare const CurrencyDialog: ({ closeAriaLabel, content, errorText, inputLabel, inputValue, isOpen, isPrimaryDisabled, handleInputChange, handleModalClose, handlePrimaryAction, handleSecondaryAction, primaryColor, primaryText, secondaryColor, secondaryText, subtitle, title, }: CurrencyDialogProps) => import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
declare const CurrencyDialog: ({ closeAriaLabel, content, errorText, inputLabel, inputValue, isOpen, isPrimaryDisabled, handleInputChange, handleInputClick, handleModalClose, handlePrimaryAction, handleSecondaryAction, primaryColor, primaryText, secondaryColor, secondaryText, subtitle, title, }: CurrencyDialogProps) => import("react/jsx-runtime").JSX.Element;
|
|
27
32
|
export default CurrencyDialog;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { useScreenSize as
|
|
1
|
+
import { useScreenSize as S } from "../hooks/useScreenSize.es.js";
|
|
2
2
|
import n from "@mui/material/Stack";
|
|
3
|
-
import { H3 as
|
|
3
|
+
import { H3 as j, P as M } from "@mxenabled/mxui";
|
|
4
4
|
import f from "@mui/material/Button";
|
|
5
|
-
import { Icon as
|
|
5
|
+
import { Icon as $ } from "@mxenabled/mx-icons";
|
|
6
6
|
import { jsx as o, jsxs as t } from "react/jsx-runtime";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
var
|
|
14
|
-
const d = x || (r ? !(i !== "" && i !== "0" && !isNaN(parseFloat(i ?? "0"))) : !1), { isSmallMobile: I } =
|
|
15
|
-
return /* @__PURE__ */ o(
|
|
7
|
+
import B from "@mui/material/Dialog";
|
|
8
|
+
import F from "@mui/material/DialogContent";
|
|
9
|
+
import H from "@mui/material/IconButton";
|
|
10
|
+
import w from "@mui/material/DialogActions";
|
|
11
|
+
import A from "@mui/material/InputAdornment";
|
|
12
|
+
import N from "@mui/material/TextField";
|
|
13
|
+
var X = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValue: i, isOpen: u, isPrimaryDisabled: x = !1, handleInputChange: b, handleInputClick: C, handleModalClose: m, handlePrimaryAction: s, handleSecondaryAction: D, primaryColor: k = "primary", primaryText: p, secondaryColor: v = "primary", secondaryText: l, subtitle: y, title: c }) => {
|
|
14
|
+
const d = x || (r ? !(i !== "" && i !== "0" && !isNaN(parseFloat(i ?? "0"))) : !1), { isSmallMobile: I } = S();
|
|
15
|
+
return /* @__PURE__ */ o(B, {
|
|
16
16
|
"aria-label": c,
|
|
17
17
|
"aria-labelledby": "modal-title",
|
|
18
18
|
maxWidth: "xs",
|
|
@@ -23,7 +23,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
23
23
|
"& .MuiDialogContent-root": { p: 0 },
|
|
24
24
|
...I && { "& .MuiDialog-paper": { minWidth: "100%" } }
|
|
25
25
|
},
|
|
26
|
-
children: /* @__PURE__ */ o(
|
|
26
|
+
children: /* @__PURE__ */ o(F, { children: /* @__PURE__ */ t("form", {
|
|
27
27
|
onSubmit: (e) => {
|
|
28
28
|
e.preventDefault(), d || s();
|
|
29
29
|
},
|
|
@@ -41,11 +41,11 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
41
41
|
},
|
|
42
42
|
children: [/* @__PURE__ */ t(n, {
|
|
43
43
|
sx: { gap: 4 },
|
|
44
|
-
children: [/* @__PURE__ */ o(
|
|
44
|
+
children: [/* @__PURE__ */ o(j, {
|
|
45
45
|
id: "modal-title",
|
|
46
46
|
children: c
|
|
47
47
|
}), y]
|
|
48
|
-
}), /* @__PURE__ */ o(
|
|
48
|
+
}), /* @__PURE__ */ o(H, {
|
|
49
49
|
"aria-label": h,
|
|
50
50
|
onClick: m,
|
|
51
51
|
sx: {
|
|
@@ -54,7 +54,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
54
54
|
right: 8,
|
|
55
55
|
top: 18
|
|
56
56
|
},
|
|
57
|
-
children: /* @__PURE__ */ o(
|
|
57
|
+
children: /* @__PURE__ */ o($, {
|
|
58
58
|
name: "close",
|
|
59
59
|
sx: { color: "action.active" }
|
|
60
60
|
})
|
|
@@ -65,10 +65,10 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
65
65
|
gap: 24,
|
|
66
66
|
px: 24
|
|
67
67
|
},
|
|
68
|
-
children: [/* @__PURE__ */ o(
|
|
68
|
+
children: [/* @__PURE__ */ o(M, {
|
|
69
69
|
variant: "subtitle1",
|
|
70
70
|
children: g
|
|
71
|
-
}), r && /* @__PURE__ */ o(
|
|
71
|
+
}), r && /* @__PURE__ */ o(N, {
|
|
72
72
|
autoFocus: !0,
|
|
73
73
|
error: !!a,
|
|
74
74
|
helperText: a,
|
|
@@ -77,8 +77,9 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
77
77
|
onChange: ({ target: { value: e } }) => {
|
|
78
78
|
/^\d*\.?\d*$/.test(e) && b?.(e);
|
|
79
79
|
},
|
|
80
|
+
onClick: C,
|
|
80
81
|
slotProps: {
|
|
81
|
-
input: { startAdornment: /* @__PURE__ */ o(
|
|
82
|
+
input: { startAdornment: /* @__PURE__ */ o(A, {
|
|
82
83
|
position: "start",
|
|
83
84
|
children: "$"
|
|
84
85
|
}) },
|
|
@@ -93,7 +94,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
93
94
|
value: i
|
|
94
95
|
})]
|
|
95
96
|
}),
|
|
96
|
-
/* @__PURE__ */ t(
|
|
97
|
+
/* @__PURE__ */ t(w, {
|
|
97
98
|
sx: {
|
|
98
99
|
justifyContent: "flex-end",
|
|
99
100
|
gap: 16,
|
|
@@ -103,11 +104,11 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
103
104
|
children: [l && /* @__PURE__ */ o(f, {
|
|
104
105
|
"aria-label": l,
|
|
105
106
|
color: v,
|
|
106
|
-
onClick:
|
|
107
|
+
onClick: D,
|
|
107
108
|
children: l
|
|
108
109
|
}), /* @__PURE__ */ o(f, {
|
|
109
110
|
"aria-label": p,
|
|
110
|
-
color:
|
|
111
|
+
color: k,
|
|
111
112
|
disabled: d,
|
|
112
113
|
onClick: s,
|
|
113
114
|
variant: "contained",
|
|
@@ -119,7 +120,7 @@ var U = ({ closeAriaLabel: h, content: g, errorText: a, inputLabel: r, inputValu
|
|
|
119
120
|
});
|
|
120
121
|
};
|
|
121
122
|
export {
|
|
122
|
-
|
|
123
|
+
X as default
|
|
123
124
|
};
|
|
124
125
|
|
|
125
126
|
//# sourceMappingURL=CurrencyDialog.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CurrencyDialog.es.js","names":[],"sources":["../../../src/common/components/CurrencyDialog.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, P } from '@mxenabled/mxui'\n\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport Dialog from '@mui/material/Dialog'\nimport DialogActions from '@mui/material/DialogActions'\nimport DialogContent from '@mui/material/DialogContent'\nimport IconButton from '@mui/material/IconButton'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\n\nimport { useScreenSize } from '../hooks'\n\ninterface CurrencyDialogProps {\n closeAriaLabel: string\n /** Body copy; a node so callers can emphasise part of it (e.g. a bold account name). */\n content?: ReactNode\n /** Validation or save error, shown under the input and marking it invalid. */\n errorText?: string\n inputLabel?: string\n inputValue?: string\n isOpen: boolean\n /** Disables the primary button on top of the built-in input check (e.g. while saving). */\n isPrimaryDisabled?: boolean\n handleInputChange?: (value: string) => void\n handleModalClose: () => void\n handlePrimaryAction: () => void\n handleSecondaryAction?: () => void\n primaryColor?: ButtonProps['color']\n primaryText: string\n secondaryColor?: ButtonProps['color']\n secondaryText?: string\n /** A line under the title, e.g. the account a setting applies to. */\n subtitle?: ReactNode\n title: string\n}\n\n// Created for V2 of BudgetDetailsDrawer.\n// The previous design will be replaced once the new design is finalized.\n// Also needs to be updated to use the mxui library\n// with the updated Dialog components.\n\nconst CurrencyDialog = ({\n closeAriaLabel,\n content,\n errorText,\n inputLabel,\n inputValue,\n isOpen,\n isPrimaryDisabled = false,\n handleInputChange,\n handleModalClose,\n handlePrimaryAction,\n handleSecondaryAction,\n primaryColor = 'primary',\n primaryText,\n secondaryColor = 'primary',\n secondaryText,\n subtitle,\n title,\n}: CurrencyDialogProps) => {\n const numericValue = parseFloat(inputValue ?? '0')\n const isValid = inputValue !== '' && inputValue !== '0' && !isNaN(numericValue)\n // One rule for the button and for Enter: the form's submit must not bypass a disabled button.\n const isPrimaryBlocked = isPrimaryDisabled || (inputLabel ? !isValid : false)\n\n const { isSmallMobile } = useScreenSize()\n\n return (\n <Dialog\n aria-label={title}\n aria-labelledby=\"modal-title\"\n maxWidth=\"xs\"\n onClose={handleModalClose}\n open={isOpen}\n role=\"dialog\"\n sx={{\n '& .MuiDialogContent-root': { p: 0 },\n ...(isSmallMobile && { '& .MuiDialog-paper': { minWidth: '100%' } }),\n }}\n >\n <DialogContent>\n <form\n onSubmit={(e) => {\n e.preventDefault()\n if (!isPrimaryBlocked) handlePrimaryAction()\n }}\n >\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n justifyContent: 'space-between',\n pb: 12,\n pl: 24,\n position: 'relative',\n pr: 16,\n pt: 24,\n }}\n >\n <Stack sx={{ gap: 4 }}>\n <H3 id=\"modal-title\">{title}</H3>\n {subtitle}\n </Stack>\n <IconButton\n aria-label={closeAriaLabel}\n onClick={handleModalClose}\n sx={{ minHeight: 24, position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n </Stack>\n <Stack sx={{ gap: 24, px: 24 }}>\n <P variant=\"subtitle1\">{content}</P>\n {inputLabel && (\n <TextField\n autoFocus={true}\n error={!!errorText}\n helperText={errorText}\n inputMode=\"decimal\"\n label={inputLabel}\n onChange={({ target: { value } }) => {\n if (!/^\\d*\\.?\\d*$/.test(value)) return\n\n handleInputChange?.(value)\n }}\n slotProps={{\n input: {\n startAdornment: <InputAdornment position=\"start\">$</InputAdornment>,\n },\n // Announce errors: focus is usually on the primary button when one appears.\n formHelperText: { role: 'alert' },\n htmlInput: {\n 'aria-label': inputLabel,\n noValidate: true,\n pattern: '[0-9]*\\\\.?[0-9]*',\n },\n }}\n type=\"text\"\n value={inputValue}\n />\n )}\n </Stack>\n <DialogActions\n sx={{\n justifyContent: 'flex-end',\n gap: 16,\n p: 24,\n '>:not(style)~:not(style)': { ml: 0 },\n }}\n >\n {secondaryText && (\n <Button\n aria-label={secondaryText}\n color={secondaryColor}\n onClick={handleSecondaryAction}\n >\n {secondaryText}\n </Button>\n )}\n <Button\n aria-label={primaryText}\n color={primaryColor}\n disabled={isPrimaryBlocked}\n onClick={handlePrimaryAction}\n variant=\"contained\"\n >\n {primaryText}\n </Button>\n </DialogActions>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default CurrencyDialog\n"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"CurrencyDialog.es.js","names":[],"sources":["../../../src/common/components/CurrencyDialog.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, P } from '@mxenabled/mxui'\n\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport Dialog from '@mui/material/Dialog'\nimport DialogActions from '@mui/material/DialogActions'\nimport DialogContent from '@mui/material/DialogContent'\nimport IconButton from '@mui/material/IconButton'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\n\nimport { useScreenSize } from '../hooks'\n\ninterface CurrencyDialogProps {\n closeAriaLabel: string\n /** Body copy; a node so callers can emphasise part of it (e.g. a bold account name). */\n content?: ReactNode\n /** Validation or save error, shown under the input and marking it invalid. */\n errorText?: string\n inputLabel?: string\n inputValue?: string\n isOpen: boolean\n /** Disables the primary button on top of the built-in input check (e.g. while saving). */\n isPrimaryDisabled?: boolean\n handleInputChange?: (value: string) => void\n /**\n * Click on the amount input (e.g. analytics). Click, not focus: the input autofocuses on open,\n * so a focus callback would fire per open rather than per user interaction.\n */\n handleInputClick?: () => void\n handleModalClose: () => void\n handlePrimaryAction: () => void\n handleSecondaryAction?: () => void\n primaryColor?: ButtonProps['color']\n primaryText: string\n secondaryColor?: ButtonProps['color']\n secondaryText?: string\n /** A line under the title, e.g. the account a setting applies to. */\n subtitle?: ReactNode\n title: string\n}\n\n// Created for V2 of BudgetDetailsDrawer.\n// The previous design will be replaced once the new design is finalized.\n// Also needs to be updated to use the mxui library\n// with the updated Dialog components.\n\nconst CurrencyDialog = ({\n closeAriaLabel,\n content,\n errorText,\n inputLabel,\n inputValue,\n isOpen,\n isPrimaryDisabled = false,\n handleInputChange,\n handleInputClick,\n handleModalClose,\n handlePrimaryAction,\n handleSecondaryAction,\n primaryColor = 'primary',\n primaryText,\n secondaryColor = 'primary',\n secondaryText,\n subtitle,\n title,\n}: CurrencyDialogProps) => {\n const numericValue = parseFloat(inputValue ?? '0')\n const isValid = inputValue !== '' && inputValue !== '0' && !isNaN(numericValue)\n // One rule for the button and for Enter: the form's submit must not bypass a disabled button.\n const isPrimaryBlocked = isPrimaryDisabled || (inputLabel ? !isValid : false)\n\n const { isSmallMobile } = useScreenSize()\n\n return (\n <Dialog\n aria-label={title}\n aria-labelledby=\"modal-title\"\n maxWidth=\"xs\"\n onClose={handleModalClose}\n open={isOpen}\n role=\"dialog\"\n sx={{\n '& .MuiDialogContent-root': { p: 0 },\n ...(isSmallMobile && { '& .MuiDialog-paper': { minWidth: '100%' } }),\n }}\n >\n <DialogContent>\n <form\n onSubmit={(e) => {\n e.preventDefault()\n if (!isPrimaryBlocked) handlePrimaryAction()\n }}\n >\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n justifyContent: 'space-between',\n pb: 12,\n pl: 24,\n position: 'relative',\n pr: 16,\n pt: 24,\n }}\n >\n <Stack sx={{ gap: 4 }}>\n <H3 id=\"modal-title\">{title}</H3>\n {subtitle}\n </Stack>\n <IconButton\n aria-label={closeAriaLabel}\n onClick={handleModalClose}\n sx={{ minHeight: 24, position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n </Stack>\n <Stack sx={{ gap: 24, px: 24 }}>\n <P variant=\"subtitle1\">{content}</P>\n {inputLabel && (\n <TextField\n autoFocus={true}\n error={!!errorText}\n helperText={errorText}\n inputMode=\"decimal\"\n label={inputLabel}\n onChange={({ target: { value } }) => {\n if (!/^\\d*\\.?\\d*$/.test(value)) return\n\n handleInputChange?.(value)\n }}\n onClick={handleInputClick}\n slotProps={{\n input: {\n startAdornment: <InputAdornment position=\"start\">$</InputAdornment>,\n },\n // Announce errors: focus is usually on the primary button when one appears.\n formHelperText: { role: 'alert' },\n htmlInput: {\n 'aria-label': inputLabel,\n noValidate: true,\n pattern: '[0-9]*\\\\.?[0-9]*',\n },\n }}\n type=\"text\"\n value={inputValue}\n />\n )}\n </Stack>\n <DialogActions\n sx={{\n justifyContent: 'flex-end',\n gap: 16,\n p: 24,\n '>:not(style)~:not(style)': { ml: 0 },\n }}\n >\n {secondaryText && (\n <Button\n aria-label={secondaryText}\n color={secondaryColor}\n onClick={handleSecondaryAction}\n >\n {secondaryText}\n </Button>\n )}\n <Button\n aria-label={primaryText}\n color={primaryColor}\n disabled={isPrimaryBlocked}\n onClick={handlePrimaryAction}\n variant=\"contained\"\n >\n {primaryText}\n </Button>\n </DialogActions>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default CurrencyDialog\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useGlobalCopyStore as C, useGlobalUiStore as _, useInsightsV2Store as h } from "../../common/context/hooks.es.js";
|
|
2
2
|
import B from "../../common/components/drawer/Drawer.es.js";
|
|
3
3
|
import v from "../../common/components/Loader.es.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { hasDrawerBlock as w } from "../mappings/hasDrawerBlock.es.js";
|
|
5
|
+
import { getDynamicCtaBasedOnTriggers as D } from "./util.es.js";
|
|
6
6
|
import { actionBlocks as b } from "../mappings/blocks.es.js";
|
|
7
7
|
import { observer as L } from "mobx-react-lite";
|
|
8
8
|
import x from "@mui/material/Stack";
|
|
@@ -16,7 +16,7 @@ var m = ({ actionBlock: s, ...o }) => {
|
|
|
16
16
|
if (!o.insights_payload) return null;
|
|
17
17
|
const { primary_cta: u, secondary_cta: f, in_description_cta: k } = o.insights_payload, a = c.isDrawerOpen && c.guid === o.guid;
|
|
18
18
|
if (!p) return a ? /* @__PURE__ */ e(v, {}) : null;
|
|
19
|
-
const r =
|
|
19
|
+
const r = D(c, u, f, k), l = w(r?.block), t = l ? b[r?.block] : void 0;
|
|
20
20
|
if (t?.presentation === "modal") return a ? /* @__PURE__ */ e(m, {
|
|
21
21
|
actionBlock: t,
|
|
22
22
|
beat: o
|
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
import { buildDynamicCopy as
|
|
2
|
-
import
|
|
3
|
-
import { useGlobalCopyStore as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { useBeatActions as
|
|
7
|
-
import {
|
|
8
|
-
import { useCtaHandler as
|
|
9
|
-
import {
|
|
1
|
+
import { buildDynamicCopy as j } from "../../../core/utils/Localization.es.js";
|
|
2
|
+
import z from "../../../common/utils/AccountDisplayName.es.js";
|
|
3
|
+
import { useGlobalCopyStore as q, useInsightsV2Store as J, useSettingsStore as K, useUserStore as Q } from "../../../common/context/hooks.es.js";
|
|
4
|
+
import X from "../../../common/components/Loader.es.js";
|
|
5
|
+
import Z from "../../../common/components/CurrencyDialog.es.js";
|
|
6
|
+
import { useBeatActions as oo } from "../../beatActions.es.js";
|
|
7
|
+
import { ANALYTICS_EVENTS_CONFIG as c } from "../../analytics/analyticsEventsConfig.es.js";
|
|
8
|
+
import { useCtaHandler as to } from "../../ctaActions/useCtaAction.es.js";
|
|
9
|
+
import { useAnalyticsEvent as eo } from "../../analytics/useAnalyticsEvent.es.js";
|
|
10
|
+
import { getDynamicCtaBasedOnTriggers as io } from "../util.es.js";
|
|
11
|
+
import { AMOUNT_INPUT_VARIANTS as no, findLowBalanceProfile as ro, isAmountInputVariant as x, isWholeAmount as ao, toInputValue as so } from "./layout.es.js";
|
|
10
12
|
import r from "react";
|
|
11
|
-
import { observer as
|
|
12
|
-
import
|
|
13
|
-
import { H3 as
|
|
14
|
-
import { Icon as
|
|
13
|
+
import { observer as co } from "mobx-react-lite";
|
|
14
|
+
import P from "@mui/material/Stack";
|
|
15
|
+
import { H3 as lo, InstitutionLogo as uo, Text as H } from "@mxenabled/mxui";
|
|
16
|
+
import { Icon as mo } from "@mxenabled/mx-icons";
|
|
15
17
|
import { jsx as e, jsxs as k } from "react/jsx-runtime";
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
var
|
|
18
|
+
import po from "@mui/material/Dialog";
|
|
19
|
+
import _o from "@mui/material/IconButton";
|
|
20
|
+
var w = ({ children: I, closeAriaLabel: i, heading: o, onClose: l }) => {
|
|
19
21
|
const u = r.useId();
|
|
20
|
-
return /* @__PURE__ */ e(
|
|
22
|
+
return /* @__PURE__ */ e(po, {
|
|
21
23
|
"aria-labelledby": o ? u : void 0,
|
|
22
24
|
maxWidth: "xs",
|
|
23
25
|
onClose: l,
|
|
24
26
|
open: !0,
|
|
25
|
-
children: /* @__PURE__ */ k(
|
|
27
|
+
children: /* @__PURE__ */ k(P, {
|
|
26
28
|
sx: {
|
|
27
29
|
gap: 12,
|
|
28
30
|
p: 24,
|
|
29
31
|
position: "relative"
|
|
30
32
|
},
|
|
31
33
|
children: [
|
|
32
|
-
o && /* @__PURE__ */ e(
|
|
34
|
+
o && /* @__PURE__ */ e(lo, {
|
|
33
35
|
id: u,
|
|
34
36
|
children: o
|
|
35
37
|
}),
|
|
36
|
-
/* @__PURE__ */ e(
|
|
38
|
+
/* @__PURE__ */ e(_o, {
|
|
37
39
|
"aria-label": i || "Close",
|
|
38
40
|
onClick: l,
|
|
39
41
|
sx: {
|
|
@@ -41,88 +43,91 @@ var D = ({ children: y, closeAriaLabel: i, heading: o, onClose: l }) => {
|
|
|
41
43
|
right: 8,
|
|
42
44
|
top: 18
|
|
43
45
|
},
|
|
44
|
-
children: /* @__PURE__ */ e(
|
|
46
|
+
children: /* @__PURE__ */ e(mo, {
|
|
45
47
|
name: "close",
|
|
46
48
|
sx: { color: "action.active" }
|
|
47
49
|
})
|
|
48
50
|
}),
|
|
49
|
-
|
|
51
|
+
I
|
|
50
52
|
]
|
|
51
53
|
})
|
|
52
54
|
});
|
|
53
|
-
},
|
|
54
|
-
const { common: o, insights_feed: l } =
|
|
55
|
+
}, ko = co(function({ beat: i }) {
|
|
56
|
+
const { common: o, insights_feed: l } = q(), { actionDrawer: u, closeActionDrawer: m } = J(), f = K(), { userInsightProfile: v } = Q(), S = to(i, oo()), T = eo(), { primary_cta: B, secondary_cta: O, in_description_cta: V } = i.insights_payload ?? {}, s = io(u, B, O, V)?.variant, a = l?.amount_input_modal, t = s === no.LOW_BALANCE_THRESHOLD, h = t ? ro(f.notificationProfiles, i.primary_account_guid) : void 0, E = t ? h?.threshold : v?.savings_opportunity_minimum_checking_account_balance, [G, R] = r.useState(t), d = r.useRef(!0);
|
|
55
57
|
r.useEffect(() => (d.current = !0, () => {
|
|
56
58
|
d.current = !1;
|
|
57
59
|
}), []), r.useEffect(() => {
|
|
58
60
|
t && f.loadNotificationProfiles().finally(() => {
|
|
59
|
-
d.current &&
|
|
61
|
+
d.current && R(!1);
|
|
60
62
|
});
|
|
61
63
|
}, [t, f]);
|
|
62
|
-
const [
|
|
63
|
-
if (
|
|
64
|
+
const [F, M] = r.useState(null), [A, C] = r.useState(!1), [U, y] = r.useState(!1), p = F ?? so(E);
|
|
65
|
+
if (G) return /* @__PURE__ */ e(w, {
|
|
64
66
|
closeAriaLabel: o?.close_aria,
|
|
65
|
-
onClose:
|
|
66
|
-
children: /* @__PURE__ */ e(
|
|
67
|
+
onClose: m,
|
|
68
|
+
children: /* @__PURE__ */ e(X, {})
|
|
67
69
|
});
|
|
68
|
-
const n =
|
|
69
|
-
if (!a || !n || !
|
|
70
|
-
return console.warn(`amount_input_modal_block: ${a ?
|
|
70
|
+
const n = x(s) ? a?.[s] : void 0, b = !!n?.title && !!n.description && !!n.input_label && !!a?.whole_number_error && !!a.save_error && !!o?.save_button && (!t || !!o.cancel_button) && !!o.close_aria;
|
|
71
|
+
if (!a || !n || !b || !(t ? h : v))
|
|
72
|
+
return console.warn(`amount_input_modal_block: ${a ? x(s) ? b ? t ? "no low-balance profile for the account" : "user insight profile not set by the host" : `copy incomplete for "${s}"` : `unknown variant "${s}"` : "copy namespace missing"}`), /* @__PURE__ */ e(w, {
|
|
71
73
|
closeAriaLabel: o?.close_aria,
|
|
72
74
|
heading: o?.error_heading,
|
|
73
|
-
onClose:
|
|
74
|
-
children: /* @__PURE__ */ e(
|
|
75
|
+
onClose: m,
|
|
76
|
+
children: /* @__PURE__ */ e(H, { children: o?.error_message })
|
|
75
77
|
});
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const D = () => {
|
|
79
|
+
T(t ? c.EDIT_INSIGHT_THRESHOLD_CANCELED : c.EDIT_SAFETY_NET_CANCEL), m();
|
|
80
|
+
}, L = !t && !!E, N = i.primary_account ? z(i.primary_account) : "", g = ao(p), $ = p !== "" && !g, W = async () => {
|
|
81
|
+
if (!g || A) return;
|
|
82
|
+
const _ = Number(p);
|
|
83
|
+
C(!0), y(!1);
|
|
84
|
+
const Y = t ? await S("updateNotificationProfileThreshold", {
|
|
85
|
+
profileGuid: h?.guid,
|
|
86
|
+
threshold: _
|
|
87
|
+
}, { analytics: c.EDIT_INSIGHT_THRESHOLD_SAVE }) : await S("updateUserInsightProfile", { fields: { savings_opportunity_minimum_checking_account_balance: _ } }, { analytics: c.EDIT_SAFETY_NET_SAVE });
|
|
88
|
+
d.current && (C(!1), Y ? m() : y(!0));
|
|
85
89
|
};
|
|
86
|
-
return /* @__PURE__ */ e(
|
|
90
|
+
return /* @__PURE__ */ e(Z, {
|
|
87
91
|
closeAriaLabel: o.close_aria,
|
|
88
|
-
content:
|
|
89
|
-
errorText:
|
|
90
|
-
handleInputChange: (
|
|
91
|
-
|
|
92
|
+
content: j(L && n.description_edit || n.description, /* @__PURE__ */ e("strong", { children: N }, "account")),
|
|
93
|
+
errorText: $ ? a.whole_number_error : U ? a.save_error : void 0,
|
|
94
|
+
handleInputChange: (_) => {
|
|
95
|
+
M(_), y(!1);
|
|
92
96
|
},
|
|
93
|
-
|
|
97
|
+
handleInputClick: t ? () => T(c.EDIT_INSIGHT_THRESHOLD_INPUT_SELECTED) : void 0,
|
|
98
|
+
handleModalClose: D,
|
|
94
99
|
handlePrimaryAction: () => {
|
|
95
|
-
|
|
100
|
+
W();
|
|
96
101
|
},
|
|
97
|
-
handleSecondaryAction:
|
|
102
|
+
handleSecondaryAction: D,
|
|
98
103
|
inputLabel: n.input_label,
|
|
99
|
-
inputValue:
|
|
104
|
+
inputValue: p,
|
|
100
105
|
isOpen: !0,
|
|
101
|
-
isPrimaryDisabled:
|
|
106
|
+
isPrimaryDisabled: A || !g,
|
|
102
107
|
primaryText: o.save_button,
|
|
103
108
|
secondaryText: t ? o.cancel_button : void 0,
|
|
104
|
-
subtitle: t && i.primary_account ? /* @__PURE__ */ k(
|
|
109
|
+
subtitle: t && i.primary_account ? /* @__PURE__ */ k(P, {
|
|
105
110
|
sx: {
|
|
106
111
|
alignItems: "center",
|
|
107
112
|
flexDirection: "row",
|
|
108
113
|
gap: 8
|
|
109
114
|
},
|
|
110
|
-
children: [/* @__PURE__ */ e(
|
|
115
|
+
children: [/* @__PURE__ */ e(uo, {
|
|
111
116
|
alt: "",
|
|
112
117
|
institutionGuid: i.primary_account.institution_guid ?? "",
|
|
113
118
|
size: 16
|
|
114
|
-
}), /* @__PURE__ */ e(
|
|
119
|
+
}), /* @__PURE__ */ e(H, {
|
|
115
120
|
color: "text.secondary",
|
|
116
121
|
uppercase: !0,
|
|
117
122
|
variant: "body2",
|
|
118
|
-
children:
|
|
123
|
+
children: N
|
|
119
124
|
})]
|
|
120
125
|
}) : void 0,
|
|
121
|
-
title:
|
|
126
|
+
title: L && n.title_edit || n.title
|
|
122
127
|
});
|
|
123
128
|
});
|
|
124
129
|
export {
|
|
125
|
-
|
|
130
|
+
ko as AmountInputModalBlock
|
|
126
131
|
};
|
|
127
132
|
|
|
128
133
|
//# sourceMappingURL=AmountInputModalBlock.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AmountInputModalBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.tsx"],"sourcesContent":["import React from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport MuiDialog from '@mui/material/Dialog'\nimport IconButton from '@mui/material/IconButton'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, InstitutionLogo, Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n CurrencyDialog,\n Loader,\n buildDynamicCopy,\n useGlobalCopyStore,\n useInsightsV2Store,\n useSettingsStore,\n useUserStore,\n} from '../../../common'\nimport getAccountDisplayName from '../../../common/utils/AccountDisplayName'\nimport { useBeatActions } from '../../beatActions'\nimport { useCtaHandler } from '../../ctaActions'\nimport type { ActionBlockProps } from '../../mappings/blocks'\nimport { getDynamicCtaBasedOnTriggers } from '../util'\n\nimport {\n AMOUNT_INPUT_VARIANTS,\n findLowBalanceProfile,\n isAmountInputVariant,\n isWholeAmount,\n toInputValue,\n} from './layout'\n\n/**\n * The dialog frame for the states that have no form: loading, and a non-retriable problem. Named by\n * its heading and closable by button, Esc and backdrop, like the form dialog.\n */\nconst StatusDialog = ({\n children,\n closeAriaLabel,\n heading,\n onClose,\n}: React.PropsWithChildren<{ closeAriaLabel?: string; heading?: string; onClose: () => void }>) => {\n const headingId = React.useId()\n\n return (\n <MuiDialog\n aria-labelledby={heading ? headingId : undefined}\n maxWidth=\"xs\"\n onClose={onClose}\n open={true}\n >\n <Stack sx={{ gap: 12, p: 24, position: 'relative' }}>\n {heading && <H3 id={headingId}>{heading}</H3>}\n <IconButton\n aria-label={closeAriaLabel || 'Close'}\n onClick={onClose}\n sx={{ position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n {children}\n </Stack>\n </MuiDialog>\n )\n}\n\n/**\n * `amount_input_modal_block`: one whole-dollar setting edited in a dialog (Figma 441:24913 and\n * 5402:34137). The CTA's variant picks what is edited:\n * - `low_balance_threshold`: the `threshold` on the beat account's low-balance alert profile;\n * - `safety_net`: `savings_opportunity_minimum_checking_account_balance` on the insight profile.\n *\n * Registered with `presentation: 'modal'`, so no drawer opens behind it; closing the dialog ends\n * the action-drawer session. `observer` is required: the starting values are read from stores.\n */\nexport const AmountInputModalBlock = observer(function AmountInputModalBlock({\n beat,\n}: ActionBlockProps) {\n const { common: commonCopy, insights_feed: insightsFeedCopy } = useGlobalCopyStore()\n const { actionDrawer, closeActionDrawer } = useInsightsV2Store()\n const settingsStore = useSettingsStore()\n const { userInsightProfile } = useUserStore()\n const runHandler = useCtaHandler(beat, useBeatActions())\n\n const { primary_cta, secondary_cta, in_description_cta } = beat.insights_payload ?? {}\n const variant = getDynamicCtaBasedOnTriggers(\n actionDrawer,\n primary_cta,\n secondary_cta,\n in_description_cta,\n )?.variant\n const copy = insightsFeedCopy?.amount_input_modal\n const isLowBalance = variant === AMOUNT_INPUT_VARIANTS.LOW_BALANCE_THRESHOLD\n\n const lowBalanceProfile = isLowBalance\n ? findLowBalanceProfile(settingsStore.notificationProfiles, beat.primary_account_guid)\n : undefined\n const savedAmount = isLowBalance\n ? lowBalanceProfile?.threshold\n : userInsightProfile?.savings_opportunity_minimum_checking_account_balance\n\n // Mounted only while open (modal presentation), so mounting is the open: profiles are reloaded\n // here on each open, so the threshold prefills as saved now. `SettingsStore` swallows a failed\n // request, which leaves no profile for the account: the missing-profile error below covers it.\n const [isLoadingProfiles, setIsLoadingProfiles] = React.useState(isLowBalance)\n // False once unmounted: a save that resolves after the user closed the dialog must not close\n // (or update) whatever action session is open by then.\n const isMounted = React.useRef(true)\n\n React.useEffect(() => {\n isMounted.current = true\n\n return () => {\n isMounted.current = false\n }\n }, [])\n\n React.useEffect(() => {\n if (!isLowBalance) return\n\n void settingsStore.loadNotificationProfiles().finally(() => {\n if (isMounted.current) setIsLoadingProfiles(false)\n })\n }, [isLowBalance, settingsStore])\n\n // `null` until the user types: the field shows the saved amount until then.\n const [typed, setTyped] = React.useState<string | null>(null)\n const [isSaving, setIsSaving] = React.useState(false)\n const [saveFailed, setSaveFailed] = React.useState(false)\n\n const value = typed ?? toInputValue(savedAmount)\n\n if (isLoadingProfiles) {\n return (\n <StatusDialog closeAriaLabel={commonCopy?.close_aria} onClose={closeActionDrawer}>\n <Loader />\n </StatusDialog>\n )\n }\n\n // Copy ships separately (see the doc's Rollout order), so check every string the form renders:\n // a partial ship falls to the error dialog instead of throwing mid-render.\n const variantCopy = isAmountInputVariant(variant) ? copy?.[variant] : undefined\n const hasCopy =\n !!variantCopy?.title &&\n !!variantCopy.description &&\n !!variantCopy.input_label &&\n !!copy?.whole_number_error &&\n !!copy.save_error &&\n !!commonCopy?.save_button &&\n // Cancel renders for the low-balance variant only.\n (!isLowBalance || !!commonCopy.cancel_button) &&\n !!commonCopy.close_aria\n const hasProfile = isLowBalance ? !!lowBalanceProfile : !!userInsightProfile\n\n if (!copy || !variantCopy || !hasCopy || !hasProfile) {\n // One message to the user; distinct warnings so a rollout problem can be traced to its cause.\n console.warn(\n `amount_input_modal_block: ${\n !copy\n ? 'copy namespace missing'\n : !isAmountInputVariant(variant)\n ? `unknown variant \"${variant}\"`\n : !hasCopy\n ? `copy incomplete for \"${variant}\"`\n : isLowBalance\n ? 'no low-balance profile for the account'\n : 'user insight profile not set by the host'\n }`,\n )\n\n return (\n <StatusDialog\n closeAriaLabel={commonCopy?.close_aria}\n heading={commonCopy?.error_heading}\n onClose={closeActionDrawer}\n >\n <Text>{commonCopy?.error_message}</Text>\n </StatusDialog>\n )\n }\n\n const isEdit = !isLowBalance && !!savedAmount\n const accountName = beat.primary_account ? getAccountDisplayName(beat.primary_account) : ''\n const isValid = isWholeAmount(value)\n const isInvalid = value !== '' && !isValid\n\n const handleSave = async () => {\n // Guarded here too, not only by the disabled button: Enter submits the dialog's form directly.\n if (!isValid || isSaving) return\n\n const amount = Number(value)\n\n setIsSaving(true)\n setSaveFailed(false)\n\n const ok = isLowBalance\n ? await runHandler('updateNotificationProfileThreshold', {\n profileGuid: lowBalanceProfile?.guid,\n threshold: amount,\n })\n : await runHandler('updateUserInsightProfile', {\n fields: { savings_opportunity_minimum_checking_account_balance: amount },\n })\n\n if (!isMounted.current) return\n\n setIsSaving(false)\n\n if (ok) closeActionDrawer()\n else setSaveFailed(true)\n }\n\n return (\n <CurrencyDialog\n closeAriaLabel={commonCopy.close_aria}\n // Only strings and the <strong> go in, so the result is renderable (the helper's type also\n // admits raw DOM nodes).\n content={\n buildDynamicCopy(\n (isEdit && variantCopy.description_edit) || variantCopy.description,\n <strong key=\"account\">{accountName}</strong>,\n ) as React.ReactNode\n }\n errorText={isInvalid ? copy.whole_number_error : saveFailed ? copy.save_error : undefined}\n handleInputChange={(next) => {\n setTyped(next)\n setSaveFailed(false)\n }}\n handleModalClose={closeActionDrawer}\n handlePrimaryAction={() => void handleSave()}\n handleSecondaryAction={closeActionDrawer}\n inputLabel={variantCopy.input_label}\n inputValue={value}\n isOpen={true}\n isPrimaryDisabled={isSaving || !isValid}\n primaryText={commonCopy.save_button}\n secondaryText={isLowBalance ? commonCopy.cancel_button : undefined}\n subtitle={\n isLowBalance && beat.primary_account ? (\n <Stack sx={{ alignItems: 'center', flexDirection: 'row', gap: 8 }}>\n {/* Decorative: the account name is the visible text beside it. */}\n <InstitutionLogo\n alt=\"\"\n institutionGuid={beat.primary_account.institution_guid ?? ''}\n size={16}\n />\n <Text color=\"text.secondary\" uppercase={true} variant=\"body2\">\n {accountName}\n </Text>\n </Stack>\n ) : undefined\n }\n title={(isEdit && variantCopy.title_edit) || variantCopy.title}\n />\n )\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AmountInputModalBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/AmountInputModalBlock/AmountInputModalBlock.tsx"],"sourcesContent":["import React from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport MuiDialog from '@mui/material/Dialog'\nimport IconButton from '@mui/material/IconButton'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, InstitutionLogo, Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n CurrencyDialog,\n Loader,\n buildDynamicCopy,\n useGlobalCopyStore,\n useInsightsV2Store,\n useSettingsStore,\n useUserStore,\n} from '../../../common'\nimport getAccountDisplayName from '../../../common/utils/AccountDisplayName'\nimport { ANALYTICS_EVENTS_CONFIG, useAnalyticsEvent } from '../../analytics'\nimport { useBeatActions } from '../../beatActions'\nimport { useCtaHandler } from '../../ctaActions'\nimport type { ActionBlockProps } from '../../mappings/blocks'\nimport { getDynamicCtaBasedOnTriggers } from '../util'\n\nimport {\n AMOUNT_INPUT_VARIANTS,\n findLowBalanceProfile,\n isAmountInputVariant,\n isWholeAmount,\n toInputValue,\n} from './layout'\n\n/**\n * The dialog frame for the states that have no form: loading, and a non-retriable problem. Named by\n * its heading and closable by button, Esc and backdrop, like the form dialog.\n */\nconst StatusDialog = ({\n children,\n closeAriaLabel,\n heading,\n onClose,\n}: React.PropsWithChildren<{ closeAriaLabel?: string; heading?: string; onClose: () => void }>) => {\n const headingId = React.useId()\n\n return (\n <MuiDialog\n aria-labelledby={heading ? headingId : undefined}\n maxWidth=\"xs\"\n onClose={onClose}\n open={true}\n >\n <Stack sx={{ gap: 12, p: 24, position: 'relative' }}>\n {heading && <H3 id={headingId}>{heading}</H3>}\n <IconButton\n aria-label={closeAriaLabel || 'Close'}\n onClick={onClose}\n sx={{ position: 'absolute', right: 8, top: 18 }}\n >\n <Icon name=\"close\" sx={{ color: 'action.active' }} />\n </IconButton>\n {children}\n </Stack>\n </MuiDialog>\n )\n}\n\n/**\n * `amount_input_modal_block`: one whole-dollar setting edited in a dialog (Figma 441:24913 and\n * 5402:34137). The CTA's variant picks what is edited:\n * - `low_balance_threshold`: the `threshold` on the beat account's low-balance alert profile;\n * - `safety_net`: `savings_opportunity_minimum_checking_account_balance` on the insight profile.\n *\n * Registered with `presentation: 'modal'`, so no drawer opens behind it; closing the dialog ends\n * the action-drawer session. `observer` is required: the starting values are read from stores.\n */\nexport const AmountInputModalBlock = observer(function AmountInputModalBlock({\n beat,\n}: ActionBlockProps) {\n const { common: commonCopy, insights_feed: insightsFeedCopy } = useGlobalCopyStore()\n const { actionDrawer, closeActionDrawer } = useInsightsV2Store()\n const settingsStore = useSettingsStore()\n const { userInsightProfile } = useUserStore()\n const runHandler = useCtaHandler(beat, useBeatActions())\n const fireAnalyticsEvent = useAnalyticsEvent()\n\n const { primary_cta, secondary_cta, in_description_cta } = beat.insights_payload ?? {}\n const variant = getDynamicCtaBasedOnTriggers(\n actionDrawer,\n primary_cta,\n secondary_cta,\n in_description_cta,\n )?.variant\n const copy = insightsFeedCopy?.amount_input_modal\n const isLowBalance = variant === AMOUNT_INPUT_VARIANTS.LOW_BALANCE_THRESHOLD\n\n const lowBalanceProfile = isLowBalance\n ? findLowBalanceProfile(settingsStore.notificationProfiles, beat.primary_account_guid)\n : undefined\n const savedAmount = isLowBalance\n ? lowBalanceProfile?.threshold\n : userInsightProfile?.savings_opportunity_minimum_checking_account_balance\n\n // Mounted only while open (modal presentation), so mounting is the open: profiles are reloaded\n // here on each open, so the threshold prefills as saved now. `SettingsStore` swallows a failed\n // request, which leaves no profile for the account: the missing-profile error below covers it.\n const [isLoadingProfiles, setIsLoadingProfiles] = React.useState(isLowBalance)\n // False once unmounted: a save that resolves after the user closed the dialog must not close\n // (or update) whatever action session is open by then.\n const isMounted = React.useRef(true)\n\n React.useEffect(() => {\n isMounted.current = true\n\n return () => {\n isMounted.current = false\n }\n }, [])\n\n React.useEffect(() => {\n if (!isLowBalance) return\n\n void settingsStore.loadNotificationProfiles().finally(() => {\n if (isMounted.current) setIsLoadingProfiles(false)\n })\n }, [isLowBalance, settingsStore])\n\n // `null` until the user types: the field shows the saved amount until then.\n const [typed, setTyped] = React.useState<string | null>(null)\n const [isSaving, setIsSaving] = React.useState(false)\n const [saveFailed, setSaveFailed] = React.useState(false)\n\n const value = typed ?? toInputValue(savedAmount)\n\n if (isLoadingProfiles) {\n return (\n <StatusDialog closeAriaLabel={commonCopy?.close_aria} onClose={closeActionDrawer}>\n <Loader />\n </StatusDialog>\n )\n }\n\n // Copy ships separately (see the doc's Rollout order), so check every string the form renders:\n // a partial ship falls to the error dialog instead of throwing mid-render.\n const variantCopy = isAmountInputVariant(variant) ? copy?.[variant] : undefined\n const hasCopy =\n !!variantCopy?.title &&\n !!variantCopy.description &&\n !!variantCopy.input_label &&\n !!copy?.whole_number_error &&\n !!copy.save_error &&\n !!commonCopy?.save_button &&\n // Cancel renders for the low-balance variant only.\n (!isLowBalance || !!commonCopy.cancel_button) &&\n !!commonCopy.close_aria\n const hasProfile = isLowBalance ? !!lowBalanceProfile : !!userInsightProfile\n\n if (!copy || !variantCopy || !hasCopy || !hasProfile) {\n // One message to the user; distinct warnings so a rollout problem can be traced to its cause.\n console.warn(\n `amount_input_modal_block: ${\n !copy\n ? 'copy namespace missing'\n : !isAmountInputVariant(variant)\n ? `unknown variant \"${variant}\"`\n : !hasCopy\n ? `copy incomplete for \"${variant}\"`\n : isLowBalance\n ? 'no low-balance profile for the account'\n : 'user insight profile not set by the host'\n }`,\n )\n\n return (\n <StatusDialog\n closeAriaLabel={commonCopy?.close_aria}\n heading={commonCopy?.error_heading}\n onClose={closeActionDrawer}\n >\n <Text>{commonCopy?.error_message}</Text>\n </StatusDialog>\n )\n }\n\n // Trigger 4 — user dismissed without saving (close icon, Cancel button, backdrop). Pulse\n // parity: EditThreshold.js close button / EditSafetyNet.js cancel button.\n const handleDismiss = () => {\n fireAnalyticsEvent(\n isLowBalance\n ? ANALYTICS_EVENTS_CONFIG.EDIT_INSIGHT_THRESHOLD_CANCELED\n : ANALYTICS_EVENTS_CONFIG.EDIT_SAFETY_NET_CANCEL,\n )\n closeActionDrawer()\n }\n\n const isEdit = !isLowBalance && !!savedAmount\n const accountName = beat.primary_account ? getAccountDisplayName(beat.primary_account) : ''\n const isValid = isWholeAmount(value)\n const isInvalid = value !== '' && !isValid\n\n const handleSave = async () => {\n // Guarded here too, not only by the disabled button: Enter submits the dialog's form directly.\n if (!isValid || isSaving) return\n\n const amount = Number(value)\n\n setIsSaving(true)\n setSaveFailed(false)\n\n // Trigger 2: save analytics ride the handler run, fired only on success.\n const ok = isLowBalance\n ? await runHandler(\n 'updateNotificationProfileThreshold',\n { profileGuid: lowBalanceProfile?.guid, threshold: amount },\n { analytics: ANALYTICS_EVENTS_CONFIG.EDIT_INSIGHT_THRESHOLD_SAVE },\n )\n : await runHandler(\n 'updateUserInsightProfile',\n { fields: { savings_opportunity_minimum_checking_account_balance: amount } },\n { analytics: ANALYTICS_EVENTS_CONFIG.EDIT_SAFETY_NET_SAVE },\n )\n\n if (!isMounted.current) return\n\n setIsSaving(false)\n\n if (ok) closeActionDrawer()\n else setSaveFailed(true)\n }\n\n return (\n <CurrencyDialog\n closeAriaLabel={commonCopy.close_aria}\n // Only strings and the <strong> go in, so the result is renderable (the helper's type also\n // admits raw DOM nodes).\n content={\n buildDynamicCopy(\n (isEdit && variantCopy.description_edit) || variantCopy.description,\n <strong key=\"account\">{accountName}</strong>,\n ) as React.ReactNode\n }\n errorText={isInvalid ? copy.whole_number_error : saveFailed ? copy.save_error : undefined}\n handleInputChange={(next) => {\n setTyped(next)\n setSaveFailed(false)\n }}\n handleInputClick={\n // Pulse fires input-selected for the threshold dialog only (EditThreshold.js); the\n // safety-net dialog has no input event.\n isLowBalance\n ? () => fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.EDIT_INSIGHT_THRESHOLD_INPUT_SELECTED)\n : undefined\n }\n handleModalClose={handleDismiss}\n handlePrimaryAction={() => void handleSave()}\n handleSecondaryAction={handleDismiss}\n inputLabel={variantCopy.input_label}\n inputValue={value}\n isOpen={true}\n isPrimaryDisabled={isSaving || !isValid}\n primaryText={commonCopy.save_button}\n secondaryText={isLowBalance ? commonCopy.cancel_button : undefined}\n subtitle={\n isLowBalance && beat.primary_account ? (\n <Stack sx={{ alignItems: 'center', flexDirection: 'row', gap: 8 }}>\n {/* Decorative: the account name is the visible text beside it. */}\n <InstitutionLogo\n alt=\"\"\n institutionGuid={beat.primary_account.institution_guid ?? ''}\n size={16}\n />\n <Text color=\"text.secondary\" uppercase={true} variant=\"body2\">\n {accountName}\n </Text>\n </Stack>\n ) : undefined\n }\n title={(isEdit && variantCopy.title_edit) || variantCopy.title}\n />\n )\n})\n"],"mappings":"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"}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { useAccountStore as
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { useAccountStore as u, useInsightsV2Store as m } from "../../common/context/hooks.es.js";
|
|
2
|
+
import a from "../../common/components/ConnectWidgetPanel.es.js";
|
|
3
|
+
import { ANALYTICS_EVENTS_CONFIG as p } from "../analytics/analyticsEventsConfig.es.js";
|
|
4
|
+
import { useAnalyticsEvent as A } from "../analytics/useAnalyticsEvent.es.js";
|
|
5
|
+
import { useEffect as c, useRef as i } from "react";
|
|
6
|
+
import { observer as E } from "mobx-react-lite";
|
|
7
|
+
import { jsx as C } from "react/jsx-runtime";
|
|
8
|
+
var S = E(function({ beat: s }) {
|
|
9
|
+
const { actionDrawer: e } = m(), { refreshAccounts: f } = u(), r = A(), t = e.isDrawerOpen && e.guid === s.guid, n = i(t);
|
|
10
|
+
c(() => {
|
|
11
|
+
n.current && !t && f(), n.current = t;
|
|
12
|
+
}, [t]);
|
|
13
|
+
const o = i(!1);
|
|
14
|
+
return c(() => {
|
|
15
|
+
t && !o.current && r(p.OPENED_CONNECT_DRAWER), o.current = t;
|
|
16
|
+
}, [t, r]), /* @__PURE__ */ C(a, { isActive: t });
|
|
11
17
|
});
|
|
12
18
|
export {
|
|
13
|
-
|
|
19
|
+
S as ConnectWidgetActionBlock
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
//# sourceMappingURL=ConnectWidgetActionBlock.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectWidgetActionBlock.es.js","names":[],"sources":["../../../src/insights-v2/actionBlocks/ConnectWidgetActionBlock.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { observer } from 'mobx-react-lite'\n\nimport { ConnectWidgetPanel, useAccountStore, useInsightsV2Store } from '../../common'\nimport type { ActionBlockProps } from '../mappings/blocks'\n\n/**\n * `connect_widget_block` — opens MX's Connect widget itself via the shared\n * `ConnectWidgetPanel` (src/common): SSO url via ConnectStore and the\n * iframe-based `@mxenabled/web-widget-sdk` ConnectWidget, with loading/error\n * statuses.\n *\n * Renders the panel directly (not `ConnectDrawer`, which wraps it in its own\n * `Drawer`) — `ActionBlockLayer` already supplies the action drawer's chrome\n * (title from the CTA label, close button), so a second nested `Drawer` here\n * would produce two stacked drawers with two headers and two close buttons.\n *\n * `ActionBlockLayer` does not unmount a drawer-presentation block when the\n * drawer closes — it only toggles the `Drawer`'s `isOpen` — so the account\n * refresh (parity with `ConnectDrawer`'s own `onClose`) is driven by watching\n * this beat's `actionDrawer.isDrawerOpen` transition from true to false,\n * rather than an unmount effect, which would never fire.\n */\nexport const ConnectWidgetActionBlock = observer(function ConnectWidgetActionBlock({\n beat,\n}: ActionBlockProps) {\n const { actionDrawer } = useInsightsV2Store()\n const { refreshAccounts } = useAccountStore()\n\n const isCurrentBeatOpen = actionDrawer.isDrawerOpen && actionDrawer.guid === beat.guid\n const wasOpen = useRef(isCurrentBeatOpen)\n\n useEffect(() => {\n if (wasOpen.current && !isCurrentBeatOpen) {\n void refreshAccounts()\n }\n wasOpen.current = isCurrentBeatOpen\n }, [isCurrentBeatOpen])\n\n return <ConnectWidgetPanel isActive={isCurrentBeatOpen} />\n})\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConnectWidgetActionBlock.es.js","names":[],"sources":["../../../src/insights-v2/actionBlocks/ConnectWidgetActionBlock.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport { observer } from 'mobx-react-lite'\n\nimport { ConnectWidgetPanel, useAccountStore, useInsightsV2Store } from '../../common'\nimport { ANALYTICS_EVENTS_CONFIG, useAnalyticsEvent } from '../analytics'\nimport type { ActionBlockProps } from '../mappings/blocks'\n\n/**\n * `connect_widget_block` — opens MX's Connect widget itself via the shared\n * `ConnectWidgetPanel` (src/common): SSO url via ConnectStore and the\n * iframe-based `@mxenabled/web-widget-sdk` ConnectWidget, with loading/error\n * statuses.\n *\n * Renders the panel directly (not `ConnectDrawer`, which wraps it in its own\n * `Drawer`) — `ActionBlockLayer` already supplies the action drawer's chrome\n * (title from the CTA label, close button), so a second nested `Drawer` here\n * would produce two stacked drawers with two headers and two close buttons.\n *\n * `ActionBlockLayer` does not unmount a drawer-presentation block when the\n * drawer closes — it only toggles the `Drawer`'s `isOpen` — so the account\n * refresh (parity with `ConnectDrawer`'s own `onClose`) is driven by watching\n * this beat's `actionDrawer.isDrawerOpen` transition from true to false,\n * rather than an unmount effect, which would never fire.\n */\nexport const ConnectWidgetActionBlock = observer(function ConnectWidgetActionBlock({\n beat,\n}: ActionBlockProps) {\n const { actionDrawer } = useInsightsV2Store()\n const { refreshAccounts } = useAccountStore()\n const fireAnalyticsEvent = useAnalyticsEvent()\n\n const isCurrentBeatOpen = actionDrawer.isDrawerOpen && actionDrawer.guid === beat.guid\n const wasOpen = useRef(isCurrentBeatOpen)\n\n useEffect(() => {\n if (wasOpen.current && !isCurrentBeatOpen) {\n void refreshAccounts()\n }\n wasOpen.current = isCurrentBeatOpen\n }, [isCurrentBeatOpen])\n\n // OPENED_CONNECT_DRAWER: the drawer actually opened (any path — CTA or otherwise). Own ref +\n // effect, separate from the refresh logic above: this one is rising-edge, so its ref starts\n // `false` — the block's first render typically happens with the drawer already open (children\n // render on open), and that first open must fire too. Fires once per open, re-arms on close.\n // Pulse parity: ConnectDiscoveredAccounts/Beat.js onPrimaryCtaClick's `opened_connect_drawer`.\n const hasFiredOpen = useRef(false)\n\n useEffect(() => {\n if (isCurrentBeatOpen && !hasFiredOpen.current) {\n fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.OPENED_CONNECT_DRAWER)\n }\n hasFiredOpen.current = isCurrentBeatOpen\n }, [isCurrentBeatOpen, fireAnalyticsEvent])\n\n return <ConnectWidgetPanel isActive={isCurrentBeatOpen} />\n})\n"],"mappings":";;;;;;;AAwBA,IAAa,IAA2B,EAAS,SAAkC,EACjF,MAAA,EAAA,GACmB;AACnB,QAAM,EAAE,cAAA,EAAA,IAAiB,EAAmB,GACtC,EAAE,iBAAA,EAAA,IAAoB,EAAgB,GACtC,IAAqB,EAAkB,GAEvC,IAAoB,EAAa,gBAAgB,EAAa,SAAS,EAAK,MAC5E,IAAU,EAAO,CAAiB;AAExC,EAAA,EAAA,MAAgB;AACd,IAAI,EAAQ,WAAW,CAAC,KACtB,EAAqB,GAEvB,EAAQ,UAAU;AAAA,EACpB,GAAG,CAAC,CAAiB,CAAC;AAOtB,QAAM,IAAe,EAAO,EAAK;AAEjC,SAAA,EAAA,MAAgB;AACd,IAAI,KAAqB,CAAC,EAAa,WACrC,EAAmB,EAAwB,qBAAqB,GAElE,EAAa,UAAU;AAAA,EACzB,GAAG,CAAC,GAAmB,CAAkB,CAAC,GAEnC,gBAAA,EAAC,GAAD,EAAoB,UAAU,EAAoB,CAAA;AAC3D,CAAC"}
|