UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

804 lines (803 loc) 25.6 kB
"use strict"; 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 }); }