@blocklet/payment-react 1.24.4 → 1.25.0

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
@@ -1,4 +1,5 @@
1
1
  import type { AutoRechargeConfig } from '@blocklet/payment-types';
2
+ import type { SlippageConfigValue } from '../slippage-config';
2
3
  export interface AutoTopupFormData {
3
4
  enabled: boolean;
4
5
  threshold: string;
@@ -21,6 +22,7 @@ export interface AutoTopupFormData {
21
22
  city?: string;
22
23
  postal_code?: string;
23
24
  };
25
+ slippage_config?: SlippageConfigValue | null;
24
26
  }
25
27
  export interface AutoTopupModalProps {
26
28
  open: boolean;
@@ -54,7 +54,12 @@ const DEFAULT_VALUES = {
54
54
  recharge_currency_id: "",
55
55
  price_id: "",
56
56
  daily_max_amount: 0,
57
- daily_max_attempts: 0
57
+ daily_max_attempts: 0,
58
+ slippage_config: null
59
+ };
60
+ const fetchExchangeRate = async (currencyId) => {
61
+ const { data } = await api.post("/api/exchange-rates/validate", { currency: currencyId });
62
+ return data;
58
63
  };
59
64
  export const waitForAutoRechargeComplete = async (configId) => {
60
65
  let result;
@@ -264,6 +269,8 @@ export default function AutoTopup({
264
269
  const { t, locale } = useLocaleContext();
265
270
  const { session, connect, settings } = usePaymentContext();
266
271
  const [changePaymentMethod, setChangePaymentMethod] = useState(false);
272
+ const [slippagePercent, setSlippagePercent] = useState(0.5);
273
+ const [slippageConfig, setSlippageConfig] = useState(null);
267
274
  const [state, setState] = useSetState({
268
275
  loading: false,
269
276
  submitting: false,
@@ -295,6 +302,10 @@ export default function AutoTopup({
295
302
  const enabled = watch("enabled");
296
303
  const quantity = watch("quantity");
297
304
  const rechargeCurrencyId = watch("recharge_currency_id");
305
+ const selectedMethod = settings.paymentMethods.find((method) => {
306
+ return method.payment_currencies.find((c) => c.id === rechargeCurrencyId);
307
+ });
308
+ const isStripePayment = selectedMethod?.type === "stripe";
298
309
  const handleClose = () => {
299
310
  setState({
300
311
  loading: false,
@@ -320,6 +331,22 @@ export default function AutoTopup({
320
331
  max_amount: data.daily_limits?.max_amount || 0,
321
332
  max_attempts: data.daily_limits?.max_attempts || 0
322
333
  });
334
+ if (data.slippage_config) {
335
+ setSlippageConfig(data.slippage_config);
336
+ setSlippagePercent(data.slippage_config.percent ?? 0.5);
337
+ }
338
+ }
339
+ });
340
+ const isDynamicPricing = config?.price?.pricing_type === "dynamic";
341
+ const { data: exchangeRateData } = useRequest(() => fetchExchangeRate(rechargeCurrencyId), {
342
+ refreshDeps: [rechargeCurrencyId],
343
+ ready: !!rechargeCurrencyId && isDynamicPricing && enabled && !isStripePayment,
344
+ pollingInterval: 3e4,
345
+ // Refresh every 30 seconds
346
+ pollingWhenHidden: false,
347
+ // Stop polling when tab is hidden
348
+ onError: (error) => {
349
+ console.warn("Failed to fetch exchange rate:", error.message);
323
350
  }
324
351
  });
325
352
  const filterCurrencies = useMemo(() => {
@@ -431,6 +458,12 @@ export default function AutoTopup({
431
458
  },
432
459
  change_payment_method: changePaymentMethod
433
460
  };
461
+ if (isDynamicPricing && slippageConfig) {
462
+ submitData.slippage_config = {
463
+ ...slippageConfig,
464
+ updated_at_ms: Date.now()
465
+ };
466
+ }
434
467
  const { data } = await api.post("/api/auto-recharge-configs/submit", submitData);
435
468
  if (data.balanceResult && !data.balanceResult.sufficient) {
436
469
  await handleAuthorizationRequired({
@@ -457,10 +490,7 @@ export default function AutoTopup({
457
490
  handleFormSubmit(formData);
458
491
  };
459
492
  const rechargeCurrency = filterCurrencies.find((c) => c.id === rechargeCurrencyId);
460
- const selectedMethod = settings.paymentMethods.find((method) => {
461
- return method.payment_currencies.find((c) => c.id === rechargeCurrencyId);
462
- });
463
- const showStripeForm = state.authorizationRequired && selectedMethod?.type === "stripe";
493
+ const showStripeForm = state.authorizationRequired && isStripePayment;
464
494
  const onStripeConfirm = async () => {
465
495
  await handleConnected();
466
496
  };
@@ -641,7 +671,19 @@ export default function AutoTopup({
641
671
  quantity,
642
672
  onQuantityChange: (newQuantity) => setValue("quantity", newQuantity),
643
673
  maxQuantity: 9999,
644
- minQuantity: 1
674
+ minQuantity: 1,
675
+ exchangeRate: exchangeRateData?.rate,
676
+ isDynamicPricing: isDynamicPricing && !isStripePayment,
677
+ exchangeRateData,
678
+ slippageConfig,
679
+ slippagePercent,
680
+ onSlippageChange: (newSlippageConfig) => {
681
+ setSlippageConfig(newSlippageConfig);
682
+ if (newSlippageConfig.percent !== void 0) {
683
+ setSlippagePercent(newSlippageConfig.percent);
684
+ }
685
+ },
686
+ disabled: state.submitting
645
687
  }
646
688
  ),
647
689
  config && rechargeCurrency && /* @__PURE__ */ jsx(
@@ -1,4 +1,12 @@
1
1
  import type { TPaymentCurrency } from '@blocklet/payment-types';
2
+ import type { SlippageConfigValue } from '../slippage-config';
3
+ interface ExchangeRateData {
4
+ rate?: string;
5
+ provider_name?: string;
6
+ provider_id?: string;
7
+ provider_display?: string;
8
+ timestamp_ms?: number;
9
+ }
2
10
  interface AutoTopupProductCardProps {
3
11
  product: any;
4
12
  price: any;
@@ -8,6 +16,13 @@ interface AutoTopupProductCardProps {
8
16
  maxQuantity?: number;
9
17
  minQuantity?: number;
10
18
  creditCurrency: TPaymentCurrency;
19
+ exchangeRate?: string | null;
20
+ isDynamicPricing?: boolean;
21
+ exchangeRateData?: ExchangeRateData | null;
22
+ slippageConfig?: SlippageConfigValue | null;
23
+ slippagePercent?: number;
24
+ onSlippageChange?: (config: SlippageConfigValue) => void;
25
+ disabled?: boolean;
11
26
  }
12
- export default function AutoTopupProductCard({ product, price, currency, quantity, onQuantityChange, maxQuantity, minQuantity, creditCurrency, }: AutoTopupProductCardProps): import("react").JSX.Element;
27
+ export default function AutoTopupProductCard({ product, price, currency, quantity, onQuantityChange, maxQuantity, minQuantity, creditCurrency, exchangeRate, isDynamicPricing, exchangeRateData, slippageConfig, slippagePercent, onSlippageChange, disabled, }: AutoTopupProductCardProps): import("react").JSX.Element;
13
28
  export {};
@@ -1,9 +1,18 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Stack, Typography, TextField, Card } from "@mui/material";
3
3
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
4
- import { useState } from "react";
4
+ import { useState, useMemo } from "react";
5
5
  import ProductCard from "../../payment/product-card.js";
6
- import { formatPrice, formatNumber, formatCreditForCheckout } from "../../libs/util.js";
6
+ import {
7
+ formatPrice,
8
+ formatNumber,
9
+ formatDynamicPrice,
10
+ formatUsdAmount,
11
+ formatExchangeRate,
12
+ formatToDatetime,
13
+ formatCreditForCheckout
14
+ } from "../../libs/util.js";
15
+ import QuoteDetailsPanel from "../quote-details-panel.js";
7
16
  export default function AutoTopupProductCard({
8
17
  product,
9
18
  price,
@@ -12,11 +21,41 @@ export default function AutoTopupProductCard({
12
21
  onQuantityChange,
13
22
  maxQuantity = 99,
14
23
  minQuantity = 1,
15
- creditCurrency
24
+ creditCurrency,
25
+ exchangeRate = null,
26
+ isDynamicPricing = false,
27
+ exchangeRateData = null,
28
+ slippageConfig = null,
29
+ slippagePercent = 0.5,
30
+ onSlippageChange = void 0,
31
+ disabled = false
16
32
  }) {
17
33
  const { t, locale } = useLocaleContext();
18
34
  const [localQuantity, setLocalQuantity] = useState(quantity);
19
35
  const localQuantityNum = Number(localQuantity) || 0;
36
+ const { paymentAmount, usdReferenceDisplay } = useMemo(() => {
37
+ if (!isDynamicPricing || !exchangeRate || !price?.base_amount) {
38
+ return {
39
+ paymentAmount: formatPrice(price, currency, product?.unit_label, localQuantity, true),
40
+ usdReferenceDisplay: null
41
+ };
42
+ }
43
+ const baseAmount = Number(price.base_amount) * localQuantityNum;
44
+ const rate = Number(exchangeRate);
45
+ if (rate <= 0 || !Number.isFinite(baseAmount)) {
46
+ return {
47
+ paymentAmount: formatPrice(price, currency, product?.unit_label, localQuantity, true),
48
+ usdReferenceDisplay: null
49
+ };
50
+ }
51
+ const tokenAmount = baseAmount / rate;
52
+ const formattedToken = formatDynamicPrice(tokenAmount, true, 6);
53
+ const formattedUsd = formatUsdAmount(baseAmount.toString(), locale);
54
+ return {
55
+ paymentAmount: `${formattedToken} ${currency.symbol}`,
56
+ usdReferenceDisplay: formattedUsd ? `\u2248 $${formattedUsd}` : null
57
+ };
58
+ }, [isDynamicPricing, exchangeRate, price, currency, product?.unit_label, localQuantity, localQuantityNum, locale]);
20
59
  const handleQuantityChange = (newQuantity) => {
21
60
  if (!newQuantity) {
22
61
  setLocalQuantity(void 0);
@@ -141,7 +180,7 @@ export default function AutoTopupProductCard({
141
180
  direction: "row",
142
181
  sx: {
143
182
  justifyContent: "space-between",
144
- alignItems: "center",
183
+ alignItems: "flex-start",
145
184
  mt: 2,
146
185
  pt: 2,
147
186
  borderTop: "1px solid",
@@ -158,17 +197,60 @@ export default function AutoTopupProductCard({
158
197
  children: t("payment.autoTopup.rechargeAmount")
159
198
  }
160
199
  ),
161
- /* @__PURE__ */ jsx(
162
- Typography,
163
- {
164
- variant: "h6",
165
- sx: {
166
- fontWeight: 600,
167
- color: "text.primary"
168
- },
169
- children: formatPrice(price, currency, product?.unit_label, localQuantity, true)
170
- }
171
- )
200
+ /* @__PURE__ */ jsxs(Stack, { sx: { alignItems: "flex-end" }, children: [
201
+ /* @__PURE__ */ jsx(
202
+ Typography,
203
+ {
204
+ variant: "h6",
205
+ sx: {
206
+ fontWeight: 600,
207
+ color: "text.primary"
208
+ },
209
+ children: paymentAmount
210
+ }
211
+ ),
212
+ usdReferenceDisplay && /* @__PURE__ */ jsx(
213
+ Typography,
214
+ {
215
+ sx: {
216
+ fontSize: "0.7875rem",
217
+ color: "text.lighter"
218
+ },
219
+ children: usdReferenceDisplay
220
+ }
221
+ ),
222
+ isDynamicPricing && exchangeRateData?.rate && /* @__PURE__ */ jsx(
223
+ QuoteDetailsPanel,
224
+ {
225
+ rateLine: t("payment.checkout.quote.rateLine", {
226
+ symbol: currency.symbol,
227
+ rate: `$${formatExchangeRate(exchangeRateData.rate) || exchangeRateData.rate}`
228
+ }),
229
+ rows: [
230
+ {
231
+ label: t("payment.checkout.quote.detailProvider"),
232
+ value: exchangeRateData?.provider_display || exchangeRateData?.provider_name || "\u2014"
233
+ },
234
+ {
235
+ label: t("payment.checkout.quote.detailUpdatedAt"),
236
+ value: exchangeRateData?.timestamp_ms ? formatToDatetime(exchangeRateData.timestamp_ms, locale) : "\u2014"
237
+ },
238
+ {
239
+ label: t("payment.checkout.quote.detailSlippage"),
240
+ value: slippageConfig?.mode === "rate" && slippageConfig.min_acceptable_rate ? `$${formatExchangeRate(slippageConfig.min_acceptable_rate) || slippageConfig.min_acceptable_rate}` : `${slippageConfig?.percent ?? slippagePercent}%`,
241
+ isSlippage: true
242
+ }
243
+ ],
244
+ isSubscription: true,
245
+ slippageValue: slippageConfig?.percent ?? slippagePercent,
246
+ slippageConfig: slippageConfig || void 0,
247
+ onSlippageChange,
248
+ exchangeRate: exchangeRateData?.rate,
249
+ baseCurrency: "USD",
250
+ disabled
251
+ }
252
+ )
253
+ ] })
172
254
  ]
173
255
  }
174
256
  )
@@ -0,0 +1,9 @@
1
+ import { type SxProps } from '@mui/material';
2
+ interface DynamicPricingUnavailableProps {
3
+ error?: string;
4
+ onRetry?: () => void | Promise<void>;
5
+ showRetry?: boolean;
6
+ sx?: SxProps;
7
+ }
8
+ export default function DynamicPricingUnavailable({ error, onRetry, showRetry, sx, }: DynamicPricingUnavailableProps): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,58 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Alert, AlertTitle, Typography, Button, Box, CircularProgress } from "@mui/material";
3
+ import { ErrorOutline, Refresh } from "@mui/icons-material";
4
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
+ import { useState } from "react";
6
+ export default function DynamicPricingUnavailable({
7
+ error = void 0,
8
+ onRetry = void 0,
9
+ showRetry = true,
10
+ sx = void 0
11
+ }) {
12
+ const { t } = useLocaleContext();
13
+ const [retrying, setRetrying] = useState(false);
14
+ if (error) {
15
+ console.error("[Dynamic Pricing Error]", error);
16
+ }
17
+ const handleRetry = async () => {
18
+ if (!onRetry || retrying) return;
19
+ setRetrying(true);
20
+ try {
21
+ await onRetry();
22
+ } finally {
23
+ setRetrying(false);
24
+ }
25
+ };
26
+ return /* @__PURE__ */ jsx(
27
+ Alert,
28
+ {
29
+ severity: "warning",
30
+ icon: /* @__PURE__ */ jsx(ErrorOutline, {}),
31
+ sx: {
32
+ borderRadius: 2,
33
+ "& .MuiAlert-message": {
34
+ width: "100%"
35
+ },
36
+ ...sx
37
+ },
38
+ children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", width: "100%" }, children: [
39
+ /* @__PURE__ */ jsxs(Box, { children: [
40
+ /* @__PURE__ */ jsx(AlertTitle, { sx: { fontWeight: 600 }, children: t("payment.dynamicPricing.unavailable.title") }),
41
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: "text.secondary", mt: 0.5 }, children: t("payment.dynamicPricing.unavailable.message") })
42
+ ] }),
43
+ showRetry && onRetry && /* @__PURE__ */ jsx(
44
+ Button,
45
+ {
46
+ size: "small",
47
+ variant: "outlined",
48
+ onClick: handleRetry,
49
+ disabled: retrying,
50
+ startIcon: retrying ? /* @__PURE__ */ jsx(CircularProgress, { size: 16 }) : /* @__PURE__ */ jsx(Refresh, {}),
51
+ sx: { ml: 2, flexShrink: 0 },
52
+ children: t("payment.dynamicPricing.unavailable.retry")
53
+ }
54
+ )
55
+ ] })
56
+ }
57
+ );
58
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * LoadingAmount Component
3
+ *
4
+ * Displays amount with skeleton loading state during currency switch.
5
+ * Only shows skeleton when isRateLoading is true (currency switch scenario).
6
+ */
7
+ import type { SxProps, Theme } from '@mui/material';
8
+ export interface LoadingAmountProps {
9
+ value: string;
10
+ loading?: boolean;
11
+ skeletonWidth?: number;
12
+ height?: number;
13
+ sx?: SxProps<Theme>;
14
+ animateValueChange?: boolean;
15
+ transitionDuration?: number;
16
+ }
17
+ export default function LoadingAmount({ value, loading, skeletonWidth, height, sx, animateValueChange, transitionDuration, }: LoadingAmountProps): import("react").JSX.Element;
@@ -0,0 +1,46 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Skeleton, Typography } from "@mui/material";
3
+ import { useEffect, useRef, useState } from "react";
4
+ export default function LoadingAmount({
5
+ value,
6
+ loading = false,
7
+ skeletonWidth = 80,
8
+ height = 24,
9
+ sx = {},
10
+ animateValueChange = false,
11
+ transitionDuration = 300
12
+ }) {
13
+ const [displayValue, setDisplayValue] = useState(value);
14
+ const [isTransitioning, setIsTransitioning] = useState(false);
15
+ const prevValueRef = useRef(value);
16
+ useEffect(() => {
17
+ if (value !== prevValueRef.current) {
18
+ prevValueRef.current = value;
19
+ if (animateValueChange && !loading) {
20
+ setIsTransitioning(true);
21
+ const timer = setTimeout(() => {
22
+ setDisplayValue(value);
23
+ setIsTransitioning(false);
24
+ }, transitionDuration / 2);
25
+ return () => clearTimeout(timer);
26
+ }
27
+ setDisplayValue(value);
28
+ }
29
+ return void 0;
30
+ }, [value, loading, animateValueChange, transitionDuration]);
31
+ if (loading) {
32
+ return /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: skeletonWidth, height });
33
+ }
34
+ return /* @__PURE__ */ jsx(
35
+ Typography,
36
+ {
37
+ component: "span",
38
+ sx: {
39
+ ...sx,
40
+ opacity: isTransitioning ? 0 : 1,
41
+ transition: animateValueChange ? `opacity ${transitionDuration / 2}ms ease-in-out` : void 0
42
+ },
43
+ children: displayValue
44
+ }
45
+ );
46
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Price Change Confirmation Dialog (Final Freeze Architecture)
3
+ *
4
+ * Displayed when the price changes between Preview and Submit
5
+ * beyond the user's configured slippage threshold.
6
+ *
7
+ * @see Intent: blocklets/core/ai/intent/20260112-dynamic-price.md
8
+ */
9
+ export interface PriceChangeConfirmProps {
10
+ open: boolean;
11
+ previewRate?: string;
12
+ submitRate?: string;
13
+ changePercent: number;
14
+ onConfirm: () => void;
15
+ onCancel: () => void;
16
+ loading?: boolean;
17
+ }
18
+ export default function PriceChangeConfirm({ open, previewRate, submitRate, changePercent, onConfirm, onCancel, loading, }: PriceChangeConfirmProps): import("react").JSX.Element;
@@ -0,0 +1,107 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material";
3
+ import WarningAmberIcon from "@mui/icons-material/WarningAmber";
4
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
+ export default function PriceChangeConfirm({
6
+ open,
7
+ previewRate = void 0,
8
+ submitRate = void 0,
9
+ changePercent,
10
+ onConfirm,
11
+ onCancel,
12
+ loading = false
13
+ }) {
14
+ const { t } = useLocaleContext();
15
+ const changeDirection = changePercent > 0 ? "increased" : "decreased";
16
+ const absChangePercent = Math.abs(changePercent);
17
+ return /* @__PURE__ */ jsxs(
18
+ Dialog,
19
+ {
20
+ open,
21
+ onClose: loading ? void 0 : onCancel,
22
+ maxWidth: "sm",
23
+ fullWidth: true,
24
+ PaperProps: {
25
+ sx: {
26
+ borderRadius: 2
27
+ }
28
+ },
29
+ children: [
30
+ /* @__PURE__ */ jsxs(
31
+ DialogTitle,
32
+ {
33
+ sx: {
34
+ display: "flex",
35
+ alignItems: "center",
36
+ gap: 1,
37
+ pb: 1
38
+ },
39
+ children: [
40
+ /* @__PURE__ */ jsx(WarningAmberIcon, { color: "warning" }),
41
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", component: "span", children: t("payment.checkout.priceChange.title", { fallback: "Price Changed" }) })
42
+ ]
43
+ }
44
+ ),
45
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
46
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", color: "text.secondary", children: t("payment.checkout.priceChange.description", {
47
+ fallback: `The exchange rate has ${changeDirection} by ${absChangePercent.toFixed(2)}% since you started this checkout.`,
48
+ direction: changeDirection,
49
+ percent: absChangePercent.toFixed(2)
50
+ }) }),
51
+ (previewRate || submitRate) && /* @__PURE__ */ jsx(
52
+ Box,
53
+ {
54
+ sx: {
55
+ bgcolor: "action.hover",
56
+ borderRadius: 1,
57
+ p: 2
58
+ },
59
+ children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
60
+ previewRate && /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between" }, children: [
61
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
62
+ t("payment.checkout.priceChange.previewRate", { fallback: "Preview Rate" }),
63
+ ":"
64
+ ] }),
65
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", fontFamily: "monospace", children: previewRate })
66
+ ] }),
67
+ submitRate && /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between" }, children: [
68
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
69
+ t("payment.checkout.priceChange.currentRate", { fallback: "Current Rate" }),
70
+ ":"
71
+ ] }),
72
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", fontFamily: "monospace", children: submitRate })
73
+ ] }),
74
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between" }, children: [
75
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
76
+ t("payment.checkout.priceChange.change", { fallback: "Change" }),
77
+ ":"
78
+ ] }),
79
+ /* @__PURE__ */ jsxs(
80
+ Typography,
81
+ {
82
+ variant: "body2",
83
+ fontWeight: "bold",
84
+ color: changePercent > 0 ? "error.main" : "success.main",
85
+ children: [
86
+ changePercent > 0 ? "+" : "",
87
+ changePercent.toFixed(2),
88
+ "%"
89
+ ]
90
+ }
91
+ )
92
+ ] })
93
+ ] })
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("payment.checkout.priceChange.confirm", {
97
+ fallback: "Do you want to continue with the new price?"
98
+ }) })
99
+ ] }) }),
100
+ /* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, pb: 2 }, children: [
101
+ /* @__PURE__ */ jsx(Button, { onClick: onCancel, disabled: loading, variant: "outlined", color: "inherit", children: t("payment.checkout.priceChange.cancel", { fallback: "Cancel" }) }),
102
+ /* @__PURE__ */ jsx(Button, { onClick: onConfirm, disabled: loading, variant: "contained", color: "primary", autoFocus: true, children: loading ? t("payment.checkout.priceChange.confirming", { fallback: "Confirming..." }) : t("payment.checkout.priceChange.accept", { fallback: "Accept & Continue" }) })
103
+ ] })
104
+ ]
105
+ }
106
+ );
107
+ }
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SlippageConfigValue } from './slippage-config';
3
+ type QuoteDetailRow = {
4
+ label: string;
5
+ value: ReactNode;
6
+ isSlippage?: boolean;
7
+ tooltip?: string;
8
+ };
9
+ type QuoteDetailsPanelProps = {
10
+ rateLine: string;
11
+ rows: QuoteDetailRow[];
12
+ isSubscription?: boolean;
13
+ slippageValue?: number;
14
+ onSlippageChange?: (value: SlippageConfigValue) => void | Promise<void>;
15
+ slippageConfig?: SlippageConfigValue;
16
+ exchangeRate?: string | null;
17
+ baseCurrency?: string;
18
+ disabled?: boolean;
19
+ };
20
+ export default function QuoteDetailsPanel({ rateLine, rows, isSubscription, slippageValue, onSlippageChange, slippageConfig, exchangeRate, baseCurrency, disabled, }: QuoteDetailsPanelProps): import("react").JSX.Element | null;
21
+ export {};