@blocklet/payment-react
Version:
Reusable react components for payment kit v2
525 lines (524 loc) • 17.7 kB
JavaScript
"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
});
}