@blocklet/payment-react 1.24.4 → 1.25.1

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 (98) hide show
  1. package/es/components/auto-topup/modal.d.ts +2 -0
  2. package/es/components/auto-topup/modal.js +48 -6
  3. package/es/components/auto-topup/product-card.d.ts +16 -1
  4. package/es/components/auto-topup/product-card.js +97 -15
  5. package/es/components/dynamic-pricing-unavailable.d.ts +9 -0
  6. package/es/components/dynamic-pricing-unavailable.js +58 -0
  7. package/es/components/loading-amount.d.ts +17 -0
  8. package/es/components/loading-amount.js +46 -0
  9. package/es/components/price-change-confirm.d.ts +18 -0
  10. package/es/components/price-change-confirm.js +107 -0
  11. package/es/components/quote-details-panel.d.ts +21 -0
  12. package/es/components/quote-details-panel.js +170 -0
  13. package/es/components/quote-lock-banner.d.ts +7 -0
  14. package/es/components/quote-lock-banner.js +79 -0
  15. package/es/components/slippage-config.d.ts +20 -0
  16. package/es/components/slippage-config.js +261 -0
  17. package/es/history/invoice/list.js +125 -15
  18. package/es/hooks/dynamic-pricing.d.ts +102 -0
  19. package/es/hooks/dynamic-pricing.js +393 -0
  20. package/es/index.d.ts +6 -1
  21. package/es/index.js +9 -1
  22. package/es/libs/util.d.ts +42 -5
  23. package/es/libs/util.js +345 -57
  24. package/es/locales/en.js +114 -3
  25. package/es/locales/zh.js +114 -3
  26. package/es/payment/form/index.d.ts +4 -1
  27. package/es/payment/form/index.js +454 -22
  28. package/es/payment/index.d.ts +1 -1
  29. package/es/payment/index.js +279 -16
  30. package/es/payment/product-item.d.ts +26 -1
  31. package/es/payment/product-item.js +330 -51
  32. package/es/payment/summary-section/promotion-section.d.ts +32 -0
  33. package/es/payment/summary-section/promotion-section.js +143 -0
  34. package/es/payment/summary-section/total-section.d.ts +39 -0
  35. package/es/payment/summary-section/total-section.js +83 -0
  36. package/es/payment/summary.d.ts +17 -2
  37. package/es/payment/summary.js +300 -253
  38. package/es/types/index.d.ts +11 -0
  39. package/lib/components/auto-topup/modal.d.ts +2 -0
  40. package/lib/components/auto-topup/modal.js +54 -6
  41. package/lib/components/auto-topup/product-card.d.ts +16 -1
  42. package/lib/components/auto-topup/product-card.js +75 -7
  43. package/lib/components/dynamic-pricing-unavailable.d.ts +9 -0
  44. package/lib/components/dynamic-pricing-unavailable.js +81 -0
  45. package/lib/components/loading-amount.d.ts +17 -0
  46. package/lib/components/loading-amount.js +53 -0
  47. package/lib/components/price-change-confirm.d.ts +18 -0
  48. package/lib/components/price-change-confirm.js +157 -0
  49. package/lib/components/quote-details-panel.d.ts +21 -0
  50. package/lib/components/quote-details-panel.js +226 -0
  51. package/lib/components/quote-lock-banner.d.ts +7 -0
  52. package/lib/components/quote-lock-banner.js +93 -0
  53. package/lib/components/slippage-config.d.ts +20 -0
  54. package/lib/components/slippage-config.js +316 -0
  55. package/lib/history/invoice/list.js +167 -27
  56. package/lib/hooks/dynamic-pricing.d.ts +102 -0
  57. package/lib/hooks/dynamic-pricing.js +390 -0
  58. package/lib/index.d.ts +6 -1
  59. package/lib/index.js +32 -0
  60. package/lib/libs/util.d.ts +42 -5
  61. package/lib/libs/util.js +367 -49
  62. package/lib/locales/en.js +114 -3
  63. package/lib/locales/zh.js +114 -3
  64. package/lib/payment/form/index.d.ts +4 -1
  65. package/lib/payment/form/index.js +476 -20
  66. package/lib/payment/index.d.ts +1 -1
  67. package/lib/payment/index.js +308 -14
  68. package/lib/payment/product-item.d.ts +26 -1
  69. package/lib/payment/product-item.js +270 -35
  70. package/lib/payment/summary-section/promotion-section.d.ts +32 -0
  71. package/lib/payment/summary-section/promotion-section.js +133 -0
  72. package/lib/payment/summary-section/total-section.d.ts +39 -0
  73. package/lib/payment/summary-section/total-section.js +117 -0
  74. package/lib/payment/summary.d.ts +17 -2
  75. package/lib/payment/summary.js +205 -127
  76. package/lib/types/index.d.ts +11 -0
  77. package/package.json +3 -3
  78. package/src/components/auto-topup/modal.tsx +59 -6
  79. package/src/components/auto-topup/product-card.tsx +118 -11
  80. package/src/components/dynamic-pricing-unavailable.tsx +69 -0
  81. package/src/components/loading-amount.tsx +66 -0
  82. package/src/components/price-change-confirm.tsx +136 -0
  83. package/src/components/quote-details-panel.tsx +218 -0
  84. package/src/components/quote-lock-banner.tsx +99 -0
  85. package/src/components/slippage-config.tsx +336 -0
  86. package/src/history/invoice/list.tsx +143 -9
  87. package/src/hooks/dynamic-pricing.ts +617 -0
  88. package/src/index.ts +9 -0
  89. package/src/libs/util.ts +473 -58
  90. package/src/locales/en.tsx +117 -0
  91. package/src/locales/zh.tsx +111 -0
  92. package/src/payment/form/index.tsx +561 -19
  93. package/src/payment/index.tsx +349 -10
  94. package/src/payment/product-item.tsx +451 -37
  95. package/src/payment/summary-section/promotion-section.tsx +172 -0
  96. package/src/payment/summary-section/total-section.tsx +141 -0
  97. package/src/payment/summary.tsx +334 -192
  98. package/src/types/index.ts +15 -0
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = QuoteDetailsPanel;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _ExpandMore = _interopRequireDefault(require("@mui/icons-material/ExpandMore"));
9
+ var _InfoOutlined = _interopRequireDefault(require("@mui/icons-material/InfoOutlined"));
10
+ var _Settings = _interopRequireDefault(require("@mui/icons-material/Settings"));
11
+ var _material = require("@mui/material");
12
+ var _react = require("react");
13
+ var _context = require("@arcblock/ux/lib/Locale/context");
14
+ var _slippageConfig = _interopRequireDefault(require("./slippage-config"));
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ function QuoteDetailsPanel({
17
+ rateLine,
18
+ rows,
19
+ isSubscription = false,
20
+ slippageValue = 0.5,
21
+ onSlippageChange = void 0,
22
+ slippageConfig = void 0,
23
+ exchangeRate = null,
24
+ baseCurrency = "USD",
25
+ disabled = false
26
+ }) {
27
+ const {
28
+ t
29
+ } = (0, _context.useLocaleContext)();
30
+ const [open, setOpen] = (0, _react.useState)(false);
31
+ const [showContent, setShowContent] = (0, _react.useState)(true);
32
+ const [dialogOpen, setDialogOpen] = (0, _react.useState)(false);
33
+ const [pendingConfig, setPendingConfig] = (0, _react.useState)(null);
34
+ const [submitting, setSubmitting] = (0, _react.useState)(false);
35
+ const hasRows = rows.length > 0;
36
+ const handleOpenDialog = (0, _react.useCallback)(() => {
37
+ setPendingConfig(slippageConfig || {
38
+ mode: "percent",
39
+ percent: slippageValue
40
+ });
41
+ setDialogOpen(true);
42
+ }, [slippageValue, slippageConfig]);
43
+ const handleCloseDialog = () => {
44
+ setDialogOpen(false);
45
+ setPendingConfig(null);
46
+ };
47
+ const handleSlippageChange = value => {
48
+ setPendingConfig(prev => prev ? {
49
+ ...prev,
50
+ percent: value
51
+ } : {
52
+ mode: "percent",
53
+ percent: value
54
+ });
55
+ };
56
+ const handleConfigChange = config => {
57
+ setPendingConfig(config);
58
+ };
59
+ const handleSubmit = async () => {
60
+ if (!pendingConfig || !onSlippageChange) return;
61
+ setSubmitting(true);
62
+ try {
63
+ const configToSave = {
64
+ ...pendingConfig,
65
+ ...(baseCurrency ? {
66
+ base_currency: baseCurrency
67
+ } : {})
68
+ };
69
+ await onSlippageChange(configToSave);
70
+ setDialogOpen(false);
71
+ setPendingConfig(null);
72
+ } catch (err) {
73
+ console.error("Failed to update slippage", err);
74
+ } finally {
75
+ setSubmitting(false);
76
+ }
77
+ };
78
+ (0, _react.useEffect)(() => {
79
+ if (!rateLine) return void 0;
80
+ setShowContent(false);
81
+ const timer = setTimeout(() => {
82
+ setShowContent(true);
83
+ }, 150);
84
+ return () => clearTimeout(timer);
85
+ }, [rateLine]);
86
+ const renderedRows = (0, _react.useMemo)(() => rows.map(row => {
87
+ const isSlippageRow = row.isSlippage && isSubscription && onSlippageChange;
88
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
89
+ direction: "row",
90
+ sx: {
91
+ alignItems: "center",
92
+ justifyContent: "space-between",
93
+ gap: 2
94
+ },
95
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
96
+ direction: "row",
97
+ spacing: 0.5,
98
+ alignItems: "center",
99
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
100
+ sx: {
101
+ fontSize: "0.75rem",
102
+ color: "text.secondary"
103
+ },
104
+ children: row.label
105
+ }), row.tooltip && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, {
106
+ title: row.tooltip,
107
+ placement: "top",
108
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_InfoOutlined.default, {
109
+ sx: {
110
+ fontSize: "0.75rem",
111
+ color: "text.lighter"
112
+ }
113
+ })
114
+ })]
115
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
116
+ direction: "row",
117
+ alignItems: "center",
118
+ spacing: 0.5,
119
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
120
+ sx: {
121
+ fontSize: "0.75rem",
122
+ color: "text.primary"
123
+ },
124
+ children: row.value
125
+ }), isSlippageRow && !disabled && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
126
+ size: "small",
127
+ onClick: handleOpenDialog,
128
+ sx: {
129
+ p: 0.25
130
+ },
131
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_Settings.default, {
132
+ sx: {
133
+ fontSize: "0.875rem",
134
+ color: "text.secondary"
135
+ }
136
+ })
137
+ })]
138
+ })]
139
+ }, row.label);
140
+ }), [rows, isSubscription, onSlippageChange, disabled, handleOpenDialog]);
141
+ const showSlippageOnly = !rateLine && isSubscription && onSlippageChange && rows.some(r => r.isSlippage);
142
+ if (!rateLine && !showSlippageOnly) {
143
+ return null;
144
+ }
145
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
146
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
147
+ sx: {
148
+ width: "100%",
149
+ mt: 0.5
150
+ },
151
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
152
+ direction: "row",
153
+ sx: {
154
+ alignItems: "center",
155
+ justifyContent: "space-between",
156
+ gap: 1
157
+ },
158
+ children: [rateLine ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Fade, {
159
+ in: showContent,
160
+ timeout: 300,
161
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
162
+ sx: {
163
+ fontSize: "0.7875rem",
164
+ color: "text.lighter"
165
+ },
166
+ children: rateLine
167
+ })
168
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {}), hasRows && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
169
+ size: "small",
170
+ onClick: () => setOpen(prev => !prev),
171
+ "aria-label": open ? "collapse" : "expand",
172
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_ExpandMore.default, {
173
+ sx: {
174
+ fontSize: "1rem",
175
+ transition: "transform 0.2s ease",
176
+ transform: open ? "rotate(180deg)" : "rotate(0deg)"
177
+ }
178
+ })
179
+ })]
180
+ }), hasRows && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Collapse, {
181
+ in: open,
182
+ timeout: "auto",
183
+ unmountOnExit: true,
184
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Fade, {
185
+ in: showContent,
186
+ timeout: 300,
187
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
188
+ sx: {
189
+ mt: 1,
190
+ p: 1.25,
191
+ borderRadius: 1,
192
+ border: "1px solid",
193
+ borderColor: "divider",
194
+ bgcolor: "action.hover"
195
+ },
196
+ spacing: 1,
197
+ children: renderedRows
198
+ })
199
+ })
200
+ })]
201
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Dialog, {
202
+ open: dialogOpen,
203
+ onClose: handleCloseDialog,
204
+ maxWidth: "sm",
205
+ fullWidth: true,
206
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.DialogTitle, {
207
+ children: t("payment.checkout.quote.slippage.title")
208
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.DialogContent, {
209
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_slippageConfig.default, {
210
+ value: pendingConfig?.percent ?? slippageValue,
211
+ onChange: handleSlippageChange,
212
+ config: pendingConfig || slippageConfig,
213
+ onConfigChange: handleConfigChange,
214
+ exchangeRate,
215
+ baseCurrency,
216
+ disabled: disabled || submitting,
217
+ sx: {
218
+ mt: 1
219
+ },
220
+ onCancel: handleCloseDialog,
221
+ onSave: handleSubmit
222
+ })
223
+ })]
224
+ })]
225
+ });
226
+ }
@@ -0,0 +1,7 @@
1
+ import type { TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
2
+ interface QuoteLockBannerProps {
3
+ items: TLineItemExpanded[];
4
+ currency: TPaymentCurrency;
5
+ }
6
+ export default function QuoteLockBanner({ items, currency }: QuoteLockBannerProps): import("react").JSX.Element | null;
7
+ export {};
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = QuoteLockBanner;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _context = require("@arcblock/ux/lib/Locale/context");
9
+ var _iconsMaterial = require("@mui/icons-material");
10
+ var _material = require("@mui/material");
11
+ var _react = require("react");
12
+ var _util = require("../libs/util");
13
+ function getQuoteLockInfo(items, currency) {
14
+ if (!items?.length || !currency) {
15
+ return null;
16
+ }
17
+ const dynamicItems = items.filter(item => {
18
+ const price = item.upsell_price || item.price;
19
+ return price?.pricing_type === "dynamic" && item?.quoted_amount;
20
+ });
21
+ if (!dynamicItems.length) {
22
+ return null;
23
+ }
24
+ let expiresAt = null;
25
+ let exchangeRate = null;
26
+ dynamicItems.forEach(item => {
27
+ if (item?.expires_at) {
28
+ expiresAt = expiresAt === null ? item?.expires_at : Math.min(expiresAt, item?.expires_at);
29
+ }
30
+ if (!exchangeRate) {
31
+ exchangeRate = item?.exchange_rate || null;
32
+ }
33
+ });
34
+ return {
35
+ exchangeRate,
36
+ tokenSymbol: currency.symbol,
37
+ baseCurrency: (dynamicItems[0]?.upsell_price || dynamicItems[0]?.price)?.base_currency || "USD",
38
+ expiresAt
39
+ };
40
+ }
41
+ function QuoteLockBanner({
42
+ items,
43
+ currency
44
+ }) {
45
+ const {
46
+ t
47
+ } = (0, _context.useLocaleContext)();
48
+ const quoteLockInfo = (0, _react.useMemo)(() => getQuoteLockInfo(items, currency), [items, currency]);
49
+ if (!quoteLockInfo || !quoteLockInfo.exchangeRate) {
50
+ return null;
51
+ }
52
+ const formattedRateValue = (0, _util.formatExchangeRate)(quoteLockInfo.exchangeRate);
53
+ let formattedRate = "";
54
+ if (formattedRateValue) {
55
+ formattedRate = quoteLockInfo.baseCurrency === "USD" ? `$${formattedRateValue}` : `${formattedRateValue} ${quoteLockInfo.baseCurrency}`;
56
+ }
57
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
58
+ sx: {
59
+ bgcolor: "action.hover",
60
+ border: "1px solid",
61
+ borderColor: "divider",
62
+ borderRadius: 1,
63
+ px: 2,
64
+ py: 1.5,
65
+ mb: 2
66
+ },
67
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
68
+ sx: {
69
+ display: "flex",
70
+ alignItems: "center",
71
+ gap: 1.5,
72
+ flexWrap: "wrap"
73
+ },
74
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.LockOutlined, {
75
+ sx: {
76
+ fontSize: "1rem",
77
+ color: "success.main"
78
+ }
79
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
80
+ sx: {
81
+ fontSize: "0.875rem",
82
+ color: "text.primary",
83
+ flex: 1
84
+ },
85
+ children: t("payment.checkout.quote.dynamicPricingInfo", {
86
+ symbol: quoteLockInfo.tokenSymbol,
87
+ rate: formattedRate,
88
+ currency: ""
89
+ })
90
+ })]
91
+ })
92
+ });
93
+ }
@@ -0,0 +1,20 @@
1
+ export type SlippageConfigValue = {
2
+ mode: 'percent' | 'rate';
3
+ percent: number;
4
+ min_acceptable_rate?: string;
5
+ base_currency?: string;
6
+ updated_at_ms?: number;
7
+ };
8
+ export interface SlippageConfigProps {
9
+ value: number;
10
+ onChange: (value: number) => void;
11
+ config?: SlippageConfigValue;
12
+ onConfigChange?: (value: SlippageConfigValue) => void;
13
+ exchangeRate?: string | null;
14
+ baseCurrency?: string;
15
+ disabled?: boolean;
16
+ sx?: any;
17
+ onCancel?: () => void;
18
+ onSave?: () => void;
19
+ }
20
+ export default function SlippageConfig({ value, onChange, config, onConfigChange, exchangeRate, baseCurrency, disabled, sx, onCancel, onSave, }: SlippageConfigProps): import("react").JSX.Element;
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = SlippageConfig;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _react = require("react");
9
+ var _material = require("@mui/material");
10
+ var _context = require("@arcblock/ux/lib/Locale/context");
11
+ const PRESET_SLIPPAGE = [0.5, 1, 3, 5, 10];
12
+ function SlippageConfig({
13
+ value,
14
+ onChange,
15
+ config = void 0,
16
+ onConfigChange = void 0,
17
+ exchangeRate = null,
18
+ baseCurrency = "USD",
19
+ disabled = false,
20
+ sx = {},
21
+ onCancel = void 0,
22
+ onSave = void 0
23
+ }) {
24
+ const {
25
+ t
26
+ } = (0, _context.useLocaleContext)();
27
+ const [inputValue, setInputValue] = (0, _react.useState)("");
28
+ const [inputMode, setInputMode] = (0, _react.useState)(config?.mode || "percent");
29
+ const [error, setError] = (0, _react.useState)(null);
30
+ const [isEditing, setIsEditing] = (0, _react.useState)(false);
31
+ const percentValue = config?.percent ?? value;
32
+ const roundedRate = (0, _react.useMemo)(() => {
33
+ if (!exchangeRate) return null;
34
+ const rateNum = Number(exchangeRate);
35
+ if (Number.isNaN(rateNum) || rateNum <= 0) return null;
36
+ return Math.round(rateNum * 100) / 100;
37
+ }, [exchangeRate]);
38
+ const computeMinRateFromPercent = percent => {
39
+ if (!roundedRate) return "";
40
+ const slippageMultiplier = 1 + percent / 100;
41
+ return (roundedRate / slippageMultiplier).toFixed(2);
42
+ };
43
+ (0, _react.useEffect)(() => {
44
+ if (config?.mode && config.mode !== inputMode) {
45
+ setInputMode(config.mode);
46
+ }
47
+ }, [config?.mode, inputMode]);
48
+ (0, _react.useEffect)(() => {
49
+ if (isEditing) {
50
+ return;
51
+ }
52
+ if (inputMode === "percent") {
53
+ setInputValue(percentValue.toFixed(2));
54
+ return;
55
+ }
56
+ if (config?.min_acceptable_rate) {
57
+ setInputValue(String(config.min_acceptable_rate));
58
+ return;
59
+ }
60
+ const minRate = computeMinRateFromPercent(percentValue);
61
+ setInputValue(minRate);
62
+ }, [percentValue, inputMode, exchangeRate, config?.min_acceptable_rate, isEditing]);
63
+ const handlePresetClick = preset => {
64
+ if (disabled) return;
65
+ setInputValue(preset.toFixed(2));
66
+ setInputMode("percent");
67
+ setError(null);
68
+ onChange(preset);
69
+ const minRate = computeMinRateFromPercent(preset);
70
+ onConfigChange?.({
71
+ mode: "percent",
72
+ percent: preset,
73
+ ...(minRate ? {
74
+ min_acceptable_rate: minRate
75
+ } : {})
76
+ });
77
+ };
78
+ const handleInputChange = newValue => {
79
+ setInputValue(newValue);
80
+ setError(null);
81
+ const numValue = Number(newValue);
82
+ if (!newValue || Number.isNaN(numValue)) {
83
+ setError(t("payment.checkout.quote.slippage.invalid"));
84
+ return;
85
+ }
86
+ if (numValue <= 0) {
87
+ setError(t("payment.checkout.quote.slippage.invalidPositive"));
88
+ return;
89
+ }
90
+ if (inputMode === "percent") {
91
+ onChange(numValue);
92
+ const minRate = computeMinRateFromPercent(numValue);
93
+ onConfigChange?.({
94
+ mode: "percent",
95
+ percent: numValue,
96
+ ...(minRate ? {
97
+ min_acceptable_rate: minRate
98
+ } : {})
99
+ });
100
+ } else {
101
+ if (!roundedRate) {
102
+ setError(t("payment.checkout.quote.slippage.rateRequired"));
103
+ return;
104
+ }
105
+ const percent = (roundedRate - numValue) / numValue * 100;
106
+ onChange(Math.max(0, percent));
107
+ onConfigChange?.({
108
+ mode: "rate",
109
+ percent: Math.max(0, percent),
110
+ min_acceptable_rate: newValue
111
+ });
112
+ }
113
+ };
114
+ const handleModeChange = (_, newMode) => {
115
+ if (disabled || !newMode) return;
116
+ setInputMode(newMode);
117
+ setError(null);
118
+ if (newMode === "rate") {
119
+ if (!roundedRate) {
120
+ setError(t("payment.checkout.quote.slippage.rateRequired"));
121
+ return;
122
+ }
123
+ const minRate = config?.min_acceptable_rate || computeMinRateFromPercent(percentValue);
124
+ setInputValue(minRate);
125
+ onConfigChange?.({
126
+ mode: "rate",
127
+ percent: percentValue,
128
+ min_acceptable_rate: minRate
129
+ });
130
+ } else {
131
+ setInputValue(percentValue.toFixed(2));
132
+ const minRate = computeMinRateFromPercent(percentValue);
133
+ onConfigChange?.({
134
+ mode: "percent",
135
+ percent: percentValue,
136
+ ...(minRate ? {
137
+ min_acceptable_rate: minRate
138
+ } : {})
139
+ });
140
+ }
141
+ };
142
+ const minAcceptableRate = (0, _react.useMemo)(() => {
143
+ if (!roundedRate) return null;
144
+ const slippageMultiplier = 1 + percentValue / 100;
145
+ return (roundedRate / slippageMultiplier).toFixed(2);
146
+ }, [roundedRate, percentValue]);
147
+ const currentRateLabel = (0, _react.useMemo)(() => {
148
+ if (!roundedRate) {
149
+ return "\u2014";
150
+ }
151
+ return roundedRate.toFixed(2);
152
+ }, [roundedRate]);
153
+ const handleCancel = () => {
154
+ onCancel?.();
155
+ };
156
+ const handleSave = () => {
157
+ onSave?.();
158
+ };
159
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
160
+ spacing: 2.5,
161
+ sx,
162
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
163
+ variant: "body2",
164
+ color: "text.secondary",
165
+ children: t("payment.checkout.quote.slippageLimit.description")
166
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.ToggleButtonGroup, {
167
+ value: inputMode,
168
+ exclusive: true,
169
+ onChange: handleModeChange,
170
+ size: "small",
171
+ fullWidth: true,
172
+ disabled,
173
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, {
174
+ value: "percent",
175
+ children: t("payment.checkout.quote.slippageLimit.configTogglePercent")
176
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, {
177
+ value: "rate",
178
+ disabled: !roundedRate,
179
+ children: t("payment.checkout.quote.slippageLimit.configToggleRate")
180
+ })]
181
+ }), inputMode === "percent" && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
182
+ spacing: 1.5,
183
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
184
+ direction: "row",
185
+ spacing: 1,
186
+ children: PRESET_SLIPPAGE.map(preset => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, {
187
+ value: preset,
188
+ selected: Math.abs(percentValue - preset) < 0.01,
189
+ onClick: () => handlePresetClick(preset),
190
+ size: "small",
191
+ disabled,
192
+ sx: {
193
+ flex: 1,
194
+ py: 1,
195
+ borderRadius: 1,
196
+ border: "1px solid",
197
+ borderColor: "divider",
198
+ "&.Mui-selected": {
199
+ bgcolor: "primary.main",
200
+ color: "primary.contrastText",
201
+ borderColor: "primary.main",
202
+ "&:hover": {
203
+ bgcolor: "primary.dark"
204
+ }
205
+ }
206
+ },
207
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
208
+ variant: "body2",
209
+ children: [preset, "%"]
210
+ })
211
+ }, preset))
212
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, {
213
+ size: "small",
214
+ fullWidth: true,
215
+ value: inputValue,
216
+ onChange: e => handleInputChange(e.target.value),
217
+ onFocus: () => setIsEditing(true),
218
+ onBlur: () => setIsEditing(false),
219
+ error: !!error,
220
+ helperText: error,
221
+ disabled,
222
+ label: t("common.custom"),
223
+ InputProps: {
224
+ endAdornment: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
225
+ variant: "body2",
226
+ sx: {
227
+ color: "text.secondary",
228
+ mr: 1
229
+ },
230
+ children: "%"
231
+ })
232
+ },
233
+ placeholder: "0.50"
234
+ })]
235
+ }), inputMode === "rate" && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
236
+ spacing: 1.5,
237
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, {
238
+ size: "small",
239
+ fullWidth: true,
240
+ value: inputValue,
241
+ onChange: e => handleInputChange(e.target.value),
242
+ onFocus: () => setIsEditing(true),
243
+ onBlur: () => setIsEditing(false),
244
+ error: !!error,
245
+ helperText: error,
246
+ disabled: disabled || !roundedRate,
247
+ label: t("payment.checkout.quote.slippage.rateInputLabel", {
248
+ currency: baseCurrency
249
+ }),
250
+ InputProps: {
251
+ endAdornment: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
252
+ variant: "body2",
253
+ sx: {
254
+ color: "text.secondary",
255
+ mr: 1
256
+ },
257
+ children: baseCurrency
258
+ })
259
+ },
260
+ placeholder: roundedRate?.toFixed(2) || "0.00"
261
+ })
262
+ }), roundedRate && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
263
+ sx: {
264
+ borderRadius: 1,
265
+ p: 1.5,
266
+ bgcolor: "action.hover"
267
+ },
268
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
269
+ spacing: 0.5,
270
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
271
+ direction: "row",
272
+ justifyContent: "space-between",
273
+ alignItems: "center",
274
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
275
+ variant: "body2",
276
+ color: "text.secondary",
277
+ children: t("payment.checkout.quote.slippageLimit.derivedCurrentRate")
278
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
279
+ variant: "body2",
280
+ fontWeight: 500,
281
+ children: [currentRateLabel, " ", baseCurrency]
282
+ })]
283
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
284
+ direction: "row",
285
+ justifyContent: "space-between",
286
+ alignItems: "center",
287
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
288
+ variant: "body2",
289
+ color: "text.secondary",
290
+ children: t("payment.checkout.quote.slippageLimit.derivedMinRate")
291
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
292
+ variant: "body2",
293
+ fontWeight: 500,
294
+ color: "primary.main",
295
+ children: [inputMode === "rate" ? inputValue : minAcceptableRate || "\u2014", " ", baseCurrency]
296
+ })]
297
+ })]
298
+ })
299
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
300
+ direction: "row",
301
+ spacing: 1,
302
+ justifyContent: "flex-end",
303
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
304
+ onClick: handleCancel,
305
+ disabled,
306
+ color: "inherit",
307
+ children: t("common.cancel")
308
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
309
+ variant: "contained",
310
+ onClick: handleSave,
311
+ disabled: disabled || !!error,
312
+ children: t("common.save")
313
+ })]
314
+ })]
315
+ });
316
+ }