UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

767 lines (766 loc) 28.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); module.exports = ProductItemCard; var _jsxRuntime = require("react/jsx-runtime"); var _react = require("react"); var _Add = _interopRequireDefault(require("@mui/icons-material/Add")); var _Check = _interopRequireDefault(require("@mui/icons-material/Check")); var _LocalOffer = _interopRequireDefault(require("@mui/icons-material/LocalOffer")); var _Remove = _interopRequireDefault(require("@mui/icons-material/Remove")); var _ShoppingCartCheckout = _interopRequireDefault(require("@mui/icons-material/ShoppingCartCheckout")); var _material = require("@mui/material"); var _KeyboardArrowUp = _interopRequireDefault(require("@mui/icons-material/KeyboardArrowUp")); var _paymentReactHeadless = require("@blocklet/payment-react-headless"); var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast")); var _format = require("../../utils/format"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function ProductItemCard({ item, currency, discounts, exchangeRate, onQuantityChange, onUpsell, onDownsell, trialActive, trialDays, t, recommended = false, hideUpsell = false, isRateLoading = false, showFeatures = true, children = void 0 }) { const activePrice = item.upsell_price || item.price; const product = activePrice?.product; const name = product?.name || "Item"; const logo = product?.images?.[0] || ""; const features = product?.features || []; const recurring = activePrice?.recurring; const quantity = item.quantity || 1; const isMetered = recurring?.usage_type === "metered"; const metered = isMetered ? ` ${t("common.metered")}` : ""; const perUnitFormatted = (0, _format.formatDynamicUnitPrice)(activePrice, currency, exchangeRate); const isSubscription = !!recurring; const typeBadgeText = isSubscription ? t("payment.checkout.typeBadge.subscription") : t("payment.checkout.typeBadge.oneTime"); const priceIntervalSuffix = recurring?.interval ? ` / ${t(`common.${recurring.interval}`)}` : ""; const subtitleText = (() => { if (quantity > 1 && perUnitFormatted && currency) { return `${quantity} \xD7 ${perUnitFormatted} ${currency.symbol}`; } if (isMetered) return metered.trim(); return ""; })(); const itemTotal = (() => { const quoteCurrencyId = item.quote_currency_id; if (item.custom_amount && quoteCurrencyId && quoteCurrencyId === currency?.id) { return (0, _format.formatTokenAmount)(item.custom_amount, currency); } if (activePrice?.pricing_type === "dynamic" && exchangeRate && activePrice.base_amount) { const rate = Number(exchangeRate); if (rate > 0 && Number.isFinite(rate)) { const baseUsd = Number(activePrice.base_amount); if (baseUsd > 0 && Number.isFinite(baseUsd)) { const tokenAmount = baseUsd * quantity / rate; const abs = Math.abs(tokenAmount); const precision = abs > 0 && abs < 0.01 ? 6 : 2; return tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0"; } } } if (!exchangeRate && activePrice?.base_amount != null) { const baseUsd = Number(activePrice.base_amount); if (baseUsd >= 0 && Number.isFinite(baseUsd)) { const total = baseUsd * quantity; return total.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } } if (activePrice && currency) { const unitAmount = (0, _paymentReactHeadless.getPriceUnitAmountByCurrency)(activePrice, currency); if (unitAmount && unitAmount !== "0") { const totalUnits = BigInt(unitAmount) * BigInt(quantity); return (0, _format.formatTokenAmount)(totalUnits.toString(), currency); } } return "0"; })(); const discount = discounts?.[0]; const discountCode = discount?.promotion_code_details?.code || discount?.verification_data?.code || discount?.promotion_code || ""; const perItemDiscount = (() => { if (!discountCode || !discount) return null; const couponDetails = discount?.coupon_details; if (couponDetails?.percent_off > 0) { const numericTotal = parseFloat(String(itemTotal).replace(/,/g, "")); if (!Number.isNaN(numericTotal) && numericTotal > 0) { const discAmount = numericTotal * couponDetails.percent_off / 100; const abs = Math.abs(discAmount); const precision = abs > 0 && abs < 0.01 ? 6 : 2; return `${discAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0"} ${currency?.symbol || ""}`; } } if (item.discount_amounts?.length > 0 && currency) { return `${(0, _format.formatTokenAmount)(item.discount_amounts[0].amount, currency)} ${currency.symbol}`; } return null; })(); const adjustable = item.adjustable_quantity?.enabled; const min = item.adjustable_quantity?.minimum || 1; const max = item.adjustable_quantity?.maximum; const [qtyInput, setQtyInput] = (0, _react.useState)(String(quantity)); const [isEditing, setIsEditing] = (0, _react.useState)(false); const theme = (0, _material.useTheme)(); const isMobile = (0, _material.useMediaQuery)(theme.breakpoints.down("md")); const [featuresOpen, setFeaturesOpen] = (0, _react.useState)(!isMobile); (0, _react.useEffect)(() => { if (!isEditing) setQtyInput(String(quantity)); }, [quantity, isEditing]); const debounceRef = (0, _react.useRef)(null); const debouncedQuantityChange = (0, _react.useCallback)((priceId, qty) => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { onQuantityChange(priceId, qty); }, 400); }, [onQuantityChange]); (0, _react.useEffect)(() => () => { if (debounceRef.current) clearTimeout(debounceRef.current); }, []); const canUpsell = !!item.price?.upsell?.upsells_to; const isUpselled = !!item.upsell_price; const upsellTo = item.price?.upsell?.upsells_to; let savingsPercent = 0; if (canUpsell && upsellTo) { const fromAmount = parseFloat(item.price?.base_amount || item.price?.unit_amount || "0"); const toAmount = parseFloat(upsellTo?.base_amount || upsellTo?.unit_amount || "0"); const fromInterval = item.price?.recurring?.interval; const toInterval = upsellTo?.recurring?.interval; if (fromAmount > 0 && toAmount > 0 && fromInterval && toInterval) { const monthsMap = { day: 365, week: 52, month: 12, year: 1 }; const fromYearly = fromAmount * (monthsMap[fromInterval] || 1); const toYearly = toAmount * (monthsMap[toInterval] || 1); if (fromYearly > toYearly) { savingsPercent = Math.round((fromYearly - toYearly) / fromYearly * 100); } } } const upsellInterval = upsellTo?.recurring?.interval; const upsellPrice = (() => { if (!upsellTo) return ""; const slashText = upsellInterval ? t("common.slash", { interval: t(`common.${upsellInterval}`) }) : ""; if (upsellTo.pricing_type === "dynamic" && upsellTo.base_amount && exchangeRate) { const rate = Number(exchangeRate); if (rate > 0 && Number.isFinite(rate)) { const baseUsd = parseFloat(upsellTo.base_amount); if (baseUsd > 0 && Number.isFinite(baseUsd)) { const tokenAmount = baseUsd / rate; const abs = Math.abs(tokenAmount); const precision = abs > 0 && abs < 0.01 ? 6 : 2; const formatted = tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0"; return `${formatted} ${currency?.symbol || ""} ${slashText}`; } } } if (upsellTo.pricing_type === "dynamic" && upsellTo.base_amount && upsellTo.base_currency === "USD") { const baseUsd = parseFloat(upsellTo.base_amount); const formattedUsd = baseUsd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return `$${formattedUsd} ${slashText}`; } const upsellUnitFormatted = (0, _format.formatDynamicUnitPrice)(upsellTo, currency, exchangeRate); return upsellUnitFormatted ? `${upsellUnitFormatted} ${currency?.symbol || ""} ${slashText}` : ""; })(); const originalInterval = item.price?.recurring?.interval; const downsellPrice = (() => { if (!item.price || !isUpselled) return ""; const originalPrice = item.price; const originalSlash = originalInterval ? t("common.slash", { interval: t(`common.${originalInterval}`) }) : ""; if (originalPrice.pricing_type === "dynamic" && originalPrice.base_amount && exchangeRate) { const rate = Number(exchangeRate); if (rate > 0 && Number.isFinite(rate)) { const baseUsd = parseFloat(originalPrice.base_amount); if (baseUsd > 0 && Number.isFinite(baseUsd)) { const tokenAmount = baseUsd / rate; const abs = Math.abs(tokenAmount); const precision = abs > 0 && abs < 0.01 ? 6 : 2; const formatted = tokenAmount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: precision }).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "") || "0"; return `${formatted} ${currency?.symbol || ""} ${originalSlash}`; } } } if (originalPrice.pricing_type === "dynamic" && originalPrice.base_amount && originalPrice.base_currency === "USD") { const baseUsd = parseFloat(originalPrice.base_amount); const formattedUsd = baseUsd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return `$${formattedUsd} ${originalSlash}`; } const unitFormatted = (0, _format.formatDynamicUnitPrice)(originalPrice, currency, exchangeRate); return unitFormatted ? `${unitFormatted} ${currency?.symbol || ""} ${originalSlash}` : ""; })(); const upsellToggleLabel = (() => { if (isUpselled) { const recurringLabel2 = originalInterval ? t(_format.INTERVAL_LOCALE_KEY[originalInterval] || "") : ""; return t("payment.checkout.upsell.revert", { recurring: recurringLabel2 }); } const recurringLabel = upsellInterval ? t(_format.INTERVAL_LOCALE_KEY[upsellInterval] || "") : ""; return t("payment.checkout.upsell.save", { recurring: recurringLabel }); })(); return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { position: "relative" }, children: [recommended && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, { label: "RECOMMENDED", size: "small", sx: { position: "absolute", top: 0, right: 40, transform: "translateY(-50%)", zIndex: 1, height: 22, fontSize: 9, fontWeight: 900, letterSpacing: "0.12em", bgcolor: "primary.main", color: th => (0, _format.primaryContrastColor)(th), boxShadow: "0 4px 12px rgba(45,124,243,0.2)", "& .MuiChip-label": { px: 1.5 } } }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { p: { xs: 2, md: 3 }, bgcolor: "background.paper", borderRadius: { xs: "16px", md: "24px" }, border: "1px solid", borderColor: "divider", boxShadow: th => th.palette.mode === "dark" ? "0 12px 40px -8px rgba(0,0,0,0.3)" : "0 12px 40px -8px rgba(0,0,0,0.06)", transition: "all 0.3s ease", ...(canUpsell && !hideUpsell ? { borderRadius: { xs: "16px 16px 0 0", md: "24px 24px 0 0" } } : {}), "&:hover": { borderColor: th => th.palette.mode === "dark" ? "rgba(255,255,255,0.12)" : "rgba(45,124,243,0.15)" } }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", spacing: { xs: 1.5, md: 2.5 }, sx: { alignItems: "center", width: "100%" }, children: [logo ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, { src: logo, alt: name, variant: "rounded", sx: { width: { xs: 44, md: 64 }, height: { xs: 44, md: 64 }, borderRadius: { xs: "12px", md: "16px" }, flexShrink: 0 } }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, { variant: "rounded", sx: { width: { xs: 44, md: 64 }, height: { xs: 44, md: 64 }, borderRadius: { xs: "12px", md: "16px" }, bgcolor: th => th.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "#eff6ff", flexShrink: 0 }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_ShoppingCartCheckout.default, { sx: { fontSize: { xs: 22, md: 28 }, color: "primary.main", opacity: 0.45 } }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { flex: 1, minWidth: 0, overflow: "hidden" }, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", justifyContent: "space-between", alignItems: "flex-start", sx: { mb: 0.25 }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { minWidth: 0 }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.primary", fontWeight: 800, fontSize: { xs: 15, md: 18 }, lineHeight: 1.3 }, children: name }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { component: "span", sx: { display: "inline-block", mt: 0.5, fontSize: 10, fontWeight: 700, letterSpacing: "0.08em", lineHeight: 1, textTransform: "uppercase", color: "primary.main", bgcolor: th => th.palette.mode === "dark" ? `${th.palette.primary.main}1A` : `${th.palette.primary.main}0D`, px: 0.75, py: 0.4, borderRadius: "3px" }, children: typeBadgeText }), subtitleText && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.disabled", fontSize: 13, fontWeight: 500, mt: 0.25 }, children: subtitleText })] }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, { alignItems: "flex-end", sx: { flexShrink: 0, ml: 1.5 }, children: trialActive && isSubscription ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { fontWeight: 800, color: "text.primary", whiteSpace: "nowrap", fontSize: { xs: 15, md: 18 } }, children: (0, _format.formatTrialText)(t, trialDays, recurring?.interval || "day") }) : isRateLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, { variant: "text", width: 100, height: 28 }) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { sx: { fontWeight: 800, color: "text.primary", whiteSpace: "nowrap", fontSize: { xs: 15, md: 18 }, transition: "opacity 0.3s ease" }, children: [itemTotal, " ", currency?.symbol, priceIntervalSuffix] }), exchangeRate && activePrice?.base_amount && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { sx: { fontSize: 12, color: "text.disabled", fontWeight: 600, lineHeight: 1 }, children: ["\u2248 $", (Number(activePrice.base_amount) * quantity).toFixed(2)] })] }) })] }) })] }), showFeatures && features.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { mt: { xs: 2, md: 2.5 }, pt: { xs: 2, md: 2.5 }, borderTop: "1px solid", borderColor: th => th.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "grey.100" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", onClick: () => setFeaturesOpen(v => !v), sx: { cursor: "pointer", userSelect: "none", mb: featuresOpen ? 1.25 : 0 }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { fontSize: 14, fontWeight: 700, color: "text.primary" }, children: t("payment.checkout.planFeatures") }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_KeyboardArrowUp.default, { sx: { fontSize: 20, color: "text.secondary", transition: "transform 0.2s ease", transform: featuresOpen ? "rotate(0deg)" : "rotate(180deg)" } })] }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Collapse, { in: featuresOpen, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, { spacing: 1.25, children: features.map(feature => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", spacing: 1.5, alignItems: "center", children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { width: 20, height: 20, borderRadius: "50%", bgcolor: th => th.palette.mode === "dark" ? "rgba(16,185,129,0.15)" : "rgba(16,185,129,0.1)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_Check.default, { sx: { fontSize: 14, color: "success.main" } }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { fontSize: 14, fontWeight: 500, color: "text.secondary" }, children: feature.name })] }, feature.name)) }) })] }), discountCode && perItemDiscount && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { mt: 1.5 }, children: isRateLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, { variant: "rounded", width: 160, height: 22, sx: { borderRadius: "6px" } }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, { icon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_LocalOffer.default, { sx: { color: "warning.main", fontSize: "small" } }), label: `${discountCode} (-${perItemDiscount})`, size: "small", sx: { height: 22, borderRadius: "6px", "& .MuiChip-label": { fontSize: 12 } } }) }), adjustable && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", spacing: 1, alignItems: "center", sx: { mt: 2 }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { color: "text.secondary", minWidth: "fit-content", fontSize: 13, fontWeight: 500 }, children: t("common.quantity") }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, { size: "small", onClick: () => { const cur = parseInt(qtyInput, 10) || quantity; const newQty = cur - 1; if (newQty < min) return; setQtyInput(String(newQty)); debouncedQuantityChange(item.price_id, newQty); }, disabled: (parseInt(qtyInput, 10) || quantity) <= min, sx: { width: 36, height: 36, border: "1px solid", borderColor: "divider", borderRadius: "50%", "&:hover": { bgcolor: "action.hover" } }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_Remove.default, { sx: { fontSize: 20 } }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, { value: qtyInput, size: "small", type: "number", slotProps: { htmlInput: { style: { textAlign: "center", padding: "6px 4px", fontWeight: 700, fontSize: 16, MozAppearance: "textfield" }, min, max: max || void 0 } }, sx: { minWidth: 64, "& input": { textAlign: "center" }, "& .MuiOutlinedInput-root": { borderRadius: "12px" }, "& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button": { WebkitAppearance: "none", margin: 0 }, "& input[type=number]": { MozAppearance: "textfield" } }, onFocus: () => setIsEditing(true), onChange: e => setQtyInput(e.target.value), onKeyDown: e => { if (e.key === "Enter") { e.preventDefault(); e.stopPropagation(); e.target.blur(); } }, onBlur: () => { setIsEditing(false); const v = parseInt(qtyInput, 10); if (!Number.isNaN(v) && v >= min && (!max || v <= max) && v !== quantity) { onQuantityChange(item.price_id, v); } else { setQtyInput(String(quantity)); } } }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, { size: "small", onClick: () => { const cur = parseInt(qtyInput, 10) || quantity; const newQty = cur + 1; if (max && newQty > max) return; setQtyInput(String(newQty)); debouncedQuantityChange(item.price_id, newQty); }, disabled: max ? (parseInt(qtyInput, 10) || quantity) >= max : false, sx: { width: 36, height: 36, border: "1px solid", borderColor: "divider", borderRadius: "50%", "&:hover": { bgcolor: "action.hover" } }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_Add.default, { sx: { fontSize: 20 } }) })] }), children] }), canUpsell && !hideUpsell && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { px: { xs: 2, md: 3 }, py: 1.5, bgcolor: "background.paper", border: "1px solid", borderTop: 0, borderColor: "divider", borderRadius: { xs: "0 0 16px 16px", md: "0 0 24px 24px" }, boxShadow: th => th.palette.mode === "dark" ? "0 12px 40px -8px rgba(0,0,0,0.3)" : "0 12px 40px -8px rgba(0,0,0,0.06)" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { borderTop: "1px solid", borderColor: "divider", mb: 1.5 } }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", alignItems: "center", spacing: 1, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Switch, { checked: isUpselled, onChange: async () => { try { if (isUpselled) { await onDownsell(item.upsell_price?.id || item.price.id); } else { await onUpsell(item.price_id, upsellTo.id); } } catch (err) { _Toast.default.error(err?.response?.data?.error || err?.message || "Failed"); } }, size: "small", sx: { "& .MuiSwitch-switchBase.Mui-checked": { color: "#12b886" }, "& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": { bgcolor: "#12b886" } } }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { fontSize: 13, color: "text.secondary", cursor: "pointer", fontWeight: 600 }, onClick: async () => { try { if (isUpselled) await onDownsell(item.upsell_price?.id || item.price.id);else await onUpsell(item.price_id, upsellTo.id); } catch (err) { _Toast.default.error(err?.response?.data?.error || err?.message || "Failed"); } }, children: upsellToggleLabel }), !isUpselled && savingsPercent > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, { label: t("payment.checkout.upsell.off", { saving: savingsPercent }), size: "small", sx: { height: 22, fontSize: 11, fontWeight: 700, bgcolor: th => th.palette.mode === "dark" ? "rgba(18,184,134,0.1)" : "#ebfef5", color: "#12b886", border: "1px solid", borderColor: th => th.palette.mode === "dark" ? "rgba(18,184,134,0.2)" : "#d3f9e8", borderRadius: "9999px", "& .MuiChip-label": { px: 1 } } })] }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { fontSize: 13, color: "text.primary", whiteSpace: "nowrap", fontWeight: 700 }, children: isUpselled ? downsellPrice : upsellPrice })] })] })] }); }