@blocklet/payment-react 1.24.3 → 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.
- package/es/components/auto-topup/modal.d.ts +2 -0
- package/es/components/auto-topup/modal.js +48 -6
- package/es/components/auto-topup/product-card.d.ts +16 -1
- package/es/components/auto-topup/product-card.js +97 -15
- package/es/components/dynamic-pricing-unavailable.d.ts +9 -0
- package/es/components/dynamic-pricing-unavailable.js +58 -0
- package/es/components/loading-amount.d.ts +17 -0
- package/es/components/loading-amount.js +46 -0
- package/es/components/price-change-confirm.d.ts +18 -0
- package/es/components/price-change-confirm.js +107 -0
- package/es/components/quote-details-panel.d.ts +21 -0
- package/es/components/quote-details-panel.js +170 -0
- package/es/components/quote-lock-banner.d.ts +7 -0
- package/es/components/quote-lock-banner.js +79 -0
- package/es/components/slippage-config.d.ts +20 -0
- package/es/components/slippage-config.js +261 -0
- package/es/history/credit/transactions-list.js +11 -1
- package/es/history/invoice/list.js +125 -15
- package/es/hooks/dynamic-pricing.d.ts +102 -0
- package/es/hooks/dynamic-pricing.js +393 -0
- package/es/index.d.ts +6 -1
- package/es/index.js +9 -1
- package/es/libs/util.d.ts +42 -5
- package/es/libs/util.js +345 -57
- package/es/locales/en.js +114 -3
- package/es/locales/zh.js +114 -3
- package/es/payment/form/index.d.ts +4 -1
- package/es/payment/form/index.js +454 -22
- package/es/payment/index.d.ts +1 -1
- package/es/payment/index.js +279 -16
- package/es/payment/product-item.d.ts +26 -1
- package/es/payment/product-item.js +330 -51
- package/es/payment/summary-section/promotion-section.d.ts +32 -0
- package/es/payment/summary-section/promotion-section.js +143 -0
- package/es/payment/summary-section/total-section.d.ts +39 -0
- package/es/payment/summary-section/total-section.js +83 -0
- package/es/payment/summary.d.ts +17 -2
- package/es/payment/summary.js +300 -253
- package/es/types/index.d.ts +11 -0
- package/lib/components/auto-topup/modal.d.ts +2 -0
- package/lib/components/auto-topup/modal.js +54 -6
- package/lib/components/auto-topup/product-card.d.ts +16 -1
- package/lib/components/auto-topup/product-card.js +75 -7
- package/lib/components/dynamic-pricing-unavailable.d.ts +9 -0
- package/lib/components/dynamic-pricing-unavailable.js +81 -0
- package/lib/components/loading-amount.d.ts +17 -0
- package/lib/components/loading-amount.js +53 -0
- package/lib/components/price-change-confirm.d.ts +18 -0
- package/lib/components/price-change-confirm.js +157 -0
- package/lib/components/quote-details-panel.d.ts +21 -0
- package/lib/components/quote-details-panel.js +226 -0
- package/lib/components/quote-lock-banner.d.ts +7 -0
- package/lib/components/quote-lock-banner.js +93 -0
- package/lib/components/slippage-config.d.ts +20 -0
- package/lib/components/slippage-config.js +316 -0
- package/lib/history/credit/transactions-list.js +11 -1
- package/lib/history/invoice/list.js +167 -27
- package/lib/hooks/dynamic-pricing.d.ts +102 -0
- package/lib/hooks/dynamic-pricing.js +390 -0
- package/lib/index.d.ts +6 -1
- package/lib/index.js +32 -0
- package/lib/libs/util.d.ts +42 -5
- package/lib/libs/util.js +367 -49
- package/lib/locales/en.js +114 -3
- package/lib/locales/zh.js +114 -3
- package/lib/payment/form/index.d.ts +4 -1
- package/lib/payment/form/index.js +476 -20
- package/lib/payment/index.d.ts +1 -1
- package/lib/payment/index.js +308 -14
- package/lib/payment/product-item.d.ts +26 -1
- package/lib/payment/product-item.js +270 -35
- package/lib/payment/summary-section/promotion-section.d.ts +32 -0
- package/lib/payment/summary-section/promotion-section.js +133 -0
- package/lib/payment/summary-section/total-section.d.ts +39 -0
- package/lib/payment/summary-section/total-section.js +117 -0
- package/lib/payment/summary.d.ts +17 -2
- package/lib/payment/summary.js +205 -127
- package/lib/types/index.d.ts +11 -0
- package/package.json +3 -3
- package/src/components/auto-topup/modal.tsx +59 -6
- package/src/components/auto-topup/product-card.tsx +118 -11
- package/src/components/dynamic-pricing-unavailable.tsx +69 -0
- package/src/components/loading-amount.tsx +66 -0
- package/src/components/price-change-confirm.tsx +136 -0
- package/src/components/quote-details-panel.tsx +218 -0
- package/src/components/quote-lock-banner.tsx +99 -0
- package/src/components/slippage-config.tsx +336 -0
- package/src/history/credit/transactions-list.tsx +14 -1
- package/src/history/invoice/list.tsx +143 -9
- package/src/hooks/dynamic-pricing.ts +617 -0
- package/src/index.ts +9 -0
- package/src/libs/util.ts +473 -58
- package/src/locales/en.tsx +117 -0
- package/src/locales/zh.tsx +111 -0
- package/src/payment/form/index.tsx +561 -19
- package/src/payment/index.tsx +349 -10
- package/src/payment/product-item.tsx +451 -37
- package/src/payment/summary-section/promotion-section.tsx +172 -0
- package/src/payment/summary-section/total-section.tsx +141 -0
- package/src/payment/summary.tsx +334 -192
- 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
|
|
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 {
|
|
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: "
|
|
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__ */
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 {};
|