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