@blocklet/payment-react 1.26.1 → 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 +9 -3
- 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/right/payment-panel.js +37 -8
- package/es/checkout-v2/utils/format.d.ts +1 -1
- package/es/checkout-v2/utils/format.js +1 -0
- 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 +9 -2
- 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/right/payment-panel.js +43 -6
- package/lib/checkout-v2/utils/format.d.ts +1 -1
- package/lib/checkout-v2/utils/format.js +7 -0
- 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 +18 -8
- 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/right/payment-panel.tsx +30 -5
- package/src/checkout-v2/utils/format.ts +2 -0
- 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,
|
|
@@ -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
|
)
|
|
@@ -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 {};
|
|
@@ -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": {
|
|
@@ -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
|
});
|
|
@@ -61,6 +61,9 @@ function CompositePanel() {
|
|
|
61
61
|
const canUpsell = nonCrossSellItems.length <= 1;
|
|
62
62
|
const hasTopUpsell = canUpsell && !!upsellPrimaryItem && ["subscription", "setup"].includes(mode);
|
|
63
63
|
const isUpselled = !!upsellPrimaryItem?.upsell_price;
|
|
64
|
+
const [upsellSwitching, setUpsellSwitching] = (0, _react.useState)(false);
|
|
65
|
+
const [pendingUpsell, setPendingUpsell] = (0, _react.useState)(null);
|
|
66
|
+
const visualIsUpselled = pendingUpsell !== null ? pendingUpsell : isUpselled;
|
|
64
67
|
const currentInterval = hasTopUpsell ? upsellPrimaryItem.price?.recurring?.interval : null;
|
|
65
68
|
const upsellInterval = hasTopUpsell ? upsellTarget?.recurring?.interval : null;
|
|
66
69
|
let upsellSavings = 0;
|
|
@@ -88,7 +91,7 @@ function CompositePanel() {
|
|
|
88
91
|
const isMultiItem = lineItems.items.length > 1;
|
|
89
92
|
const activeSx = {
|
|
90
93
|
bgcolor: "primary.main",
|
|
91
|
-
color:
|
|
94
|
+
color: theme => (0, _format.primaryContrastColor)(theme),
|
|
92
95
|
boxShadow: "0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)"
|
|
93
96
|
};
|
|
94
97
|
const inactiveSx = {
|
|
@@ -277,15 +280,21 @@ function CompositePanel() {
|
|
|
277
280
|
},
|
|
278
281
|
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
279
282
|
onClick: async () => {
|
|
280
|
-
if (isUpselled) {
|
|
283
|
+
if (isUpselled && !upsellSwitching) {
|
|
284
|
+
setPendingUpsell(false);
|
|
285
|
+
setUpsellSwitching(true);
|
|
281
286
|
try {
|
|
282
287
|
await lineItems.downsell(upsellPrimaryItem.upsell_price?.id || upsellPrimaryItem.price_id);
|
|
283
288
|
} catch (err) {
|
|
289
|
+
setPendingUpsell(null);
|
|
284
290
|
_Toast.default.error(err?.response?.data?.error || err?.message || "Failed");
|
|
291
|
+
} finally {
|
|
292
|
+
setUpsellSwitching(false);
|
|
293
|
+
setPendingUpsell(null);
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
296
|
},
|
|
288
|
-
sx: capsuleBtnSx(!
|
|
297
|
+
sx: capsuleBtnSx(!visualIsUpselled),
|
|
289
298
|
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
290
299
|
component: "span",
|
|
291
300
|
sx: {
|
|
@@ -298,15 +307,21 @@ function CompositePanel() {
|
|
|
298
307
|
})
|
|
299
308
|
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
300
309
|
onClick: async () => {
|
|
301
|
-
if (!isUpselled) {
|
|
310
|
+
if (!isUpselled && !upsellSwitching) {
|
|
311
|
+
setPendingUpsell(true);
|
|
312
|
+
setUpsellSwitching(true);
|
|
302
313
|
try {
|
|
303
314
|
await lineItems.upsell(upsellPrimaryItem.price_id, upsellTarget.id);
|
|
304
315
|
} catch (err) {
|
|
316
|
+
setPendingUpsell(null);
|
|
305
317
|
_Toast.default.error(err?.response?.data?.error || err?.message || "Failed");
|
|
318
|
+
} finally {
|
|
319
|
+
setUpsellSwitching(false);
|
|
320
|
+
setPendingUpsell(null);
|
|
306
321
|
}
|
|
307
322
|
}
|
|
308
323
|
},
|
|
309
|
-
sx: capsuleBtnSx(
|
|
324
|
+
sx: capsuleBtnSx(visualIsUpselled),
|
|
310
325
|
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
311
326
|
component: "span",
|
|
312
327
|
sx: {
|
|
@@ -561,7 +561,8 @@ function PaymentPanel() {
|
|
|
561
561
|
remove: promotion.remove
|
|
562
562
|
},
|
|
563
563
|
discounts,
|
|
564
|
-
discountAmount: pricing.discount
|
|
564
|
+
discountAmount: pricing.discount,
|
|
565
|
+
isAmountLoading
|
|
565
566
|
})]
|
|
566
567
|
}), (() => {
|
|
567
568
|
const totalStr = pricing.total || "0";
|
|
@@ -710,7 +711,7 @@ function PaymentPanel() {
|
|
|
710
711
|
})]
|
|
711
712
|
})]
|
|
712
713
|
});
|
|
713
|
-
})(), /* @__PURE__ */(0, _jsxRuntime.
|
|
714
|
+
})(), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Button, {
|
|
714
715
|
variant: "contained",
|
|
715
716
|
size: "large",
|
|
716
717
|
fullWidth: true,
|
|
@@ -720,15 +721,51 @@ function PaymentPanel() {
|
|
|
720
721
|
size: 20,
|
|
721
722
|
color: "inherit"
|
|
722
723
|
}) : null,
|
|
723
|
-
endIcon: !isProcessing ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_ArrowForward.default, {}) : void 0,
|
|
724
724
|
sx: {
|
|
725
725
|
py: 1.5,
|
|
726
726
|
fontSize: "1.1rem",
|
|
727
727
|
fontWeight: 600,
|
|
728
728
|
textTransform: "none",
|
|
729
|
-
borderRadius: "12px"
|
|
729
|
+
borderRadius: "12px",
|
|
730
|
+
color: theme => (0, _format.primaryContrastColor)(theme),
|
|
731
|
+
position: "relative",
|
|
732
|
+
overflow: "hidden",
|
|
733
|
+
"&:hover": {
|
|
734
|
+
bgcolor: "primary.main"
|
|
735
|
+
},
|
|
736
|
+
"&:hover .arrow-icon": {
|
|
737
|
+
transform: "translateX(4px)"
|
|
738
|
+
},
|
|
739
|
+
"&:hover .shine-layer": {
|
|
740
|
+
transform: "translateX(100%)"
|
|
741
|
+
}
|
|
730
742
|
},
|
|
731
|
-
children:
|
|
743
|
+
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
744
|
+
component: "span",
|
|
745
|
+
sx: {
|
|
746
|
+
position: "relative",
|
|
747
|
+
zIndex: 1
|
|
748
|
+
},
|
|
749
|
+
children: isProcessing ? `${t("payment.checkout.processing")}...` : buttonLabel
|
|
750
|
+
}), !isProcessing && /* @__PURE__ */(0, _jsxRuntime.jsx)(_ArrowForward.default, {
|
|
751
|
+
className: "arrow-icon",
|
|
752
|
+
sx: {
|
|
753
|
+
ml: 1,
|
|
754
|
+
position: "relative",
|
|
755
|
+
zIndex: 1,
|
|
756
|
+
transition: "transform 0.2s ease"
|
|
757
|
+
}
|
|
758
|
+
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
759
|
+
className: "shine-layer",
|
|
760
|
+
sx: {
|
|
761
|
+
position: "absolute",
|
|
762
|
+
inset: 0,
|
|
763
|
+
background: "linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)",
|
|
764
|
+
transform: "translateX(-100%)",
|
|
765
|
+
transition: "transform 0.7s ease",
|
|
766
|
+
pointerEvents: "none"
|
|
767
|
+
}
|
|
768
|
+
})]
|
|
732
769
|
}), isMobile && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
733
770
|
direction: "row",
|
|
734
771
|
alignItems: "center",
|
|
@@ -828,7 +865,7 @@ function PaymentPanel() {
|
|
|
828
865
|
mode,
|
|
829
866
|
subscription,
|
|
830
867
|
staking: pricing.staking,
|
|
831
|
-
appName: session?.
|
|
868
|
+
appName: (0, _util.getStatementDescriptor)(session?.line_items || [])
|
|
832
869
|
}), !isMobile && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
833
870
|
direction: "row",
|
|
834
871
|
alignItems: "center",
|
|
@@ -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 {};
|
|
@@ -10,9 +10,16 @@ exports.formatTokenAmount = formatTokenAmount;
|
|
|
10
10
|
exports.formatTrialText = formatTrialText;
|
|
11
11
|
exports.getSessionHeaderMeta = getSessionHeaderMeta;
|
|
12
12
|
exports.getUnitAmountForCurrency = getUnitAmountForCurrency;
|
|
13
|
+
Object.defineProperty(exports, "primaryContrastColor", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () {
|
|
16
|
+
return _util2.primaryContrastColor;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
13
19
|
exports.tSafe = tSafe;
|
|
14
20
|
exports.whiteTooltipSx = void 0;
|
|
15
21
|
var _util = require("@ocap/util");
|
|
22
|
+
var _util2 = require("../../libs/util");
|
|
16
23
|
const INTERVAL_LOCALE_KEY = exports.INTERVAL_LOCALE_KEY = {
|
|
17
24
|
day: "common.daily",
|
|
18
25
|
week: "common.weekly",
|
|
@@ -10,6 +10,7 @@ var _styles = require("@mui/material/styles");
|
|
|
10
10
|
var _ArrowBack = _interopRequireDefault(require("@mui/icons-material/ArrowBack"));
|
|
11
11
|
var _Header = _interopRequireDefault(require("@blocklet/ui-react/lib/Header"));
|
|
12
12
|
var _context = require("@arcblock/ux/lib/Locale/context");
|
|
13
|
+
var _format = require("../utils/format");
|
|
13
14
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
15
|
function GeometricDecoration() {
|
|
15
16
|
const theme = (0, _styles.useTheme)();
|
|
@@ -223,6 +224,7 @@ function ErrorContent({
|
|
|
223
224
|
fontWeight: 600,
|
|
224
225
|
fontSize: 16,
|
|
225
226
|
letterSpacing: "0.02em",
|
|
227
|
+
color: th => (0, _format.primaryContrastColor)(th),
|
|
226
228
|
boxShadow: `0 8px 32px -4px ${(0, _styles.alpha)(primaryColor, 0.3)}`,
|
|
227
229
|
"&:hover": {
|
|
228
230
|
boxShadow: `0 12px 40px -4px ${(0, _styles.alpha)(primaryColor, 0.4)}`,
|
|
@@ -537,6 +537,7 @@ function SubscriptionLinks({
|
|
|
537
537
|
md: 17
|
|
538
538
|
},
|
|
539
539
|
letterSpacing: "0.02em",
|
|
540
|
+
color: theme => (0, _format.primaryContrastColor)(theme),
|
|
540
541
|
boxShadow: "0 8px 24px -4px rgba(59,130,246,0.25)",
|
|
541
542
|
"&:hover": {
|
|
542
543
|
boxShadow: "0 12px 28px -4px rgba(59,130,246,0.35)"
|
|
@@ -577,6 +578,7 @@ function InvoiceLink({
|
|
|
577
578
|
md: 17
|
|
578
579
|
},
|
|
579
580
|
letterSpacing: "0.02em",
|
|
581
|
+
color: theme => (0, _format.primaryContrastColor)(theme),
|
|
580
582
|
boxShadow: "0 8px 24px -4px rgba(59,130,246,0.25)",
|
|
581
583
|
"&:hover": {
|
|
582
584
|
boxShadow: "0 12px 28px -4px rgba(59,130,246,0.35)"
|
|
@@ -335,11 +335,15 @@ function OverdueInvoicePayment({
|
|
|
335
335
|
} = item;
|
|
336
336
|
const inProcess = payLoading && selectCurrencyId === currency.id;
|
|
337
337
|
const status = paymentStatus[currency.id] || "idle";
|
|
338
|
+
const containedColorSx = (options?.variant || "contained") === "contained" ? {
|
|
339
|
+
color: th => (0, _util.primaryContrastColor)(th)
|
|
340
|
+
} : {};
|
|
338
341
|
if (status === "success") {
|
|
339
342
|
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
|
|
340
343
|
variant: options?.variant || "contained",
|
|
341
344
|
size: "small",
|
|
342
345
|
onClick: () => checkAndHandleInvoicePaid(currency.id),
|
|
346
|
+
sx: containedColorSx,
|
|
343
347
|
...(primaryButton ? {} : {
|
|
344
348
|
color: "success",
|
|
345
349
|
startIcon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.CheckCircle, {})
|
|
@@ -378,7 +382,10 @@ function OverdueInvoicePayment({
|
|
|
378
382
|
disabled: paying || status === "processing",
|
|
379
383
|
loading: paying || status === "processing",
|
|
380
384
|
onClick: onPay,
|
|
381
|
-
sx:
|
|
385
|
+
sx: {
|
|
386
|
+
...containedColorSx,
|
|
387
|
+
...(options?.sx || {})
|
|
388
|
+
},
|
|
382
389
|
children: buttonText
|
|
383
390
|
})
|
|
384
391
|
});
|
|
@@ -389,7 +396,10 @@ function OverdueInvoicePayment({
|
|
|
389
396
|
disabled: inProcess,
|
|
390
397
|
loading: inProcess,
|
|
391
398
|
onClick: () => handlePay(item),
|
|
392
|
-
sx:
|
|
399
|
+
sx: {
|
|
400
|
+
...containedColorSx,
|
|
401
|
+
...(options?.sx || {})
|
|
402
|
+
},
|
|
393
403
|
children: status === "error" ? t("payment.subscription.overdue.retry") : t("payment.subscription.overdue.payNow")
|
|
394
404
|
});
|
|
395
405
|
};
|
package/lib/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/lib/libs/util.js
CHANGED
|
@@ -75,6 +75,7 @@ exports.lazyLoad = lazyLoad;
|
|
|
75
75
|
exports.mergeExtraParams = void 0;
|
|
76
76
|
exports.openDonationSettings = openDonationSettings;
|
|
77
77
|
exports.parseMarkedText = parseMarkedText;
|
|
78
|
+
exports.primaryContrastColor = primaryContrastColor;
|
|
78
79
|
exports.showStaking = showStaking;
|
|
79
80
|
exports.sleep = sleep;
|
|
80
81
|
exports.stopEvent = stopEvent;
|
|
@@ -1644,4 +1645,7 @@ function formatLinkWithLocale(url, locale) {
|
|
|
1644
1645
|
const separator = url.includes("?") ? "&" : "?";
|
|
1645
1646
|
return `${url}${separator}locale=${locale}`;
|
|
1646
1647
|
}
|
|
1648
|
+
}
|
|
1649
|
+
function primaryContrastColor(theme) {
|
|
1650
|
+
return theme.palette.getContrastText(theme.palette.primary.main);
|
|
1647
1651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.2",
|
|
4
4
|
"description": "Reusable react components for payment kit v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@arcblock/react-hooks": "^3.5.1",
|
|
60
60
|
"@arcblock/ux": "^3.5.1",
|
|
61
61
|
"@arcblock/ws": "^1.28.5",
|
|
62
|
-
"@blocklet/payment-react-headless": "1.26.
|
|
62
|
+
"@blocklet/payment-react-headless": "1.26.2",
|
|
63
63
|
"@blocklet/theme": "^3.5.1",
|
|
64
64
|
"@blocklet/ui-react": "^3.5.1",
|
|
65
65
|
"@mui/icons-material": "^7.1.2",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"@babel/core": "^7.27.4",
|
|
98
98
|
"@babel/preset-env": "^7.27.2",
|
|
99
99
|
"@babel/preset-react": "^7.27.1",
|
|
100
|
-
"@blocklet/payment-types": "1.26.
|
|
100
|
+
"@blocklet/payment-types": "1.26.2",
|
|
101
101
|
"@storybook/addon-essentials": "^7.6.20",
|
|
102
102
|
"@storybook/addon-interactions": "^7.6.20",
|
|
103
103
|
"@storybook/addon-links": "^7.6.20",
|
|
@@ -128,5 +128,5 @@
|
|
|
128
128
|
"vite-plugin-babel": "^1.3.1",
|
|
129
129
|
"vite-plugin-node-polyfills": "^0.23.0"
|
|
130
130
|
},
|
|
131
|
-
"gitHead": "
|
|
131
|
+
"gitHead": "71242a68d27d56666487176425153dc08071960f"
|
|
132
132
|
}
|
|
@@ -3,7 +3,7 @@ import ShoppingCartCheckoutIcon from '@mui/icons-material/ShoppingCartCheckout';
|
|
|
3
3
|
import { Avatar, Box, Button, Chip, Stack, Typography } from '@mui/material';
|
|
4
4
|
import type { TPaymentCurrency, TPrice } from '@blocklet/payment-types';
|
|
5
5
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
6
|
-
import { formatDynamicUnitPrice, tSafe, INTERVAL_LOCALE_KEY } from '../../utils/format';
|
|
6
|
+
import { formatDynamicUnitPrice, tSafe, INTERVAL_LOCALE_KEY, primaryContrastColor } from '../../utils/format';
|
|
7
7
|
|
|
8
8
|
interface CrossSellCardProps {
|
|
9
9
|
crossSellItem: TPrice;
|
|
@@ -53,7 +53,7 @@ export default function CrossSellCard({
|
|
|
53
53
|
fontWeight: 900,
|
|
54
54
|
letterSpacing: '0.12em',
|
|
55
55
|
bgcolor: 'primary.main',
|
|
56
|
-
color:
|
|
56
|
+
color: (theme: any) => primaryContrastColor(theme),
|
|
57
57
|
boxShadow: '0 4px 12px rgba(45,124,243,0.2)',
|
|
58
58
|
'& .MuiChip-label': { px: 1.5 },
|
|
59
59
|
}}
|
|
@@ -150,7 +150,7 @@ export default function CrossSellCard({
|
|
|
150
150
|
transition: 'all 0.2s',
|
|
151
151
|
'&:hover': {
|
|
152
152
|
bgcolor: 'primary.main',
|
|
153
|
-
color:
|
|
153
|
+
color: (theme: any) => primaryContrastColor(theme),
|
|
154
154
|
borderColor: 'primary.main',
|
|
155
155
|
},
|
|
156
156
|
'&:active': { transform: 'scale(0.95)' },
|
|
@@ -22,7 +22,13 @@ import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
|
|
22
22
|
import type { TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
|
|
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';
|
|
26
32
|
|
|
27
33
|
interface ProductItemCardProps {
|
|
28
34
|
item: TLineItemExpanded & { adjustable_quantity?: { enabled: boolean; minimum?: number; maximum?: number } };
|
|
@@ -292,7 +298,7 @@ export default function ProductItemCard({
|
|
|
292
298
|
fontWeight: 900,
|
|
293
299
|
letterSpacing: '0.12em',
|
|
294
300
|
bgcolor: 'primary.main',
|
|
295
|
-
color:
|
|
301
|
+
color: (th: any) => primaryContrastColor(th),
|
|
296
302
|
boxShadow: '0 4px 12px rgba(45,124,243,0.2)',
|
|
297
303
|
'& .MuiChip-label': { px: 1.5 },
|
|
298
304
|
}}
|
|
@@ -467,12 +473,16 @@ export default function ProductItemCard({
|
|
|
467
473
|
{/* Discount chip */}
|
|
468
474
|
{discountCode && perItemDiscount && (
|
|
469
475
|
<Box sx={{ mt: 1.5 }}>
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
+
{isRateLoading ? (
|
|
477
|
+
<Skeleton variant="rounded" width={160} height={22} sx={{ borderRadius: '6px' }} />
|
|
478
|
+
) : (
|
|
479
|
+
<Chip
|
|
480
|
+
icon={<LocalOfferIcon sx={{ color: 'warning.main', fontSize: 'small' }} />}
|
|
481
|
+
label={`${discountCode} (-${perItemDiscount})`}
|
|
482
|
+
size="small"
|
|
483
|
+
sx={{ height: 22, borderRadius: '6px', '& .MuiChip-label': { fontSize: 12 } }}
|
|
484
|
+
/>
|
|
485
|
+
)}
|
|
476
486
|
</Box>
|
|
477
487
|
)}
|
|
478
488
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CircularProgress,
|
|
10
10
|
IconButton,
|
|
11
11
|
InputAdornment,
|
|
12
|
+
Skeleton,
|
|
12
13
|
Stack,
|
|
13
14
|
TextField,
|
|
14
15
|
Typography,
|
|
@@ -28,6 +29,8 @@ interface PromotionInputProps {
|
|
|
28
29
|
discountAmount: string | null;
|
|
29
30
|
/** Start with input field visible (skip the "Add promotion code" button) */
|
|
30
31
|
initialShowInput?: boolean;
|
|
32
|
+
/** Show skeleton for the discount amount while switching */
|
|
33
|
+
isAmountLoading?: boolean;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export default function PromotionInput({
|
|
@@ -35,6 +38,7 @@ export default function PromotionInput({
|
|
|
35
38
|
discounts,
|
|
36
39
|
discountAmount,
|
|
37
40
|
initialShowInput = false,
|
|
41
|
+
isAmountLoading = false,
|
|
38
42
|
}: PromotionInputProps) {
|
|
39
43
|
const { t } = useLocaleContext();
|
|
40
44
|
const [showInput, setShowInput] = useState(false);
|
|
@@ -115,9 +119,13 @@ export default function PromotionInput({
|
|
|
115
119
|
<CloseIcon sx={{ fontSize: 12, color: '#12b886' }} />
|
|
116
120
|
</IconButton>
|
|
117
121
|
</Stack>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
{isAmountLoading ? (
|
|
123
|
+
<Skeleton variant="text" width={80} height={22} />
|
|
124
|
+
) : (
|
|
125
|
+
<Typography sx={{ color: 'text.primary', fontWeight: 600, fontSize: 14 }}>
|
|
126
|
+
-{discountAmount || '0'}
|
|
127
|
+
</Typography>
|
|
128
|
+
)}
|
|
121
129
|
</Stack>
|
|
122
130
|
);
|
|
123
131
|
})}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button, CircularProgress } from '@mui/material';
|
|
2
|
+
import { primaryContrastColor } from '../../utils/format';
|
|
2
3
|
|
|
3
4
|
interface SubmitButtonProps {
|
|
4
5
|
canSubmit: boolean;
|
|
@@ -30,6 +31,7 @@ export default function SubmitButton({
|
|
|
30
31
|
fontSize: '1.3rem',
|
|
31
32
|
fontWeight: 600,
|
|
32
33
|
textTransform: 'none',
|
|
34
|
+
color: (theme) => primaryContrastColor(theme),
|
|
33
35
|
}}>
|
|
34
36
|
{isProcessing ? processingLabel : label}
|
|
35
37
|
</Button>
|
|
@@ -17,7 +17,13 @@ import {
|
|
|
17
17
|
} from '@blocklet/payment-react-headless';
|
|
18
18
|
|
|
19
19
|
import { useMobile } from '../../../hooks/mobile';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
INTERVAL_LOCALE_KEY,
|
|
22
|
+
formatTrialText,
|
|
23
|
+
getSessionHeaderMeta,
|
|
24
|
+
tSafe,
|
|
25
|
+
primaryContrastColor,
|
|
26
|
+
} from '../../utils/format';
|
|
21
27
|
import ProductItemCard from '../../components/left/product-item-card';
|
|
22
28
|
import BillingToggle from '../../components/left/billing-toggle';
|
|
23
29
|
import CrossSellCard from '../../components/left/cross-sell-card';
|
|
@@ -60,6 +66,10 @@ export default function CompositePanel() {
|
|
|
60
66
|
const canUpsell = nonCrossSellItems.length <= 1;
|
|
61
67
|
const hasTopUpsell = canUpsell && !!upsellPrimaryItem && ['subscription', 'setup'].includes(mode);
|
|
62
68
|
const isUpselled = !!(upsellPrimaryItem as any)?.upsell_price;
|
|
69
|
+
const [upsellSwitching, setUpsellSwitching] = useState(false);
|
|
70
|
+
// Optimistic: track which tab the user clicked so highlight switches immediately
|
|
71
|
+
const [pendingUpsell, setPendingUpsell] = useState<boolean | null>(null);
|
|
72
|
+
const visualIsUpselled = pendingUpsell !== null ? pendingUpsell : isUpselled;
|
|
63
73
|
|
|
64
74
|
// Intervals for capsule toggle
|
|
65
75
|
const currentInterval = hasTopUpsell ? (upsellPrimaryItem!.price as any)?.recurring?.interval : null;
|
|
@@ -94,7 +104,7 @@ export default function CompositePanel() {
|
|
|
94
104
|
// Capsule button sx helper
|
|
95
105
|
const activeSx = {
|
|
96
106
|
bgcolor: 'primary.main',
|
|
97
|
-
color:
|
|
107
|
+
color: (theme: any) => primaryContrastColor(theme),
|
|
98
108
|
boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',
|
|
99
109
|
};
|
|
100
110
|
const inactiveSx = {
|
|
@@ -260,17 +270,23 @@ export default function CompositePanel() {
|
|
|
260
270
|
{/* Current interval */}
|
|
261
271
|
<Box
|
|
262
272
|
onClick={async () => {
|
|
263
|
-
if (isUpselled) {
|
|
273
|
+
if (isUpselled && !upsellSwitching) {
|
|
274
|
+
setPendingUpsell(false);
|
|
275
|
+
setUpsellSwitching(true);
|
|
264
276
|
try {
|
|
265
277
|
await lineItems.downsell(
|
|
266
278
|
(upsellPrimaryItem as any).upsell_price?.id || upsellPrimaryItem!.price_id
|
|
267
279
|
);
|
|
268
280
|
} catch (err: any) {
|
|
281
|
+
setPendingUpsell(null);
|
|
269
282
|
Toast.error(err?.response?.data?.error || err?.message || 'Failed');
|
|
283
|
+
} finally {
|
|
284
|
+
setUpsellSwitching(false);
|
|
285
|
+
setPendingUpsell(null);
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
288
|
}}
|
|
273
|
-
sx={capsuleBtnSx(!
|
|
289
|
+
sx={capsuleBtnSx(!visualIsUpselled)}>
|
|
274
290
|
<Typography component="span" sx={{ fontSize: 14, fontWeight: 700, color: 'inherit', lineHeight: 1 }}>
|
|
275
291
|
{t(INTERVAL_LOCALE_KEY[currentInterval!] || '')}
|
|
276
292
|
</Typography>
|
|
@@ -278,15 +294,21 @@ export default function CompositePanel() {
|
|
|
278
294
|
{/* Upsell interval */}
|
|
279
295
|
<Box
|
|
280
296
|
onClick={async () => {
|
|
281
|
-
if (!isUpselled) {
|
|
297
|
+
if (!isUpselled && !upsellSwitching) {
|
|
298
|
+
setPendingUpsell(true);
|
|
299
|
+
setUpsellSwitching(true);
|
|
282
300
|
try {
|
|
283
301
|
await lineItems.upsell(upsellPrimaryItem!.price_id, upsellTarget.id);
|
|
284
302
|
} catch (err: any) {
|
|
303
|
+
setPendingUpsell(null);
|
|
285
304
|
Toast.error(err?.response?.data?.error || err?.message || 'Failed');
|
|
305
|
+
} finally {
|
|
306
|
+
setUpsellSwitching(false);
|
|
307
|
+
setPendingUpsell(null);
|
|
286
308
|
}
|
|
287
309
|
}
|
|
288
310
|
}}
|
|
289
|
-
sx={capsuleBtnSx(
|
|
311
|
+
sx={capsuleBtnSx(visualIsUpselled)}>
|
|
290
312
|
<Typography component="span" sx={{ fontSize: 14, fontWeight: 700, color: 'inherit', lineHeight: 1 }}>
|
|
291
313
|
{t(INTERVAL_LOCALE_KEY[upsellInterval!] || '')}
|
|
292
314
|
</Typography>
|
|
@@ -39,10 +39,10 @@ import {
|
|
|
39
39
|
import { joinURL } from 'ufo';
|
|
40
40
|
import { usePaymentContext } from '../../../contexts/payment';
|
|
41
41
|
import { useMobile } from '../../../hooks/mobile';
|
|
42
|
-
import { getPrefix } from '../../../libs/util';
|
|
42
|
+
import { getPrefix, getStatementDescriptor } from '../../../libs/util';
|
|
43
43
|
import OverdueInvoicePayment from '../../../components/over-due-invoice-payment';
|
|
44
44
|
|
|
45
|
-
import { tSafe, whiteTooltipSx } from '../../utils/format';
|
|
45
|
+
import { tSafe, whiteTooltipSx, primaryContrastColor } from '../../utils/format';
|
|
46
46
|
import CustomerInfoCard from '../../components/right/customer-info-card';
|
|
47
47
|
import SubscriptionDisclaimer from '../../components/right/subscription-disclaimer';
|
|
48
48
|
import StatusFeedback from '../../components/right/status-feedback';
|
|
@@ -459,6 +459,7 @@ export default function PaymentPanel() {
|
|
|
459
459
|
}}
|
|
460
460
|
discounts={discounts}
|
|
461
461
|
discountAmount={pricing.discount}
|
|
462
|
+
isAmountLoading={isAmountLoading}
|
|
462
463
|
/>
|
|
463
464
|
</>
|
|
464
465
|
)}
|
|
@@ -564,15 +565,39 @@ export default function PaymentPanel() {
|
|
|
564
565
|
disabled={!canSubmit || submit.status === 'waiting_stripe'}
|
|
565
566
|
onClick={handleAction}
|
|
566
567
|
startIcon={isProcessing ? <CircularProgress size={20} color="inherit" /> : null}
|
|
567
|
-
endIcon={!isProcessing ? <ArrowForwardIcon /> : undefined}
|
|
568
568
|
sx={{
|
|
569
569
|
py: 1.5,
|
|
570
570
|
fontSize: '1.1rem',
|
|
571
571
|
fontWeight: 600,
|
|
572
572
|
textTransform: 'none',
|
|
573
573
|
borderRadius: '12px',
|
|
574
|
+
color: (theme) => primaryContrastColor(theme),
|
|
575
|
+
position: 'relative',
|
|
576
|
+
overflow: 'hidden',
|
|
577
|
+
'&:hover': { bgcolor: 'primary.main' },
|
|
578
|
+
'&:hover .arrow-icon': { transform: 'translateX(4px)' },
|
|
579
|
+
'&:hover .shine-layer': { transform: 'translateX(100%)' },
|
|
574
580
|
}}>
|
|
575
|
-
{
|
|
581
|
+
<Box component="span" sx={{ position: 'relative', zIndex: 1 }}>
|
|
582
|
+
{isProcessing ? `${t('payment.checkout.processing')}...` : buttonLabel}
|
|
583
|
+
</Box>
|
|
584
|
+
{!isProcessing && (
|
|
585
|
+
<ArrowForwardIcon
|
|
586
|
+
className="arrow-icon"
|
|
587
|
+
sx={{ ml: 1, position: 'relative', zIndex: 1, transition: 'transform 0.2s ease' }}
|
|
588
|
+
/>
|
|
589
|
+
)}
|
|
590
|
+
<Box
|
|
591
|
+
className="shine-layer"
|
|
592
|
+
sx={{
|
|
593
|
+
position: 'absolute',
|
|
594
|
+
inset: 0,
|
|
595
|
+
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)',
|
|
596
|
+
transform: 'translateX(-100%)',
|
|
597
|
+
transition: 'transform 0.7s ease',
|
|
598
|
+
pointerEvents: 'none',
|
|
599
|
+
}}
|
|
600
|
+
/>
|
|
576
601
|
</Button>
|
|
577
602
|
|
|
578
603
|
{/* Mobile: SSL footer inside fixed bar, below button */}
|
|
@@ -642,7 +667,7 @@ export default function PaymentPanel() {
|
|
|
642
667
|
mode={mode}
|
|
643
668
|
subscription={subscription}
|
|
644
669
|
staking={pricing.staking}
|
|
645
|
-
appName={(session?.
|
|
670
|
+
appName={getStatementDescriptor(session?.line_items || [])}
|
|
646
671
|
/>
|
|
647
672
|
|
|
648
673
|
{!isMobile && (
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { fromUnitToToken } from '@ocap/util';
|
|
2
2
|
import type { TPaymentCurrency } from '@blocklet/payment-types';
|
|
3
3
|
|
|
4
|
+
export { primaryContrastColor } from '../../libs/util';
|
|
5
|
+
|
|
4
6
|
// Interval key → locale key mapping
|
|
5
7
|
export const INTERVAL_LOCALE_KEY: Record<string, string> = {
|
|
6
8
|
day: 'common.daily',
|
|
@@ -3,6 +3,7 @@ import { alpha, useTheme } from '@mui/material/styles';
|
|
|
3
3
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
4
4
|
import Header from '@blocklet/ui-react/lib/Header';
|
|
5
5
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
6
|
+
import { primaryContrastColor } from '../utils/format';
|
|
6
7
|
|
|
7
8
|
interface ErrorViewProps {
|
|
8
9
|
error: string;
|
|
@@ -191,6 +192,7 @@ function ErrorContent({ error, errorCode = undefined }: { error: string; errorCo
|
|
|
191
192
|
fontWeight: 600,
|
|
192
193
|
fontSize: 16,
|
|
193
194
|
letterSpacing: '0.02em',
|
|
195
|
+
color: (th) => primaryContrastColor(th),
|
|
194
196
|
boxShadow: `0 8px 32px -4px ${alpha(primaryColor, 0.3)}`,
|
|
195
197
|
'&:hover': {
|
|
196
198
|
boxShadow: `0 12px 40px -4px ${alpha(primaryColor, 0.4)}`,
|
|
@@ -23,7 +23,7 @@ import type { TCheckoutSessionExpanded } from '@blocklet/payment-types';
|
|
|
23
23
|
import { usePaymentMethodContext } from '@blocklet/payment-react-headless';
|
|
24
24
|
|
|
25
25
|
import { getPrefix } from '../../libs/util';
|
|
26
|
-
import { formatTokenAmount } from '../utils/format';
|
|
26
|
+
import { formatTokenAmount, primaryContrastColor } from '../utils/format';
|
|
27
27
|
|
|
28
28
|
// ── Animations ──
|
|
29
29
|
|
|
@@ -573,6 +573,7 @@ function SubscriptionLinks({
|
|
|
573
573
|
fontWeight: 700,
|
|
574
574
|
fontSize: { xs: 16, md: 17 },
|
|
575
575
|
letterSpacing: '0.02em',
|
|
576
|
+
color: (theme) => primaryContrastColor(theme),
|
|
576
577
|
boxShadow: '0 8px 24px -4px rgba(59,130,246,0.25)',
|
|
577
578
|
'&:hover': {
|
|
578
579
|
boxShadow: '0 12px 28px -4px rgba(59,130,246,0.35)',
|
|
@@ -616,6 +617,7 @@ function InvoiceLink({
|
|
|
616
617
|
fontWeight: 700,
|
|
617
618
|
fontSize: { xs: 16, md: 17 },
|
|
618
619
|
letterSpacing: '0.02em',
|
|
620
|
+
color: (theme) => primaryContrastColor(theme),
|
|
619
621
|
boxShadow: '0 8px 24px -4px rgba(59,130,246,0.25)',
|
|
620
622
|
'&:hover': {
|
|
621
623
|
boxShadow: '0 12px 28px -4px rgba(59,130,246,0.35)',
|
|
@@ -19,7 +19,7 @@ import Dialog from '@arcblock/ux/lib/Dialog/dialog';
|
|
|
19
19
|
import { CheckCircle as CheckCircleIcon } from '@mui/icons-material';
|
|
20
20
|
import debounce from 'lodash/debounce';
|
|
21
21
|
import { usePaymentContext } from '../contexts/payment';
|
|
22
|
-
import { formatAmount, formatError, getPrefix, isCrossOrigin } from '../libs/util';
|
|
22
|
+
import { formatAmount, formatError, getPrefix, isCrossOrigin, primaryContrastColor } from '../libs/util';
|
|
23
23
|
import { useSubscription } from '../hooks/subscription';
|
|
24
24
|
import api from '../libs/api';
|
|
25
25
|
import LoadingButton from './loading-button';
|
|
@@ -397,6 +397,8 @@ function OverdueInvoicePayment({
|
|
|
397
397
|
const { currency } = item;
|
|
398
398
|
const inProcess = payLoading && selectCurrencyId === currency.id;
|
|
399
399
|
const status = paymentStatus[currency.id] || 'idle';
|
|
400
|
+
const containedColorSx =
|
|
401
|
+
(options?.variant || 'contained') === 'contained' ? { color: (th: any) => primaryContrastColor(th) } : {};
|
|
400
402
|
|
|
401
403
|
if (status === 'success') {
|
|
402
404
|
return (
|
|
@@ -404,6 +406,7 @@ function OverdueInvoicePayment({
|
|
|
404
406
|
variant={options?.variant || 'contained'}
|
|
405
407
|
size="small"
|
|
406
408
|
onClick={() => checkAndHandleInvoicePaid(currency.id)}
|
|
409
|
+
sx={containedColorSx}
|
|
407
410
|
{...(primaryButton
|
|
408
411
|
? {}
|
|
409
412
|
: {
|
|
@@ -442,7 +445,7 @@ function OverdueInvoicePayment({
|
|
|
442
445
|
disabled={paying || status === 'processing'}
|
|
443
446
|
loading={paying || status === 'processing'}
|
|
444
447
|
onClick={onPay}
|
|
445
|
-
sx={options?.sx}>
|
|
448
|
+
sx={{ ...containedColorSx, ...((options?.sx || {}) as any) }}>
|
|
446
449
|
{buttonText}
|
|
447
450
|
</LoadingButton>
|
|
448
451
|
)}
|
|
@@ -456,7 +459,7 @@ function OverdueInvoicePayment({
|
|
|
456
459
|
disabled={inProcess}
|
|
457
460
|
loading={inProcess}
|
|
458
461
|
onClick={() => handlePay(item)}
|
|
459
|
-
sx={options?.sx}>
|
|
462
|
+
sx={{ ...containedColorSx, ...((options?.sx || {}) as any) }}>
|
|
460
463
|
{status === 'error' ? t('payment.subscription.overdue.retry') : t('payment.subscription.overdue.payNow')}
|
|
461
464
|
</LoadingButton>
|
|
462
465
|
);
|
package/src/libs/util.ts
CHANGED
|
@@ -1823,3 +1823,10 @@ export function formatLinkWithLocale(url: string, locale?: string) {
|
|
|
1823
1823
|
return `${url}${separator}locale=${locale}`;
|
|
1824
1824
|
}
|
|
1825
1825
|
}
|
|
1826
|
+
|
|
1827
|
+
// Compute text color that contrasts with primary.main, works in both light and dark mode
|
|
1828
|
+
export function primaryContrastColor(theme: {
|
|
1829
|
+
palette: { primary: { main: string }; getContrastText: (bg: string) => string };
|
|
1830
|
+
}): string {
|
|
1831
|
+
return theme.palette.getContrastText(theme.palette.primary.main);
|
|
1832
|
+
}
|