UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

358 lines (357 loc) 14 kB
import { jsx, jsxs } from "react/jsx-runtime"; import { useLocaleContext } from "@arcblock/ux/lib/Locale/context"; import { Box, Stack, Typography, IconButton, TextField, Alert } from "@mui/material"; import { Add, Remove } from "@mui/icons-material"; import { useMemo, useState } from "react"; import Status from "../components/status.js"; import Switch from "../components/switch-button.js"; import { findCurrency, formatLineItemPricing, formatNumber, formatPrice, formatQuantityInventory, formatRecurring, formatUpsellSaving } from "../libs/util.js"; import ProductCard from "./product-card.js"; import dayjs from "../libs/dayjs.js"; import { usePaymentContext } from "../contexts/payment.js"; export default function ProductItem({ item, items, trialInDays, trialEnd = 0, currency, mode = "normal", children = null, onUpsell, onDownsell, completed = false, adjustableQuantity = { enabled: false }, onQuantityChange = () => { } }) { const { t, locale } = useLocaleContext(); const { settings } = usePaymentContext(); const pricing = formatLineItemPricing(item, currency, { trialEnd, trialInDays }, locale); const saving = formatUpsellSaving(items, currency); const metered = item.price?.recurring?.usage_type === "metered" ? t("common.metered") : ""; const canUpsell = mode === "normal" && items.length === 1; const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config?.credit_amount; const creditAmount = isCreditProduct ? Number(item.price.metadata.credit_config.credit_amount) : 0; const creditCurrency = isCreditProduct ? findCurrency(settings.paymentMethods, item.price.metadata?.credit_config?.currency_id ?? "") : null; const validDuration = item.price.metadata?.credit_config?.valid_duration_value; const validDurationUnit = item.price.metadata?.credit_config?.valid_duration_unit || "days"; const [localQuantity, setLocalQuantity] = useState(item.quantity); const canAdjustQuantity = adjustableQuantity.enabled && mode === "normal"; const minQuantity = Math.max(adjustableQuantity.minimum || 1, 1); const quantityAvailable = Math.min(item.price.quantity_limit_per_checkout, item.price.quantity_available); const maxQuantity = quantityAvailable ? Math.min(adjustableQuantity.maximum || Infinity, quantityAvailable) : adjustableQuantity.maximum || Infinity; const handleQuantityChange = (newQuantity) => { if (newQuantity >= minQuantity && newQuantity <= maxQuantity) { if (formatQuantityInventory(item.price, newQuantity, locale)) { return; } setLocalQuantity(newQuantity); onQuantityChange(item.price_id, newQuantity); } }; const handleQuantityIncrease = () => { if (localQuantity < maxQuantity) { handleQuantityChange(localQuantity + 1); } }; const handleQuantityDecrease = () => { if (localQuantity > minQuantity) { handleQuantityChange(localQuantity - 1); } }; const handleQuantityInputChange = (event) => { const value = parseInt(event.target.value, 10); if (!Number.isNaN(value)) { handleQuantityChange(value); } }; const formatCreditInfo = () => { if (!isCreditProduct) return null; const isRecurring = item.price.type === "recurring"; const totalCredit = formatNumber(creditAmount * localQuantity); let message = ""; if (isRecurring) { message = t("payment.checkout.credit.recurringInfo", { amount: totalCredit, period: formatRecurring(item.price.recurring, true, "per", locale) }); } else { message = t("payment.checkout.credit.oneTimeInfo", { amount: totalCredit, symbol: creditCurrency?.symbol || "Credits" }); } if (validDuration && validDuration > 0) { message += `\uFF0C${t("payment.checkout.credit.expiresIn", { duration: validDuration, unit: t(`common.${validDurationUnit}`) })}`; } return message; }; const primaryText = useMemo(() => { const price = item.upsell_price || item.price || {}; const isRecurring = price?.type === "recurring" && price?.recurring; const trial = trialInDays > 0 || trialEnd > dayjs().unix(); if (isRecurring && !trial && price?.recurring?.usage_type !== "metered") { return `${pricing.primary} ${price.recurring ? formatRecurring(price.recurring, false, "slash", locale) : ""}`; } return pricing.primary; }, [trialInDays, trialEnd, pricing, item, locale]); const quantityInventoryError = formatQuantityInventory(item.price, localQuantity, locale); return /* @__PURE__ */ jsxs( Stack, { direction: "column", spacing: 1, className: "product-item", sx: { alignItems: "flex-start", width: "100%" }, children: [ /* @__PURE__ */ jsxs( Stack, { direction: "column", className: "product-item-content", sx: { alignItems: "flex-start", width: "100%" }, children: [ /* @__PURE__ */ jsxs( Stack, { direction: "row", spacing: 0.5, sx: { alignItems: "center", flexWrap: "wrap", justifyContent: "space-between", width: "100%" }, children: [ /* @__PURE__ */ jsx( ProductCard, { logo: item.price.product?.images[0], name: item.price.product?.name, extra: /* @__PURE__ */ jsx( Box, { sx: { display: "flex", alignItems: "center" }, children: item.price.type === "recurring" && item.price.recurring ? [pricing.quantity, t("common.billed", { rule: `${formatRecurring(item.upsell_price?.recurring || item.price.recurring, true, "per", locale)} ${metered}` })].filter(Boolean).join(", ") : pricing.quantity } ) } ), /* @__PURE__ */ jsxs( Stack, { direction: "column", sx: { alignItems: "flex-end", flex: 1 }, children: [ /* @__PURE__ */ jsx(Typography, { sx: { color: "text.primary", fontWeight: 500, whiteSpace: "nowrap" }, gutterBottom: true, children: primaryText }), pricing.secondary && /* @__PURE__ */ jsx(Typography, { sx: { fontSize: "0.74375rem", color: "text.lighter" }, children: pricing.secondary }) ] } ) ] } ), quantityInventoryError ? /* @__PURE__ */ jsx( Status, { label: quantityInventoryError, variant: "outlined", sx: { mt: 1, borderColor: "chip.error.border", backgroundColor: "chip.error.background", color: "chip.error.text" } } ) : null, canAdjustQuantity && !completed && /* @__PURE__ */ jsx(Box, { sx: { mt: 1, p: 1 }, children: /* @__PURE__ */ jsxs( Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [ /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary", minWidth: "fit-content" }, children: [ t("common.quantity"), ":" ] } ), /* @__PURE__ */ jsx( IconButton, { size: "small", onClick: handleQuantityDecrease, disabled: localQuantity <= minQuantity, sx: { minWidth: 32, width: 32, height: 32 }, children: /* @__PURE__ */ jsx(Remove, { fontSize: "small" }) } ), /* @__PURE__ */ jsx( TextField, { size: "small", value: localQuantity, onChange: handleQuantityInputChange, type: "number", slotProps: { htmlInput: { min: minQuantity, max: maxQuantity, style: { textAlign: "center", padding: "4px", minWidth: 80 } } } } ), /* @__PURE__ */ jsx( IconButton, { size: "small", onClick: handleQuantityIncrease, disabled: localQuantity >= maxQuantity, sx: { minWidth: 32, width: 32, height: 32 }, children: /* @__PURE__ */ jsx(Add, { fontSize: "small" }) } ) ] } ) }), isCreditProduct && /* @__PURE__ */ jsx(Alert, { severity: "info", sx: { mt: 1, fontSize: "0.875rem" }, icon: false, children: formatCreditInfo() }), children ] } ), canUpsell && !item.upsell_price_id && item.price.upsell?.upsells_to && /* @__PURE__ */ jsxs( Stack, { direction: "row", className: "product-item-upsell", sx: { alignItems: "center", justifyContent: "space-between", width: "100%" }, children: [ /* @__PURE__ */ jsxs( Typography, { component: "label", htmlFor: "upsell-switch", sx: { fontSize: 12, cursor: "pointer", color: "text.secondary" }, children: [ /* @__PURE__ */ jsx( Switch, { id: "upsell-switch", sx: { mr: 1 }, variant: "success", checked: false, onChange: () => onUpsell(item.price_id, item.price.upsell?.upsells_to_id) } ), t("payment.checkout.upsell.save", { recurring: formatRecurring(item.price.upsell.upsells_to.recurring, true, "per", locale) }), /* @__PURE__ */ jsx( Status, { label: t("payment.checkout.upsell.off", { saving }), variant: "outlined", sx: { ml: 1, borderColor: "chip.warning.border", backgroundColor: "chip.warning.background", color: "chip.warning.text" } } ) ] } ), /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: 12 }, children: formatPrice(item.price.upsell.upsells_to, currency, item.price.product?.unit_label, 1, true, locale) }) ] } ), canUpsell && item.upsell_price_id && /* @__PURE__ */ jsxs( Stack, { direction: "row", className: "product-item-upsell", sx: { alignItems: "center", justifyContent: "space-between", width: "100%" }, children: [ /* @__PURE__ */ jsxs( Typography, { component: "label", htmlFor: "upsell-switch", sx: { fontSize: 12, cursor: "pointer", color: "text.secondary" }, children: [ /* @__PURE__ */ jsx( Switch, { id: "upsell-switch", sx: { mr: 1 }, variant: "success", checked: true, onChange: () => onDownsell(item.upsell_price_id) } ), t("payment.checkout.upsell.revert", { recurring: t(`common.${formatRecurring(item.price.recurring)}`) }) ] } ), /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: 12 }, children: formatPrice(item.price, currency, item.price.product?.unit_label, 1, true, locale) }) ] } ) ] } ); }