@blocklet/payment-react
Version:
Reusable react components for payment kit v2
804 lines (803 loc) • 25.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DonateDetails = DonateDetails;
module.exports = CheckoutDonate;
var _jsxRuntime = require("react/jsx-runtime");
var _Dialog = _interopRequireDefault(require("@arcblock/ux/lib/Dialog"));
var _context = require("@arcblock/ux/lib/Locale/context");
var _material = require("@mui/material");
var _ahooks = require("ahooks");
var _omit = _interopRequireDefault(require("lodash/omit"));
var _uniqBy = _interopRequireDefault(require("lodash/uniqBy"));
var _react = require("react");
var _iconsMaterial = require("@mui/icons-material");
var _api = _interopRequireDefault(require("../libs/api"));
var _util = require("../libs/util");
var _form = _interopRequireDefault(require("./form"));
var _theme = require("../theme");
var _payment = require("../contexts/payment");
var _livemode = _interopRequireDefault(require("../components/livemode"));
var _mobile = require("../hooks/mobile");
var _donate = require("../contexts/donate");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const donationCache = {};
const createOrUpdateDonation = (settings, livemode = true) => {
const donationKey = `${settings.target}-${livemode}`;
if (!donationCache[donationKey]) {
donationCache[donationKey] = _api.default.post(`/api/donations?livemode=${livemode}`, (0, _omit.default)(settings, ["appearance"])).then(res => res?.data).finally(() => {
setTimeout(() => {
delete donationCache[donationKey];
}, 3e3);
});
}
return donationCache[donationKey];
};
const supporterCache = {};
const fetchSupporters = (target, livemode = true) => {
if (!supporterCache[target]) {
supporterCache[target] = _api.default.get("/api/donations", {
params: {
target,
livemode
}
}).then(res => res?.data).finally(() => {
setTimeout(() => {
delete supporterCache[target];
}, 3e3);
});
}
return supporterCache[target];
};
const emojiFont = {
fontFamily: 'Avenir, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"'
};
function DonateDetails({
supporters = [],
currency,
method
}) {
const {
locale
} = (0, _context.useLocaleContext)();
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
className: "cko-donate-details",
sx: {
width: "100%",
minWidth: "256px",
maxWidth: "calc(100vw - 32px)",
maxHeight: "300px",
overflowX: "hidden",
overflowY: "auto",
margin: "0 auto"
},
children: supporters.map(x => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
padding: "6px",
"&:hover": {
backgroundColor: theme => theme.palette.divider,
transition: "background-color 200ms linear",
cursor: "pointer"
},
borderBottom: "1px solid",
borderColor: "divider",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
},
onClick: () => {
const {
link,
text
} = (0, _util.getTxLink)(method, x.payment_details);
if (link && text) {
window.open(link, "_blank");
}
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center",
flex: 3,
overflow: "hidden"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
src: (0, _util.getCustomerAvatar)(x.customer?.did, x?.updated_at ? new Date(x.updated_at).toISOString() : "", 20),
alt: x.customer?.metadata?.anonymous ? "" : x.customer?.name,
variant: "circular",
sx: {
width: 20,
height: 20
},
onClick: e => {
e.stopPropagation();
if (x.customer?.metadata?.anonymous) {
return;
}
if (x.customer?.did) {
window.open((0, _util.getUserProfileLink)(x.customer?.did, locale), "_blank");
}
}
}, x.id), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
ml: "8px !important",
fontSize: "0.875rem",
fontWeight: "500",
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis"
},
onClick: e => {
if (x.customer?.metadata?.anonymous) {
return;
}
e.stopPropagation();
if (x.customer?.did) {
window.open((0, _util.getUserProfileLink)(x.customer?.did, locale), "_blank");
}
},
children: x.customer?.name
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center",
justifyContent: "flex-end",
flex: 1
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
src: currency?.logo,
alt: currency?.symbol,
variant: "circular",
sx: {
width: 16,
height: 16
}
}, x.id), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
color: "text.secondary"
},
children: (0, _util.formatBNStr)(x.amount_total, currency.decimal)
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
color: "text.secondary"
},
children: currency.symbol
})]
})]
}, x.id))
});
}
function SupporterAvatar({
supporters = [],
totalAmount = "0",
currency,
method,
showDonateDetails = false
}) {
const [open, setOpen] = (0, _react.useState)(false);
const customers = (0, _uniqBy.default)(supporters, "customer_id");
const customersNum = customers.length;
if (customersNum === 0) return null;
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
sx: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.AvatarGroup, {
max: 5,
sx: {
"& .MuiAvatar-root": {
backgroundColor: "background.paper",
"&.MuiAvatar-colorDefault": {
backgroundColor: "#bdbdbd"
}
}
},
children: customers.slice(0, 5).map(supporter => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
src: (0, _util.getCustomerAvatar)(supporter.customer?.did, supporter?.updated_at ? new Date(supporter.updated_at).toISOString() : "", 24),
alt: supporter.customer?.metadata?.anonymous ? "" : supporter.customer?.name,
sx: {
width: "24px",
height: "24px"
}
}, supporter.customer?.id))
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
fontSize: "14px",
color: "text.secondary",
pl: 1.5,
pr: 1,
ml: -1,
borderRadius: "8px",
backgroundColor: "grey.100",
height: "24px",
...(showDonateDetails ? {
cursor: "pointer",
"&:hover": {
backgroundColor: "grey.200"
}
} : {})
},
onClick: () => showDonateDetails && setOpen(true),
children: `${customersNum} supporter${customersNum > 1 ? "s" : ""} (${(0, _util.formatAmount)(totalAmount || "0", currency?.decimal)} ${currency.symbol})`
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_Dialog.default, {
open,
onClose: () => setOpen(false),
sx: {
".MuiDialogContent-root": {
width: {
xs: "100%",
md: "450px"
},
padding: "8px"
},
".cko-donate-details": {
maxHeight: {
xs: "100%",
md: "300px"
}
}
},
title: `${customersNum} supporter${customersNum > 1 ? "s" : ""}`,
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(DonateDetails, {
supporters,
currency,
method,
totalAmount
})
})]
});
}
function SupporterTable({
supporters = [],
totalAmount = "0",
currency,
method
}) {
const customers = (0, _uniqBy.default)(supporters, "customer_id");
const customersNum = customers.length;
if (customersNum === 0) return null;
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: {
xs: 0.5,
sm: 1
}
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterAvatar, {
supporters,
totalAmount,
currency,
method
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(DonateDetails, {
supporters,
totalAmount,
currency,
method
})]
});
}
function SupporterSimple({
supporters = [],
totalAmount = "0",
currency,
method
}) {
const {
t
} = (0, _context.useLocaleContext)();
const customers = (0, _uniqBy.default)(supporters, "customer_id");
const customersNum = customers.length;
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: {
xs: 0.5,
sm: 1
}
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "p",
sx: {
color: "text.secondary"
},
children: customersNum === 0 ? /* @__PURE__ */(0, _jsxRuntime.jsx)("span", {
style: emojiFont,
children: t("payment.checkout.donation.empty")
}) : t("payment.checkout.donation.summary", {
total: customersNum,
symbol: currency.symbol,
totalAmount: (0, _util.formatAmount)(totalAmount || "0", currency.decimal)
})
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.AvatarGroup, {
total: customersNum,
max: 10,
spacing: 4,
sx: {
"& .MuiAvatar-root": {
width: 24,
height: 24,
fontSize: "0.6rem"
}
},
children: customers.map(x => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
title: x.customer?.name,
alt: x.customer?.metadata?.anonymous ? "" : x.customer?.name,
src: (0, _util.getCustomerAvatar)(x.customer?.did, x?.updated_at ? new Date(x.updated_at).toISOString() : "", 48),
variant: "circular"
}, x.id))
})]
});
}
const defaultDonateAmount = {
presets: ["1", "5", "10"],
preset: "1",
minimum: "0.01",
maximum: "100",
custom: true
};
function useDonation(settings, livemode, mode = "default") {
const [state, setState] = (0, _ahooks.useSetState)({
open: false,
supporterLoaded: false,
exist: false
});
const donateContext = (0, _donate.useDonateContext)();
const {
isMobile
} = (0, _mobile.useMobile)();
const {
settings: donateConfig = {}
} = donateContext || {};
const donateSettings = {
...settings,
amount: settings.amount || donateConfig?.settings?.amount || defaultDonateAmount,
appearance: {
button: {
...(settings?.appearance?.button || {}),
text: settings?.appearance?.button?.text || donateConfig?.settings?.btnText || "Donate",
icon: settings?.appearance?.button?.icon || donateConfig?.settings?.icon || null
},
history: {
variant: settings?.appearance?.history?.variant || donateConfig?.settings?.historyType || "avatar"
}
}
};
const hasRequestedRef = (0, _react.useRef)(false);
const containerRef = (0, _react.useRef)(null);
const donation = (0, _ahooks.useRequest)(() => createOrUpdateDonation(donateSettings, livemode), {
manual: true,
loadingDelay: 300
});
const supporters = (0, _ahooks.useRequest)(() => donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({}), {
manual: true,
loadingDelay: 300
});
const rootMargin = isMobile ? "50px" : `${Math.min(window.innerHeight / 2, 300)}px`;
(0, _react.useEffect)(() => {
if (mode === "inline") return;
const element = containerRef.current;
if (!element) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !hasRequestedRef.current) {
hasRequestedRef.current = true;
(0, _util.lazyLoad)(() => {
donation.run();
supporters.run();
});
}
}, {
threshold: 0,
rootMargin
});
observer.observe(element);
return () => observer.unobserve(element);
}, [mode]);
(0, _react.useEffect)(() => {
if (donation.data && state.supporterLoaded === false) {
setState({
supporterLoaded: true
});
supporters.runAsync().catch(console.error);
}
}, [donation.data]);
return {
containerRef,
donation,
supporters,
state,
setState,
donateSettings,
supportUpdateSettings: !!donateContext.settings
};
}
function CheckoutDonateInner({
settings,
livemode = true,
timeout,
onPaid,
onError,
mode,
inlineOptions = {},
theme,
children
}) {
const {
containerRef,
state,
setState,
donation,
supporters,
donateSettings,
supportUpdateSettings
} = useDonation(settings, livemode, mode);
const customers = (0, _uniqBy.default)(supporters?.data?.supporters || [], "customer_did");
const {
t
} = (0, _context.useLocaleContext)();
const [anchorEl, setAnchorEl] = (0, _react.useState)(null);
const [popoverOpen, setPopoverOpen] = (0, _react.useState)(false);
const {
isMobile
} = (0, _mobile.useMobile)();
const {
connect,
session
} = (0, _payment.usePaymentContext)();
const handlePaid = (...args) => {
if (onPaid) {
onPaid(...args);
}
supporters.runAsync().catch(console.error);
setTimeout(() => {
setState({
open: false
});
}, timeout);
};
if (donation.error) {
return null;
}
const handlePopoverOpen = event => {
donation.run();
supporters.run();
setAnchorEl(event.currentTarget);
setPopoverOpen(true);
};
const handlePopoverClose = () => {
setPopoverOpen(false);
};
const startDonate = () => {
setState({
open: true
});
};
const inlineText = inlineOptions?.button?.text || donateSettings.appearance.button.text;
const inlineRender = /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
size: donateSettings.appearance?.button?.size || "medium",
color: donateSettings.appearance?.button?.color || "primary",
variant: donateSettings.appearance?.button?.variant || "contained",
...donateSettings.appearance?.button,
onClick: handlePopoverOpen,
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center"
},
children: [donateSettings.appearance.button.icon, typeof donateSettings.appearance.button.text === "string" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
whiteSpace: "nowrap"
},
children: donateSettings.appearance.button.text
}) : donateSettings.appearance.button.text]
})
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Popover, {
id: "mouse-over-popper",
open: popoverOpen,
anchorEl,
onClose: handlePopoverClose,
anchorOrigin: {
vertical: "top",
horizontal: "center"
},
transformOrigin: {
vertical: "bottom",
horizontal: "center"
},
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
minWidth: 320,
padding: "20px"
},
children: [supporters.loading && /* @__PURE__ */(0, _jsxRuntime.jsx)("div", {
style: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "background.paper",
opacity: 0.8
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {})
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
flexDirection: "column",
gap: 2
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
...inlineOptions.button,
onClick: () => startDonate(),
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center"
},
children: [inlineOptions?.button?.icon, typeof inlineText === "string" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
whiteSpace: "nowrap"
},
children: inlineText
}) : inlineText]
})
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterSimple, {
...supporters.data
})]
})]
})
})]
});
const defaultRender = /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: {
xs: 1,
sm: 2
},
width: "100%",
minWidth: 300,
maxWidth: 720
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
size: donateSettings.appearance?.button?.size || "medium",
color: donateSettings.appearance?.button?.color || "primary",
variant: donateSettings.appearance?.button?.variant || "outlined",
sx: {
...(!donateSettings.appearance?.button?.variant ? {
color: "primary.main",
borderColor: "divider"
} : {}),
// @ts-ignore
...(donateSettings.appearance?.button?.sx || {})
},
...donateSettings.appearance?.button,
onClick: () => startDonate(),
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center"
},
children: [donateSettings.appearance.button.icon && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
mr: 0.5,
display: "inline-flex",
alignItems: "center"
},
children: donateSettings.appearance.button.icon
}), typeof donateSettings.appearance.button.text === "string" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
children: donateSettings.appearance.button.text
}) : donateSettings.appearance.button.text]
})
}), supporters.data && donateSettings.appearance.history.variant === "avatar" && /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterAvatar, {
...supporters.data,
showDonateDetails: true
}), supporters.data && donateSettings.appearance.history.variant === "table" && /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterTable, {
...supporters.data
})]
});
const renderInnerView = () => {
if (mode === "inline") {
return inlineRender;
}
if (mode === "custom") {
return children && typeof children === "function" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: children(startDonate, `${(0, _util.formatAmount)(supporters.data?.totalAmount || "0", supporters.data?.currency?.decimal)} ${supporters.data?.currency?.symbol || ""}`, supporters.data || {}, !!supporters.loading, donateSettings)
}) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
children: ["Please provide a valid render function", " ", /* @__PURE__ */(0, _jsxRuntime.jsx)("pre", {
children: "(openDonate, donateTotalAmount, supporters, loading, donateSettings) => ReactNode"
})]
});
}
return defaultRender;
};
const isAdmin = ["owner", "admin"].includes(session?.user?.role);
return /* @__PURE__ */(0, _jsxRuntime.jsxs)("div", {
ref: containerRef,
children: [renderInnerView(), donation.data && /* @__PURE__ */(0, _jsxRuntime.jsx)(_Dialog.default, {
open: state.open,
title: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
gap: 0.5
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "h3",
sx: {
maxWidth: 320,
textOverflow: "ellipsis",
overflow: "hidden"
},
children: donateSettings.title
}), supportUpdateSettings && isAdmin && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, {
title: t("payment.checkout.donation.configTip"),
placement: "bottom",
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
size: "small",
onClick: e => {
e.stopPropagation();
(0, _util.openDonationSettings)(true);
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Settings, {
fontSize: "small",
sx: {
ml: -0.5
}
})
})
}), !donation.data.livemode && /* @__PURE__ */(0, _jsxRuntime.jsx)(_livemode.default, {
sx: {
width: "fit-content",
ml: 0.5
}
})]
}),
maxWidth: "md",
toolbar: isMobile ? null : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
gap: 1,
color: "text.secondary"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.AvatarGroup, {
total: customers?.length,
max: 5,
spacing: 4,
sx: {
"& .MuiAvatar-root": {
width: 18,
height: 18,
fontSize: "0.6rem"
}
},
children: customers.map(x => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
title: x.customer?.name,
src: (0, _util.getCustomerAvatar)(x.customer?.did, x?.updated_at ? new Date(x.updated_at).toISOString() : "", 24),
variant: "circular"
}, x.id))
}), customers?.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "body2",
children: t("payment.checkout.donation.gaveTips", {
count: customers?.length
})
})]
}),
showCloseButton: false,
disableEscapeKeyDown: true,
sx: {
".MuiDialogContent-root": {
padding: "16px 24px",
borderTop: "1px solid",
borderColor: "divider",
width: "100%"
},
".ux-dialog_header": {
gap: 5
}
},
PaperProps: {
style: {
minHeight: "auto",
width: 680
}
},
onClose: (e, reason) => setState({
open: reason === "backdropClick"
}),
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
height: "100%",
width: "100%"
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_form.default, {
id: donation.data?.id,
onPaid: handlePaid,
onError,
action: donateSettings.appearance?.button?.text,
mode: "inline",
theme,
formType: "donation",
extraParams: {
livemode
},
formRender: {
cancel: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
variant: "outlined",
size: "large",
onClick: () => {
connect.close();
setState({
open: false
});
},
children: t("common.cancel")
}),
onCancel: () => {
connect.close();
setState({
open: false
});
}
}
})
})
})]
});
}
function CheckoutDonate(rawProps) {
const props = Object.assign({
theme: "default",
livemode: void 0,
inlineOptions: {
button: {
text: "Tip"
}
},
timeout: 5e3,
mode: "default"
}, rawProps);
const {
livemode
} = (0, _payment.usePaymentContext)();
const content =
// eslint-disable-next-line react/prop-types
/* @__PURE__ */
(0, _jsxRuntime.jsx)(CheckoutDonateInner, {
...props,
livemode: props.livemode === void 0 ? livemode : props.livemode
});
if (props.theme === "inherit") {
return content;
}
if (props.theme && typeof props.theme === "object") {
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_theme.PaymentThemeProvider, {
theme: props.theme,
children: content
});
}
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_theme.PaymentThemeProvider, {
children: content
});
}