UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

525 lines (524 loc) 17.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); module.exports = PricingTable; var _jsxRuntime = require("react/jsx-runtime"); var _context = require("@arcblock/ux/lib/Locale/context"); var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast")); var _iconsMaterial = require("@mui/icons-material"); var _material = require("@mui/material"); var _system = require("@mui/system"); var _ahooks = require("ahooks"); var _react = require("react"); var _util = require("@ocap/util"); var _isEmpty = _interopRequireDefault(require("lodash/isEmpty")); var _payment = require("../contexts/payment"); var _util2 = require("../libs/util"); var _mobile = require("../hooks/mobile"); var _truncatedText = _interopRequireDefault(require("./truncated-text")); var _loadingButton = _interopRequireDefault(require("./loading-button")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const sortOrder = { year: 1, month: 2, day: 3, hour: 4 }; const groupItemsByRecurring = (items, currency) => { const grouped = {}; const recurring = {}; (items || []).forEach(x => { const key = [x.price.recurring?.interval, x.price.recurring?.interval_count].join("-"); if (x.price.currency_options?.find(c => c.currency_id === currency.id)) { recurring[key] = x.price.recurring; } if (!grouped[key]) { grouped[key] = []; } grouped[key].push(x); }); return { recurring, grouped }; }; function PricingTable({ table, alignItems = "center", interval = "", mode = "checkout", onSelect, hideCurrency = false }) { const { t, locale } = (0, _context.useLocaleContext)(); const { isMobile } = (0, _mobile.useMobile)(); const { settings: { paymentMethods = [] }, livemode, setLivemode, refresh } = (0, _payment.usePaymentContext)(); const isMobileSafariEnv = (0, _util2.isMobileSafari)(); (0, _react.useEffect)(() => { if (table) { if (livemode !== table.livemode) { setLivemode(table.livemode); } } }, [table, livemode, setLivemode, refresh]); const [currency, setCurrency] = (0, _react.useState)(table.currency || {}); const { recurring, grouped } = (0, _react.useMemo)(() => groupItemsByRecurring(table.items, currency), [table.items, currency]); const recurringKeysList = (0, _react.useMemo)(() => { if ((0, _isEmpty.default)(recurring)) { return []; } return Object.keys(recurring).sort((a, b) => { const [aType, aValue] = a.split("-"); const [bType, bValue] = b.split("-"); if (sortOrder[aType] !== sortOrder[bType]) { return sortOrder[aType] - sortOrder[bType]; } if (aValue && bValue) { return bValue - aValue; } return b - a; }); }, [recurring]); const [state, setState] = (0, _ahooks.useSetState)({ interval }); const currencyMap = (0, _react.useMemo)(() => { if (!paymentMethods || paymentMethods.length === 0) { return {}; } const ans = {}; paymentMethods.forEach(paymentMethod => { const { payment_currencies: paymentCurrencies = [] } = paymentMethod; if (paymentCurrencies && paymentCurrencies.length > 0) { paymentCurrencies.forEach(x => { ans[x.id] = { ...x, method: paymentMethod.name }; }); } }); return ans; }, [paymentMethods]); const currencyList = (0, _react.useMemo)(() => { const visited = {}; if (!state.interval) { return []; } (grouped[state.interval] || []).forEach(x => { (0, _util2.getPriceCurrencyOptions)(x.price).forEach(c => { visited[c?.currency_id] = true; }); }); return Object.keys(visited).map(x => currencyMap[x]).filter(v => v); }, [currencyMap, grouped, state.interval]); const productList = (0, _react.useMemo)(() => { return (grouped[state.interval] || []).filter(x => { const price = (0, _util2.getPriceUintAmountByCurrency)(x.price, currency); if (new _util.BN(price).isZero() || !price) { return false; } return true; }); }, [grouped, state.interval, currency]); (0, _react.useEffect)(() => { if (table) { if (!state.interval || !grouped[state.interval]) { const keys = Object.keys(recurring); if (keys[0]) { setState({ interval: keys[0] }); } } } }, [table]); const Root = (0, _system.styled)(_material.Box)` .btn-row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; width: 100%; gap: 20px; } .price-table-wrap { scrollbar-width: none; -ms-overflow-style: none; &::-webkit-scrollbar { display: none; } } @media (max-width: ${({ theme }) => theme.breakpoints.values.sm}px) { // .price-table-item { // width: 90% !important; // } // .btn-row { // padding: 0 20px; // } } @media (min-width: ${({ theme }) => theme.breakpoints.values.md}px) { .price-table-wrap:has(> div:nth-of-type(1)) { max-width: 360px !important; } .price-table-wrap:has(> div:nth-of-type(2)) { max-width: 780px !important; } .price-table-wrap:has(> div:nth-of-type(3)) { max-width: 1200px !important; } } `; return /* @__PURE__ */(0, _jsxRuntime.jsx)(Root, { sx: { flex: 1, overflow: { xs: isMobileSafariEnv ? "visible" : "hidden", md: "hidden" }, display: "flex", flexDirection: "column" }, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "column", alignItems: alignItems === "center" ? "center" : "flex-start", sx: { gap: { xs: 3, sm: mode === "select" ? 3 : 5 }, height: "100%", overflow: { xs: "auto", md: "hidden" } }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { className: "btn-row", flexDirection: "row", children: [recurringKeysList.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { children: isMobile && recurringKeysList.length > 1 ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Select, { value: state.interval, onChange: e => setState({ interval: e.target.value }), size: "small", sx: { m: 1 }, children: recurringKeysList.map(x => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.MenuItem, { value: x, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { color: x === state.interval ? "text.primary" : "text.secondary", children: (0, _util2.formatRecurring)(recurring[x], true, "", locale) }) }, x)) }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButtonGroup, { size: "small", value: state.interval, sx: { padding: "4px", borderRadius: "36px", height: "40px", boxSizing: "border-box", backgroundColor: "grey.100", border: 0 }, onChange: (_, value) => { if (value !== null) { setState({ interval: value }); } }, exclusive: true, children: recurringKeysList.map(x => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, { size: "small", value: x, sx: { textTransform: "capitalize", padding: "5px 12px", fontSize: "13px", backgroundColor: ({ palette }) => x === state.interval ? `${palette.background.default} !important` : `${palette.grey[100]} !important`, border: "0px", "&.Mui-selected": { borderRadius: "9999px !important", border: "1px solid", borderColor: "divider" } }, children: (0, _util2.formatRecurring)(recurring[x], true, "", locale) }, x)) }) }), currencyList.length > 0 && !hideCurrency && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Select, { value: currency?.id, onChange: e => setCurrency(currencyList.find(v => v?.id === e.target.value)), size: "small", sx: { m: 1, minWidth: 180 }, children: currencyList.map(x => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.MenuItem, { value: x?.id, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "row", alignItems: "center", gap: 1, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, { src: x?.logo, sx: { width: 20, height: 20 }, alt: x?.symbol }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { fontSize: "12px", color: "text.secondary", children: [x?.symbol, "\uFF08", x?.method, "\uFF09"] })] }) }, x?.id)) })] }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, { flexWrap: "wrap", direction: "row", gap: "20px", justifyContent: alignItems === "center" ? "center" : "flex-start", sx: { flex: "0 1 auto", pb: 2.5 }, className: "price-table-wrap", children: productList?.map(x => { let action = x.subscription_data?.trial_period_days ? t("payment.checkout.try") : t("payment.checkout.subscription"); if (mode === "select") { action = x.is_selected ? t("payment.checkout.selected") : t("payment.checkout.select"); } const [amount, unit] = (0, _util2.formatPriceAmount)(x.price, currency, x.product.unit_label).split("/"); return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { padding: 4, spacing: 2, direction: "column", alignItems: "flex-start", className: "price-table-item", justifyContent: "flex-start", sx: { cursor: "pointer", borderWidth: "1px", borderStyle: "solid", borderColor: mode === "select" && x.is_selected ? "primary.main" : "divider", borderRadius: 2, transition: "border-color 0.3s ease 0s, box-shadow 0.3s ease 0s", boxShadow: 2, "&:hover": { borderColor: mode === "select" && x.is_selected ? "primary.main" : "divider", boxShadow: 2 }, width: { xs: "100%", md: "360px" }, maxWidth: "360px", minWidth: "300px", padding: "20px", position: "relative" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { textAlign: "center", children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { direction: "column", justifyContent: "center", alignItems: "flex-start", spacing: 1, sx: { gap: "12px" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, { sx: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { color: "text.secondary", fontWeight: 600, sx: { fontSize: "18px !important", fontWeight: "600" }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_truncatedText.default, { text: x.product.name, maxLength: 26, useWidth: true }) }), x.is_highlight && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, { label: x.highlight_text, color: "primary", size: "small", sx: { position: "absolute", top: "20px", right: "20px" } })] }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { component: "div", sx: { my: 0, fontWeight: "700", fontSize: "32px", letterSpacing: "-0.03rem", fontVariantNumeric: "tabular-nums", display: "flex", alignItems: "baseline", gap: "4px", flexWrap: "wrap", lineHeight: "normal" }, children: [amount, unit ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, { sx: { fontSize: "16px", fontWeight: "400", color: "text.secondary", textAlign: "left" }, children: ["/ ", unit] }) : ""] }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { color: "text.secondary", sx: { marginTop: "0px !important", fontWeight: "400", fontSize: "16px", textAlign: "left" }, children: x.product.description })] }) }), x.product.features.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { width: "100%" }, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.List, { dense: true, sx: { display: "flex", flexDirection: "column", gap: "16px", padding: "0px" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { sx: { width: "100%", position: "relative", borderTop: "1px solid", borderColor: "divider", boxSizing: "border-box", height: "1px" } }), x.product.features.map(f => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.ListItem, { disableGutters: true, disablePadding: true, sx: { fontSize: "16px !important" }, children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ListItemIcon, { sx: { minWidth: 25, color: "text.secondary", fontSize: "64px" }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.CheckOutlined, { color: "success", fontSize: "small", sx: { fontSize: "18px", color: "success.main" } }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ListItemText, { sx: { ".MuiListItemText-primary": { fontSize: "16px", color: "text.primary", fontWeight: "500" } }, primary: f.name })] }, f.name))] }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(Subscribe, { x, action, onSelect, currencyId: currency?.id })] }, x?.price_id); }) })] }) }); } function Subscribe({ x, action, onSelect, currencyId }) { const [state, setState] = (0, _react.useState)({ loading: "", loaded: false }); const handleSelect = async priceId => { try { setState({ loading: priceId, loaded: true }); await onSelect(priceId, currencyId); } catch (err) { console.error(err); _Toast.default.error((0, _util2.formatError)(err)); } }; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, { fullWidth: true, size: "medium", variant: "contained", color: "primary", sx: { fontSize: "16px", padding: "10px 20px", lineHeight: "28px" }, loading: state.loading === x.price_id && !state.loaded, disabled: x.is_disabled, onClick: () => handleSelect(x.price_id), children: action }); }