@blocklet/payment-react 1.26.0 → 1.26.2
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/checkout-v2/components/left/cross-sell-card.js +3 -3
- package/es/checkout-v2/components/left/product-item-card.js +13 -7
- package/es/checkout-v2/components/left/promotion-input.d.ts +3 -1
- package/es/checkout-v2/components/left/promotion-input.js +4 -2
- package/es/checkout-v2/components/right/submit-button.js +3 -1
- package/es/checkout-v2/panels/left/composite-panel.js +27 -6
- package/es/checkout-v2/panels/left/credit-topup-panel.js +1 -5
- package/es/checkout-v2/panels/right/payment-panel.js +37 -8
- package/es/checkout-v2/utils/format.d.ts +1 -1
- package/es/checkout-v2/utils/format.js +3 -2
- package/es/checkout-v2/views/error-view.js +2 -0
- package/es/checkout-v2/views/success-view.js +3 -1
- package/es/components/over-due-invoice-payment.js +5 -3
- package/es/libs/util.d.ts +8 -0
- package/es/libs/util.js +3 -0
- package/lib/checkout-v2/components/left/cross-sell-card.js +2 -2
- package/lib/checkout-v2/components/left/product-item-card.js +13 -6
- package/lib/checkout-v2/components/left/promotion-input.d.ts +3 -1
- package/lib/checkout-v2/components/left/promotion-input.js +7 -2
- package/lib/checkout-v2/components/right/submit-button.js +3 -1
- package/lib/checkout-v2/panels/left/composite-panel.js +20 -5
- package/lib/checkout-v2/panels/left/credit-topup-panel.js +1 -5
- package/lib/checkout-v2/panels/right/payment-panel.js +43 -6
- package/lib/checkout-v2/utils/format.d.ts +1 -1
- package/lib/checkout-v2/utils/format.js +9 -2
- package/lib/checkout-v2/views/error-view.js +2 -0
- package/lib/checkout-v2/views/success-view.js +2 -0
- package/lib/components/over-due-invoice-payment.js +12 -2
- package/lib/libs/util.d.ts +8 -0
- package/lib/libs/util.js +4 -0
- package/package.json +4 -4
- package/src/checkout-v2/components/left/cross-sell-card.tsx +3 -3
- package/src/checkout-v2/components/left/product-item-card.tsx +30 -12
- package/src/checkout-v2/components/left/promotion-input.tsx +11 -3
- package/src/checkout-v2/components/right/submit-button.tsx +2 -0
- package/src/checkout-v2/panels/left/composite-panel.tsx +28 -6
- package/src/checkout-v2/panels/left/credit-topup-panel.tsx +1 -5
- package/src/checkout-v2/panels/right/payment-panel.tsx +30 -5
- package/src/checkout-v2/utils/format.ts +5 -2
- package/src/checkout-v2/views/error-view.tsx +2 -0
- package/src/checkout-v2/views/success-view.tsx +3 -1
- package/src/components/over-due-invoice-payment.tsx +6 -3
- package/src/libs/util.ts +7 -0
|
@@ -3,7 +3,7 @@ import AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";
|
|
|
3
3
|
import ShoppingCartCheckoutIcon from "@mui/icons-material/ShoppingCartCheckout";
|
|
4
4
|
import { Avatar, Box, Button, Chip, Stack, Typography } from "@mui/material";
|
|
5
5
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
6
|
-
import { formatDynamicUnitPrice, tSafe, INTERVAL_LOCALE_KEY } from "../../utils/format.js";
|
|
6
|
+
import { formatDynamicUnitPrice, tSafe, INTERVAL_LOCALE_KEY, primaryContrastColor } from "../../utils/format.js";
|
|
7
7
|
export default function CrossSellCard({
|
|
8
8
|
crossSellItem,
|
|
9
9
|
currency,
|
|
@@ -40,7 +40,7 @@ export default function CrossSellCard({
|
|
|
40
40
|
fontWeight: 900,
|
|
41
41
|
letterSpacing: "0.12em",
|
|
42
42
|
bgcolor: "primary.main",
|
|
43
|
-
color:
|
|
43
|
+
color: (theme) => primaryContrastColor(theme),
|
|
44
44
|
boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
|
|
45
45
|
"& .MuiChip-label": { px: 1.5 }
|
|
46
46
|
}
|
|
@@ -147,7 +147,7 @@ export default function CrossSellCard({
|
|
|
147
147
|
transition: "all 0.2s",
|
|
148
148
|
"&:hover": {
|
|
149
149
|
bgcolor: "primary.main",
|
|
150
|
-
color:
|
|
150
|
+
color: (theme) => primaryContrastColor(theme),
|
|
151
151
|
borderColor: "primary.main"
|
|
152
152
|
},
|
|
153
153
|
"&:active": { transform: "scale(0.95)" }
|
|
@@ -22,7 +22,13 @@ import {
|
|
|
22
22
|
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
|
23
23
|
import { getPriceUnitAmountByCurrency } from "@blocklet/payment-react-headless";
|
|
24
24
|
import Toast from "@arcblock/ux/lib/Toast";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
INTERVAL_LOCALE_KEY,
|
|
27
|
+
formatDynamicUnitPrice,
|
|
28
|
+
formatTokenAmount,
|
|
29
|
+
formatTrialText,
|
|
30
|
+
primaryContrastColor
|
|
31
|
+
} from "../../utils/format.js";
|
|
26
32
|
export default function ProductItemCard({
|
|
27
33
|
item,
|
|
28
34
|
currency,
|
|
@@ -73,7 +79,7 @@ export default function ProductItemCard({
|
|
|
73
79
|
const tokenAmount = baseUsd * quantity / rate;
|
|
74
80
|
const abs = Math.abs(tokenAmount);
|
|
75
81
|
const precision = abs > 0 && abs < 0.01 ? 6 : 2;
|
|
76
|
-
return tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(
|
|
82
|
+
return tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
}
|
|
@@ -104,7 +110,7 @@ export default function ProductItemCard({
|
|
|
104
110
|
const discAmount = numericTotal * couponDetails.percent_off / 100;
|
|
105
111
|
const abs = Math.abs(discAmount);
|
|
106
112
|
const precision = abs > 0 && abs < 0.01 ? 6 : 2;
|
|
107
|
-
return `${discAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(
|
|
113
|
+
return `${discAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0"} ${currency?.symbol || ""}`;
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
if (item.discount_amounts?.length > 0 && currency) {
|
|
@@ -153,7 +159,7 @@ export default function ProductItemCard({
|
|
|
153
159
|
const tokenAmount = baseUsd / rate;
|
|
154
160
|
const abs = Math.abs(tokenAmount);
|
|
155
161
|
const precision = abs > 0 && abs < 0.01 ? 6 : 2;
|
|
156
|
-
const formatted = tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(
|
|
162
|
+
const formatted = tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
157
163
|
return `${formatted} ${currency?.symbol || ""} ${slashText}`;
|
|
158
164
|
}
|
|
159
165
|
}
|
|
@@ -179,7 +185,7 @@ export default function ProductItemCard({
|
|
|
179
185
|
const tokenAmount = baseUsd / rate;
|
|
180
186
|
const abs = Math.abs(tokenAmount);
|
|
181
187
|
const precision = abs > 0 && abs < 0.01 ? 6 : 2;
|
|
182
|
-
const formatted = tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(
|
|
188
|
+
const formatted = tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
183
189
|
return `${formatted} ${currency?.symbol || ""} ${originalSlash}`;
|
|
184
190
|
}
|
|
185
191
|
}
|
|
@@ -217,7 +223,7 @@ export default function ProductItemCard({
|
|
|
217
223
|
fontWeight: 900,
|
|
218
224
|
letterSpacing: "0.12em",
|
|
219
225
|
bgcolor: "primary.main",
|
|
220
|
-
color:
|
|
226
|
+
color: (th) => primaryContrastColor(th),
|
|
221
227
|
boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
|
|
222
228
|
"& .MuiChip-label": { px: 1.5 }
|
|
223
229
|
}
|
|
@@ -388,7 +394,7 @@ export default function ProductItemCard({
|
|
|
388
394
|
]
|
|
389
395
|
}
|
|
390
396
|
),
|
|
391
|
-
discountCode && perItemDiscount && /* @__PURE__ */ jsx(Box, { sx: { mt: 1.5 }, children: /* @__PURE__ */ jsx(
|
|
397
|
+
discountCode && perItemDiscount && /* @__PURE__ */ jsx(Box, { sx: { mt: 1.5 }, children: isRateLoading ? /* @__PURE__ */ jsx(Skeleton, { variant: "rounded", width: 160, height: 22, sx: { borderRadius: "6px" } }) : /* @__PURE__ */ jsx(
|
|
392
398
|
Chip,
|
|
393
399
|
{
|
|
394
400
|
icon: /* @__PURE__ */ jsx(LocalOfferIcon, { sx: { color: "warning.main", fontSize: "small" } }),
|
|
@@ -14,6 +14,8 @@ interface PromotionInputProps {
|
|
|
14
14
|
discountAmount: string | null;
|
|
15
15
|
/** Start with input field visible (skip the "Add promotion code" button) */
|
|
16
16
|
initialShowInput?: boolean;
|
|
17
|
+
/** Show skeleton for the discount amount while switching */
|
|
18
|
+
isAmountLoading?: boolean;
|
|
17
19
|
}
|
|
18
|
-
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
20
|
+
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
19
21
|
export {};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
CircularProgress,
|
|
11
11
|
IconButton,
|
|
12
12
|
InputAdornment,
|
|
13
|
+
Skeleton,
|
|
13
14
|
Stack,
|
|
14
15
|
TextField,
|
|
15
16
|
Typography
|
|
@@ -19,7 +20,8 @@ export default function PromotionInput({
|
|
|
19
20
|
promotion,
|
|
20
21
|
discounts,
|
|
21
22
|
discountAmount,
|
|
22
|
-
initialShowInput = false
|
|
23
|
+
initialShowInput = false,
|
|
24
|
+
isAmountLoading = false
|
|
23
25
|
}) {
|
|
24
26
|
const { t } = useLocaleContext();
|
|
25
27
|
const [showInput, setShowInput] = useState(false);
|
|
@@ -91,7 +93,7 @@ export default function PromotionInput({
|
|
|
91
93
|
]
|
|
92
94
|
}
|
|
93
95
|
),
|
|
94
|
-
/* @__PURE__ */ jsxs(Typography, { sx: { color: "text.primary", fontWeight: 600, fontSize: 14 }, children: [
|
|
96
|
+
isAmountLoading ? /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: 80, height: 22 }) : /* @__PURE__ */ jsxs(Typography, { sx: { color: "text.primary", fontWeight: 600, fontSize: 14 }, children: [
|
|
95
97
|
"-",
|
|
96
98
|
discountAmount || "0"
|
|
97
99
|
] })
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Button, CircularProgress } from "@mui/material";
|
|
3
|
+
import { primaryContrastColor } from "../../utils/format.js";
|
|
3
4
|
export default function SubmitButton({
|
|
4
5
|
canSubmit,
|
|
5
6
|
isProcessing,
|
|
@@ -21,7 +22,8 @@ export default function SubmitButton({
|
|
|
21
22
|
py: 1.5,
|
|
22
23
|
fontSize: "1.3rem",
|
|
23
24
|
fontWeight: 600,
|
|
24
|
-
textTransform: "none"
|
|
25
|
+
textTransform: "none",
|
|
26
|
+
color: (theme) => primaryContrastColor(theme)
|
|
25
27
|
},
|
|
26
28
|
children: isProcessing ? processingLabel : label
|
|
27
29
|
}
|
|
@@ -17,7 +17,13 @@ import {
|
|
|
17
17
|
useProduct
|
|
18
18
|
} from "@blocklet/payment-react-headless";
|
|
19
19
|
import { useMobile } from "../../../hooks/mobile.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
INTERVAL_LOCALE_KEY,
|
|
22
|
+
formatTrialText,
|
|
23
|
+
getSessionHeaderMeta,
|
|
24
|
+
tSafe,
|
|
25
|
+
primaryContrastColor
|
|
26
|
+
} from "../../utils/format.js";
|
|
21
27
|
import ProductItemCard from "../../components/left/product-item-card.js";
|
|
22
28
|
import BillingToggle from "../../components/left/billing-toggle.js";
|
|
23
29
|
import CrossSellCard from "../../components/left/cross-sell-card.js";
|
|
@@ -51,6 +57,9 @@ export default function CompositePanel() {
|
|
|
51
57
|
const canUpsell = nonCrossSellItems.length <= 1;
|
|
52
58
|
const hasTopUpsell = canUpsell && !!upsellPrimaryItem && ["subscription", "setup"].includes(mode);
|
|
53
59
|
const isUpselled = !!upsellPrimaryItem?.upsell_price;
|
|
60
|
+
const [upsellSwitching, setUpsellSwitching] = useState(false);
|
|
61
|
+
const [pendingUpsell, setPendingUpsell] = useState(null);
|
|
62
|
+
const visualIsUpselled = pendingUpsell !== null ? pendingUpsell : isUpselled;
|
|
54
63
|
const currentInterval = hasTopUpsell ? upsellPrimaryItem.price?.recurring?.interval : null;
|
|
55
64
|
const upsellInterval = hasTopUpsell ? upsellTarget?.recurring?.interval : null;
|
|
56
65
|
let upsellSavings = 0;
|
|
@@ -75,7 +84,7 @@ export default function CompositePanel() {
|
|
|
75
84
|
const isMultiItem = lineItems.items.length > 1;
|
|
76
85
|
const activeSx = {
|
|
77
86
|
bgcolor: "primary.main",
|
|
78
|
-
color:
|
|
87
|
+
color: (theme) => primaryContrastColor(theme),
|
|
79
88
|
boxShadow: "0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)"
|
|
80
89
|
};
|
|
81
90
|
const inactiveSx = {
|
|
@@ -223,17 +232,23 @@ export default function CompositePanel() {
|
|
|
223
232
|
Box,
|
|
224
233
|
{
|
|
225
234
|
onClick: async () => {
|
|
226
|
-
if (isUpselled) {
|
|
235
|
+
if (isUpselled && !upsellSwitching) {
|
|
236
|
+
setPendingUpsell(false);
|
|
237
|
+
setUpsellSwitching(true);
|
|
227
238
|
try {
|
|
228
239
|
await lineItems.downsell(
|
|
229
240
|
upsellPrimaryItem.upsell_price?.id || upsellPrimaryItem.price_id
|
|
230
241
|
);
|
|
231
242
|
} catch (err) {
|
|
243
|
+
setPendingUpsell(null);
|
|
232
244
|
Toast.error(err?.response?.data?.error || err?.message || "Failed");
|
|
245
|
+
} finally {
|
|
246
|
+
setUpsellSwitching(false);
|
|
247
|
+
setPendingUpsell(null);
|
|
233
248
|
}
|
|
234
249
|
}
|
|
235
250
|
},
|
|
236
|
-
sx: capsuleBtnSx(!
|
|
251
|
+
sx: capsuleBtnSx(!visualIsUpselled),
|
|
237
252
|
children: /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: 14, fontWeight: 700, color: "inherit", lineHeight: 1 }, children: t(INTERVAL_LOCALE_KEY[currentInterval] || "") })
|
|
238
253
|
}
|
|
239
254
|
),
|
|
@@ -241,15 +256,21 @@ export default function CompositePanel() {
|
|
|
241
256
|
Box,
|
|
242
257
|
{
|
|
243
258
|
onClick: async () => {
|
|
244
|
-
if (!isUpselled) {
|
|
259
|
+
if (!isUpselled && !upsellSwitching) {
|
|
260
|
+
setPendingUpsell(true);
|
|
261
|
+
setUpsellSwitching(true);
|
|
245
262
|
try {
|
|
246
263
|
await lineItems.upsell(upsellPrimaryItem.price_id, upsellTarget.id);
|
|
247
264
|
} catch (err) {
|
|
265
|
+
setPendingUpsell(null);
|
|
248
266
|
Toast.error(err?.response?.data?.error || err?.message || "Failed");
|
|
267
|
+
} finally {
|
|
268
|
+
setUpsellSwitching(false);
|
|
269
|
+
setPendingUpsell(null);
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
272
|
},
|
|
252
|
-
sx: capsuleBtnSx(
|
|
273
|
+
sx: capsuleBtnSx(visualIsUpselled),
|
|
253
274
|
children: /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: 14, fontWeight: 700, color: "inherit", lineHeight: 1 }, children: t(INTERVAL_LOCALE_KEY[upsellInterval] || "") })
|
|
254
275
|
}
|
|
255
276
|
)
|
|
@@ -116,12 +116,8 @@ export default function CreditTopupPanel() {
|
|
|
116
116
|
const intervalDisplay = intervalValue === 1 ? t(`common.${intervalUnit}`) : `${intervalValue} ${t(`common.${intervalUnit}s`)}`;
|
|
117
117
|
return scheduleConfig.expire_with_next_grant ? t("payment.checkout.credit.schedule.withRefresh", { amount: formattedAmount, interval: intervalDisplay }) : t("payment.checkout.credit.schedule.periodic", { amount: formattedAmount, interval: intervalDisplay });
|
|
118
118
|
}
|
|
119
|
-
const productDesc = product?.description || "";
|
|
120
|
-
if (productDesc && productDesc.length > 10 && productDesc !== creditName) {
|
|
121
|
-
return productDesc;
|
|
122
|
-
}
|
|
123
119
|
return "";
|
|
124
|
-
}, [creditAmount, currencySymbol, hasSchedule, scheduleConfig,
|
|
120
|
+
}, [creditAmount, currencySymbol, hasSchedule, scheduleConfig, t]);
|
|
125
121
|
const validityText = useMemo(() => {
|
|
126
122
|
if (!hasExpiry) return "";
|
|
127
123
|
return t("payment.checkout.creditTopup.validFor", {
|
|
@@ -40,9 +40,9 @@ import {
|
|
|
40
40
|
import { joinURL } from "ufo";
|
|
41
41
|
import { usePaymentContext } from "../../../contexts/payment.js";
|
|
42
42
|
import { useMobile } from "../../../hooks/mobile.js";
|
|
43
|
-
import { getPrefix } from "../../../libs/util.js";
|
|
43
|
+
import { getPrefix, getStatementDescriptor } from "../../../libs/util.js";
|
|
44
44
|
import OverdueInvoicePayment from "../../../components/over-due-invoice-payment.js";
|
|
45
|
-
import { tSafe, whiteTooltipSx } from "../../utils/format.js";
|
|
45
|
+
import { tSafe, whiteTooltipSx, primaryContrastColor } from "../../utils/format.js";
|
|
46
46
|
import CustomerInfoCard from "../../components/right/customer-info-card.js";
|
|
47
47
|
import SubscriptionDisclaimer from "../../components/right/subscription-disclaimer.js";
|
|
48
48
|
import StatusFeedback from "../../components/right/status-feedback.js";
|
|
@@ -412,7 +412,8 @@ export default function PaymentPanel() {
|
|
|
412
412
|
remove: promotion.remove
|
|
413
413
|
},
|
|
414
414
|
discounts,
|
|
415
|
-
discountAmount: pricing.discount
|
|
415
|
+
discountAmount: pricing.discount,
|
|
416
|
+
isAmountLoading
|
|
416
417
|
}
|
|
417
418
|
)
|
|
418
419
|
] }),
|
|
@@ -502,7 +503,7 @@ export default function PaymentPanel() {
|
|
|
502
503
|
] })
|
|
503
504
|
] });
|
|
504
505
|
})(),
|
|
505
|
-
/* @__PURE__ */
|
|
506
|
+
/* @__PURE__ */ jsxs(
|
|
506
507
|
Button,
|
|
507
508
|
{
|
|
508
509
|
variant: "contained",
|
|
@@ -511,15 +512,43 @@ export default function PaymentPanel() {
|
|
|
511
512
|
disabled: !canSubmit || submit.status === "waiting_stripe",
|
|
512
513
|
onClick: handleAction,
|
|
513
514
|
startIcon: isProcessing ? /* @__PURE__ */ jsx(CircularProgress, { size: 20, color: "inherit" }) : null,
|
|
514
|
-
endIcon: !isProcessing ? /* @__PURE__ */ jsx(ArrowForwardIcon, {}) : void 0,
|
|
515
515
|
sx: {
|
|
516
516
|
py: 1.5,
|
|
517
517
|
fontSize: "1.1rem",
|
|
518
518
|
fontWeight: 600,
|
|
519
519
|
textTransform: "none",
|
|
520
|
-
borderRadius: "12px"
|
|
520
|
+
borderRadius: "12px",
|
|
521
|
+
color: (theme) => primaryContrastColor(theme),
|
|
522
|
+
position: "relative",
|
|
523
|
+
overflow: "hidden",
|
|
524
|
+
"&:hover": { bgcolor: "primary.main" },
|
|
525
|
+
"&:hover .arrow-icon": { transform: "translateX(4px)" },
|
|
526
|
+
"&:hover .shine-layer": { transform: "translateX(100%)" }
|
|
521
527
|
},
|
|
522
|
-
children:
|
|
528
|
+
children: [
|
|
529
|
+
/* @__PURE__ */ jsx(Box, { component: "span", sx: { position: "relative", zIndex: 1 }, children: isProcessing ? `${t("payment.checkout.processing")}...` : buttonLabel }),
|
|
530
|
+
!isProcessing && /* @__PURE__ */ jsx(
|
|
531
|
+
ArrowForwardIcon,
|
|
532
|
+
{
|
|
533
|
+
className: "arrow-icon",
|
|
534
|
+
sx: { ml: 1, position: "relative", zIndex: 1, transition: "transform 0.2s ease" }
|
|
535
|
+
}
|
|
536
|
+
),
|
|
537
|
+
/* @__PURE__ */ jsx(
|
|
538
|
+
Box,
|
|
539
|
+
{
|
|
540
|
+
className: "shine-layer",
|
|
541
|
+
sx: {
|
|
542
|
+
position: "absolute",
|
|
543
|
+
inset: 0,
|
|
544
|
+
background: "linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)",
|
|
545
|
+
transform: "translateX(-100%)",
|
|
546
|
+
transition: "transform 0.7s ease",
|
|
547
|
+
pointerEvents: "none"
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
]
|
|
523
552
|
}
|
|
524
553
|
),
|
|
525
554
|
isMobile && /* @__PURE__ */ jsxs(
|
|
@@ -588,7 +617,7 @@ export default function PaymentPanel() {
|
|
|
588
617
|
mode,
|
|
589
618
|
subscription,
|
|
590
619
|
staking: pricing.staking,
|
|
591
|
-
appName: session?.
|
|
620
|
+
appName: getStatementDescriptor(session?.line_items || [])
|
|
592
621
|
}
|
|
593
622
|
),
|
|
594
623
|
!isMobile && /* @__PURE__ */ jsxs(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TPaymentCurrency } from '@blocklet/payment-types';
|
|
2
|
+
export { primaryContrastColor } from '../../libs/util';
|
|
2
3
|
export declare const INTERVAL_LOCALE_KEY: Record<string, string>;
|
|
3
4
|
export declare function countryCodeToFlag(code: string): string;
|
|
4
5
|
export declare function formatTokenAmount(unitAmount: string | number | bigint, currency: TPaymentCurrency | null): string;
|
|
@@ -56,4 +57,3 @@ interface ItemMeta {
|
|
|
56
57
|
* Works for the "primary product" header above the item list.
|
|
57
58
|
*/
|
|
58
59
|
export declare function getSessionHeaderMeta(t: TFn, session: any, product: any, items: any[]): ItemMeta;
|
|
59
|
-
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fromUnitToToken } from "@ocap/util";
|
|
2
|
+
export { primaryContrastColor } from "../../libs/util.js";
|
|
2
3
|
export const INTERVAL_LOCALE_KEY = {
|
|
3
4
|
day: "common.daily",
|
|
4
5
|
week: "common.weekly",
|
|
@@ -20,7 +21,7 @@ export function formatTokenAmount(unitAmount, currency) {
|
|
|
20
21
|
const abs = Math.abs(num);
|
|
21
22
|
const precision = abs > 0 && abs < 0.01 ? 6 : 2;
|
|
22
23
|
const formatted = num.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision });
|
|
23
|
-
return formatted.replace(
|
|
24
|
+
return formatted.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
24
25
|
} catch {
|
|
25
26
|
return "0";
|
|
26
27
|
}
|
|
@@ -51,7 +52,7 @@ export function formatDynamicUnitPrice(price, currency, exchangeRate) {
|
|
|
51
52
|
const tokenAmount = baseUsd / rate;
|
|
52
53
|
const abs = Math.abs(tokenAmount);
|
|
53
54
|
const precision = abs > 0 && abs < 0.01 ? 6 : 2;
|
|
54
|
-
return tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(
|
|
55
|
+
return tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -4,6 +4,7 @@ import { alpha, useTheme } from "@mui/material/styles";
|
|
|
4
4
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|
5
5
|
import Header from "@blocklet/ui-react/lib/Header";
|
|
6
6
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
7
|
+
import { primaryContrastColor } from "../utils/format.js";
|
|
7
8
|
function GeometricDecoration() {
|
|
8
9
|
const theme = useTheme();
|
|
9
10
|
const gridColor = alpha(theme.palette.primary.main, 0.06);
|
|
@@ -173,6 +174,7 @@ function ErrorContent({ error, errorCode = void 0 }) {
|
|
|
173
174
|
fontWeight: 600,
|
|
174
175
|
fontSize: 16,
|
|
175
176
|
letterSpacing: "0.02em",
|
|
177
|
+
color: (th) => primaryContrastColor(th),
|
|
176
178
|
boxShadow: `0 8px 32px -4px ${alpha(primaryColor, 0.3)}`,
|
|
177
179
|
"&:hover": {
|
|
178
180
|
boxShadow: `0 12px 40px -4px ${alpha(primaryColor, 0.4)}`,
|
|
@@ -22,7 +22,7 @@ import { joinURL } from "ufo";
|
|
|
22
22
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
23
23
|
import { usePaymentMethodContext } from "@blocklet/payment-react-headless";
|
|
24
24
|
import { getPrefix } from "../../libs/util.js";
|
|
25
|
-
import { formatTokenAmount } from "../utils/format.js";
|
|
25
|
+
import { formatTokenAmount, primaryContrastColor } from "../utils/format.js";
|
|
26
26
|
const scaleIn = keyframes`
|
|
27
27
|
from { transform: scale(0); opacity: 0; }
|
|
28
28
|
60% { transform: scale(1.15); }
|
|
@@ -452,6 +452,7 @@ function SubscriptionLinks({
|
|
|
452
452
|
fontWeight: 700,
|
|
453
453
|
fontSize: { xs: 16, md: 17 },
|
|
454
454
|
letterSpacing: "0.02em",
|
|
455
|
+
color: (theme) => primaryContrastColor(theme),
|
|
455
456
|
boxShadow: "0 8px 24px -4px rgba(59,130,246,0.25)",
|
|
456
457
|
"&:hover": {
|
|
457
458
|
boxShadow: "0 12px 28px -4px rgba(59,130,246,0.35)"
|
|
@@ -486,6 +487,7 @@ function InvoiceLink({
|
|
|
486
487
|
fontWeight: 700,
|
|
487
488
|
fontSize: { xs: 16, md: 17 },
|
|
488
489
|
letterSpacing: "0.02em",
|
|
490
|
+
color: (theme) => primaryContrastColor(theme),
|
|
489
491
|
boxShadow: "0 8px 24px -4px rgba(59,130,246,0.25)",
|
|
490
492
|
"&:hover": {
|
|
491
493
|
boxShadow: "0 12px 28px -4px rgba(59,130,246,0.35)"
|
|
@@ -11,7 +11,7 @@ import Dialog from "@arcblock/ux/lib/Dialog/dialog";
|
|
|
11
11
|
import { CheckCircle as CheckCircleIcon } from "@mui/icons-material";
|
|
12
12
|
import debounce from "lodash/debounce";
|
|
13
13
|
import { usePaymentContext } from "../contexts/payment.js";
|
|
14
|
-
import { formatAmount, formatError, getPrefix, isCrossOrigin } from "../libs/util.js";
|
|
14
|
+
import { formatAmount, formatError, getPrefix, isCrossOrigin, primaryContrastColor } from "../libs/util.js";
|
|
15
15
|
import { useSubscription } from "../hooks/subscription.js";
|
|
16
16
|
import api from "../libs/api.js";
|
|
17
17
|
import LoadingButton from "./loading-button.js";
|
|
@@ -290,6 +290,7 @@ function OverdueInvoicePayment({
|
|
|
290
290
|
const { currency } = item;
|
|
291
291
|
const inProcess = payLoading && selectCurrencyId === currency.id;
|
|
292
292
|
const status = paymentStatus[currency.id] || "idle";
|
|
293
|
+
const containedColorSx = (options?.variant || "contained") === "contained" ? { color: (th) => primaryContrastColor(th) } : {};
|
|
293
294
|
if (status === "success") {
|
|
294
295
|
return /* @__PURE__ */ jsx(
|
|
295
296
|
Button,
|
|
@@ -297,6 +298,7 @@ function OverdueInvoicePayment({
|
|
|
297
298
|
variant: options?.variant || "contained",
|
|
298
299
|
size: "small",
|
|
299
300
|
onClick: () => checkAndHandleInvoicePaid(currency.id),
|
|
301
|
+
sx: containedColorSx,
|
|
300
302
|
...primaryButton ? {} : {
|
|
301
303
|
color: "success",
|
|
302
304
|
startIcon: /* @__PURE__ */ jsx(CheckCircleIcon, {})
|
|
@@ -334,7 +336,7 @@ function OverdueInvoicePayment({
|
|
|
334
336
|
disabled: paying || status === "processing",
|
|
335
337
|
loading: paying || status === "processing",
|
|
336
338
|
onClick: onPay,
|
|
337
|
-
sx: options?.sx,
|
|
339
|
+
sx: { ...containedColorSx, ...options?.sx || {} },
|
|
338
340
|
children: buttonText
|
|
339
341
|
}
|
|
340
342
|
)
|
|
@@ -349,7 +351,7 @@ function OverdueInvoicePayment({
|
|
|
349
351
|
disabled: inProcess,
|
|
350
352
|
loading: inProcess,
|
|
351
353
|
onClick: () => handlePay(item),
|
|
352
|
-
sx: options?.sx,
|
|
354
|
+
sx: { ...containedColorSx, ...options?.sx || {} },
|
|
353
355
|
children: status === "error" ? t("payment.subscription.overdue.retry") : t("payment.subscription.overdue.payNow")
|
|
354
356
|
}
|
|
355
357
|
);
|
package/es/libs/util.d.ts
CHANGED
|
@@ -194,3 +194,11 @@ export declare function getTokenBalanceLink(method: TPaymentMethod, address: str
|
|
|
194
194
|
export declare function isCreditMetered(price: TPrice): boolean;
|
|
195
195
|
export declare function showStaking(method: TPaymentMethod, currency: TPaymentCurrency, noStake: boolean): boolean;
|
|
196
196
|
export declare function formatLinkWithLocale(url: string, locale?: string): string;
|
|
197
|
+
export declare function primaryContrastColor(theme: {
|
|
198
|
+
palette: {
|
|
199
|
+
primary: {
|
|
200
|
+
main: string;
|
|
201
|
+
};
|
|
202
|
+
getContrastText: (bg: string) => string;
|
|
203
|
+
};
|
|
204
|
+
}): string;
|
package/es/libs/util.js
CHANGED
|
@@ -54,7 +54,7 @@ function CrossSellCard({
|
|
|
54
54
|
fontWeight: 900,
|
|
55
55
|
letterSpacing: "0.12em",
|
|
56
56
|
bgcolor: "primary.main",
|
|
57
|
-
color:
|
|
57
|
+
color: theme => (0, _format.primaryContrastColor)(theme),
|
|
58
58
|
boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
|
|
59
59
|
"& .MuiChip-label": {
|
|
60
60
|
px: 1.5
|
|
@@ -236,7 +236,7 @@ function CrossSellCard({
|
|
|
236
236
|
transition: "all 0.2s",
|
|
237
237
|
"&:hover": {
|
|
238
238
|
bgcolor: "primary.main",
|
|
239
|
-
color:
|
|
239
|
+
color: theme => (0, _format.primaryContrastColor)(theme),
|
|
240
240
|
borderColor: "primary.main"
|
|
241
241
|
},
|
|
242
242
|
"&:active": {
|
|
@@ -70,7 +70,7 @@ function ProductItemCard({
|
|
|
70
70
|
return tokenAmount.toLocaleString("en-US", {
|
|
71
71
|
minimumFractionDigits: 0,
|
|
72
72
|
maximumFractionDigits: precision
|
|
73
|
-
}).replace(
|
|
73
|
+
}).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -107,7 +107,7 @@ function ProductItemCard({
|
|
|
107
107
|
return `${discAmount.toLocaleString("en-US", {
|
|
108
108
|
minimumFractionDigits: 0,
|
|
109
109
|
maximumFractionDigits: precision
|
|
110
|
-
}).replace(
|
|
110
|
+
}).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0"} ${currency?.symbol || ""}`;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
if (item.discount_amounts?.length > 0 && currency) {
|
|
@@ -166,7 +166,7 @@ function ProductItemCard({
|
|
|
166
166
|
const formatted = tokenAmount.toLocaleString("en-US", {
|
|
167
167
|
minimumFractionDigits: 0,
|
|
168
168
|
maximumFractionDigits: precision
|
|
169
|
-
}).replace(
|
|
169
|
+
}).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
170
170
|
return `${formatted} ${currency?.symbol || ""} ${slashText}`;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
@@ -200,7 +200,7 @@ function ProductItemCard({
|
|
|
200
200
|
const formatted = tokenAmount.toLocaleString("en-US", {
|
|
201
201
|
minimumFractionDigits: 0,
|
|
202
202
|
maximumFractionDigits: precision
|
|
203
|
-
}).replace(
|
|
203
|
+
}).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0";
|
|
204
204
|
return `${formatted} ${currency?.symbol || ""} ${originalSlash}`;
|
|
205
205
|
}
|
|
206
206
|
}
|
|
@@ -246,7 +246,7 @@ function ProductItemCard({
|
|
|
246
246
|
fontWeight: 900,
|
|
247
247
|
letterSpacing: "0.12em",
|
|
248
248
|
bgcolor: "primary.main",
|
|
249
|
-
color:
|
|
249
|
+
color: th => (0, _format.primaryContrastColor)(th),
|
|
250
250
|
boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
|
|
251
251
|
"& .MuiChip-label": {
|
|
252
252
|
px: 1.5
|
|
@@ -513,7 +513,14 @@ function ProductItemCard({
|
|
|
513
513
|
sx: {
|
|
514
514
|
mt: 1.5
|
|
515
515
|
},
|
|
516
|
-
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.
|
|
516
|
+
children: isRateLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, {
|
|
517
|
+
variant: "rounded",
|
|
518
|
+
width: 160,
|
|
519
|
+
height: 22,
|
|
520
|
+
sx: {
|
|
521
|
+
borderRadius: "6px"
|
|
522
|
+
}
|
|
523
|
+
}) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
|
|
517
524
|
icon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_LocalOffer.default, {
|
|
518
525
|
sx: {
|
|
519
526
|
color: "warning.main",
|
|
@@ -14,6 +14,8 @@ interface PromotionInputProps {
|
|
|
14
14
|
discountAmount: string | null;
|
|
15
15
|
/** Start with input field visible (skip the "Add promotion code" button) */
|
|
16
16
|
initialShowInput?: boolean;
|
|
17
|
+
/** Show skeleton for the discount amount while switching */
|
|
18
|
+
isAmountLoading?: boolean;
|
|
17
19
|
}
|
|
18
|
-
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
20
|
+
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
19
21
|
export {};
|
|
@@ -16,7 +16,8 @@ function PromotionInput({
|
|
|
16
16
|
promotion,
|
|
17
17
|
discounts,
|
|
18
18
|
discountAmount,
|
|
19
|
-
initialShowInput = false
|
|
19
|
+
initialShowInput = false,
|
|
20
|
+
isAmountLoading = false
|
|
20
21
|
}) {
|
|
21
22
|
const {
|
|
22
23
|
t
|
|
@@ -116,7 +117,11 @@ function PromotionInput({
|
|
|
116
117
|
}
|
|
117
118
|
})
|
|
118
119
|
})]
|
|
119
|
-
}), /* @__PURE__ */(0, _jsxRuntime.
|
|
120
|
+
}), isAmountLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, {
|
|
121
|
+
variant: "text",
|
|
122
|
+
width: 80,
|
|
123
|
+
height: 22
|
|
124
|
+
}) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
|
|
120
125
|
sx: {
|
|
121
126
|
color: "text.primary",
|
|
122
127
|
fontWeight: 600,
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
module.exports = SubmitButton;
|
|
7
7
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
8
8
|
var _material = require("@mui/material");
|
|
9
|
+
var _format = require("../../utils/format");
|
|
9
10
|
function SubmitButton({
|
|
10
11
|
canSubmit,
|
|
11
12
|
isProcessing,
|
|
@@ -28,7 +29,8 @@ function SubmitButton({
|
|
|
28
29
|
py: 1.5,
|
|
29
30
|
fontSize: "1.3rem",
|
|
30
31
|
fontWeight: 600,
|
|
31
|
-
textTransform: "none"
|
|
32
|
+
textTransform: "none",
|
|
33
|
+
color: theme => (0, _format.primaryContrastColor)(theme)
|
|
32
34
|
},
|
|
33
35
|
children: isProcessing ? processingLabel : label
|
|
34
36
|
});
|