@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.
Files changed (43) hide show
  1. package/es/checkout-v2/components/left/cross-sell-card.js +3 -3
  2. package/es/checkout-v2/components/left/product-item-card.js +13 -7
  3. package/es/checkout-v2/components/left/promotion-input.d.ts +3 -1
  4. package/es/checkout-v2/components/left/promotion-input.js +4 -2
  5. package/es/checkout-v2/components/right/submit-button.js +3 -1
  6. package/es/checkout-v2/panels/left/composite-panel.js +27 -6
  7. package/es/checkout-v2/panels/left/credit-topup-panel.js +1 -5
  8. package/es/checkout-v2/panels/right/payment-panel.js +37 -8
  9. package/es/checkout-v2/utils/format.d.ts +1 -1
  10. package/es/checkout-v2/utils/format.js +3 -2
  11. package/es/checkout-v2/views/error-view.js +2 -0
  12. package/es/checkout-v2/views/success-view.js +3 -1
  13. package/es/components/over-due-invoice-payment.js +5 -3
  14. package/es/libs/util.d.ts +8 -0
  15. package/es/libs/util.js +3 -0
  16. package/lib/checkout-v2/components/left/cross-sell-card.js +2 -2
  17. package/lib/checkout-v2/components/left/product-item-card.js +13 -6
  18. package/lib/checkout-v2/components/left/promotion-input.d.ts +3 -1
  19. package/lib/checkout-v2/components/left/promotion-input.js +7 -2
  20. package/lib/checkout-v2/components/right/submit-button.js +3 -1
  21. package/lib/checkout-v2/panels/left/composite-panel.js +20 -5
  22. package/lib/checkout-v2/panels/left/credit-topup-panel.js +1 -5
  23. package/lib/checkout-v2/panels/right/payment-panel.js +43 -6
  24. package/lib/checkout-v2/utils/format.d.ts +1 -1
  25. package/lib/checkout-v2/utils/format.js +9 -2
  26. package/lib/checkout-v2/views/error-view.js +2 -0
  27. package/lib/checkout-v2/views/success-view.js +2 -0
  28. package/lib/components/over-due-invoice-payment.js +12 -2
  29. package/lib/libs/util.d.ts +8 -0
  30. package/lib/libs/util.js +4 -0
  31. package/package.json +4 -4
  32. package/src/checkout-v2/components/left/cross-sell-card.tsx +3 -3
  33. package/src/checkout-v2/components/left/product-item-card.tsx +30 -12
  34. package/src/checkout-v2/components/left/promotion-input.tsx +11 -3
  35. package/src/checkout-v2/components/right/submit-button.tsx +2 -0
  36. package/src/checkout-v2/panels/left/composite-panel.tsx +28 -6
  37. package/src/checkout-v2/panels/left/credit-topup-panel.tsx +1 -5
  38. package/src/checkout-v2/panels/right/payment-panel.tsx +30 -5
  39. package/src/checkout-v2/utils/format.ts +5 -2
  40. package/src/checkout-v2/views/error-view.tsx +2 -0
  41. package/src/checkout-v2/views/success-view.tsx +3 -1
  42. package/src/components/over-due-invoice-payment.tsx +6 -3
  43. 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: "#fff",
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: "#fff",
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 { INTERVAL_LOCALE_KEY, formatDynamicUnitPrice, formatTokenAmount, formatTrialText } from "../../utils/format.js";
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(/\.?0+$/, "") || "0";
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(/\.?0+$/, "") || "0"} ${currency?.symbol || ""}`;
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(/\.?0+$/, "") || "0";
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(/\.?0+$/, "") || "0";
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: "#fff",
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 { INTERVAL_LOCALE_KEY, formatTrialText, getSessionHeaderMeta, tSafe } from "../../utils/format.js";
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: "#fff",
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(!isUpselled),
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(isUpselled),
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, creditName, product, t]);
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__ */ jsx(
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: isProcessing ? `${t("payment.checkout.processing")}...` : buttonLabel
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?.metadata?.app_name || "New Payment Kit"
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(/\.?0+$/, "") || "0";
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(/\.?0+$/, "") || "0";
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
@@ -1372,3 +1372,6 @@ export function formatLinkWithLocale(url, locale) {
1372
1372
  return `${url}${separator}locale=${locale}`;
1373
1373
  }
1374
1374
  }
1375
+ export function primaryContrastColor(theme) {
1376
+ return theme.palette.getContrastText(theme.palette.primary.main);
1377
+ }
@@ -54,7 +54,7 @@ function CrossSellCard({
54
54
  fontWeight: 900,
55
55
  letterSpacing: "0.12em",
56
56
  bgcolor: "primary.main",
57
- color: "#fff",
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: "#fff",
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(/\.?0+$/, "") || "0";
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(/\.?0+$/, "") || "0"} ${currency?.symbol || ""}`;
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(/\.?0+$/, "") || "0";
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(/\.?0+$/, "") || "0";
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: "#fff",
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.Chip, {
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.jsxs)(_material.Typography, {
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
  });