UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

498 lines (497 loc) 16.7 kB
import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { useState, useCallback } from "react"; import { Box, Typography, Stack, Button, CircularProgress, Card, CardContent, IconButton, Tooltip, Collapse } from "@mui/material"; import { AddOutlined, CreditCard, SettingsOutlined, AccountBalanceWalletOutlined } from "@mui/icons-material"; import { useLocaleContext } from "@arcblock/ux/lib/Locale/context"; import { useNavigate } from "react-router-dom"; import { joinURL } from "ufo"; import { useRequest } from "ahooks"; import { getPrefix, formatBNStr, formatNumber, formatPrice } from "../../libs/util.js"; import { createLink, handleNavigation } from "../../libs/navigation.js"; import { usePaymentContext } from "../../contexts/payment.js"; import api from "../../libs/api.js"; import AutoTopupModal from "./modal.js"; import { useMobile } from "../../hooks/mobile.js"; const fetchConfig = async (customerId, currencyId) => { const { data } = await api.get(`/api/auto-recharge-configs/customer/${customerId}`, { params: { currency_id: currencyId } }); return data; }; const fetchCurrencyBalance = async (currencyId, payerAddress) => { const { data } = await api.get("/api/customers/payer-token", { params: { currencyId, payerAddress } }); return data; }; const cardStyle = { height: "100%", width: "100%", border: "1px solid", borderColor: "divider", boxShadow: 1, borderRadius: 1, backgroundColor: "background.default" }; export default function AutoTopupCard({ currencyId, onConfigChange = () => { }, sx = {}, mode = "default", children = void 0 }) { const { t } = useLocaleContext(); const navigate = useNavigate(); const { session } = usePaymentContext(); const [modalOpen, setModalOpen] = useState(false); const [paymentData, setPaymentData] = useState(null); const [quickSetupMode, setQuickSetupMode] = useState(false); const [expanded, setExpanded] = useState(mode === "default"); const customerId = session?.user?.did || ""; const { isMobile } = useMobile(); const { data: config, loading, refresh } = useRequest(() => fetchConfig(customerId, currencyId), { refreshDeps: [customerId, currencyId], ready: !!customerId && !!currencyId, onSuccess: (data) => { loadPaymentInfo(data); } }); const loadPaymentInfo = useCallback(async (data) => { if (!data?.recharge_currency_id) return; try { const paymentMethodType = data?.paymentMethod?.type; const paymentInfo = data?.payment_settings?.payment_method_options?.[paymentMethodType]; const balanceInfo = paymentInfo?.payer && paymentMethodType !== "stripe" ? await fetchCurrencyBalance(data.recharge_currency_id, paymentInfo.payer) : null; setPaymentData({ paymentInfo, balanceInfo }); } catch (error) { console.error("Failed to load payment info:", error); } }, []); const handleRecharge = (e) => { if (!paymentData?.paymentInfo?.payer) return; const url = joinURL( getPrefix(), `/customer/recharge/${config?.recharge_currency_id}?rechargeAddress=${paymentData.paymentInfo.payer}` ); const link = createLink(url, true); handleNavigation(e, link, navigate); }; const handleConfigSuccess = (newConfig) => { refresh(); onConfigChange?.(newConfig); setModalOpen(false); setQuickSetupMode(false); }; const handleToggleExpanded = () => { setExpanded(!expanded); }; if (loading) { return /* @__PURE__ */ jsx(Card, { sx: { ...cardStyle, ...sx }, children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Box, { sx: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 80 }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 24 }) }) }) }); } if (!config) { return null; } const renderPurchaseDetails = () => { const { paymentInfo, balanceInfo } = paymentData || {}; if (!paymentInfo) { return /* @__PURE__ */ jsx( Typography, { variant: "body2", sx: { color: "text.secondary" }, children: t("payment.autoTopup.notConfigured") } ); } const purchaseAmount = formatPrice( config.price, config.rechargeCurrency, config.price.product?.unit_label, config.quantity, true ); if (config?.paymentMethod?.type === "stripe") { const cardBrand = (paymentInfo?.card_brand || "Card").charAt(0).toUpperCase() + (paymentInfo?.card_brand || "Card").slice(1).toLowerCase(); const last4 = paymentInfo?.card_last4; return /* @__PURE__ */ jsxs(Stack, { spacing: 1, className: "auto-topup-method-info", children: [ /* @__PURE__ */ jsxs( Box, { sx: { display: "flex", alignItems: "center", gap: 0.5, justifyContent: { xs: "space-between", sm: "flex-start" } }, children: [ /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary" }, children: [ t("payment.autoTopup.purchaseAmount"), "\uFF1A" ] } ), /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, color: "text.primary" }, children: purchaseAmount }) ] } ), /* @__PURE__ */ jsxs( Box, { sx: { display: "flex", alignItems: "center", gap: 0.5, justifyContent: { xs: "space-between", sm: "flex-start" } }, children: [ /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary" }, children: [ t("payment.autoTopup.paymentMethod"), "\uFF1A" ] } ), /* @__PURE__ */ jsxs( Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [ /* @__PURE__ */ jsx(CreditCard, { fontSize: "small", sx: { color: "text.secondary" } }), /* @__PURE__ */ jsxs(Typography, { variant: "body2", sx: { color: "text.primary", fontWeight: 500 }, children: [ cardBrand, "(", last4, ")" ] }) ] } ) ] } ) ] }); } return /* @__PURE__ */ jsxs(Stack, { spacing: 1, className: "auto-topup-method-info", children: [ /* @__PURE__ */ jsxs( Box, { sx: { display: "flex", alignItems: "center", gap: 0.5, justifyContent: { xs: "space-between", sm: "flex-start" } }, children: [ /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary" }, children: [ t("payment.autoTopup.purchaseAmount"), "\uFF1A" ] } ), /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, color: "text.primary" }, children: purchaseAmount }) ] } ), /* @__PURE__ */ jsxs( Box, { sx: { display: "flex", alignItems: "center", justifyContent: { xs: "space-between", sm: "flex-start" } }, children: [ /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary", whiteSpace: "nowrap" }, children: [ t("payment.autoTopup.walletBalance"), "\uFF1A" ] } ), /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [ /* @__PURE__ */ jsx( Tooltip, { title: paymentInfo?.payer ? `${t("payment.autoTopup.paymentAddress")}: ${paymentInfo.payer}` : "", placement: "top", children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: [ /* @__PURE__ */ jsx(AccountBalanceWalletOutlined, { sx: { fontSize: 16, color: "text.secondary" } }), /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, color: "text.primary" }, children: balanceInfo ? `${formatBNStr(balanceInfo?.token || "0", config?.rechargeCurrency?.decimal || 18)} ${config?.rechargeCurrency?.symbol || ""}` : "--" }) ] }) } ), balanceInfo && !isMobile && /* @__PURE__ */ jsxs( Button, { size: "small", variant: "text", onClick: handleRecharge, className: "auto-topup-add-funds-button", sx: { color: "primary.main", display: "flex", alignItems: "center" }, children: [ /* @__PURE__ */ jsx(AddOutlined, { fontSize: "small" }), t("payment.autoTopup.addFunds") ] } ) ] }) ] } ), balanceInfo && isMobile && /* @__PURE__ */ jsxs( Button, { size: "small", variant: "text", onClick: handleRecharge, className: "auto-topup-add-funds-button", sx: { color: "primary.main", display: "flex", alignItems: "center", justifyContent: "flex-end" }, children: [ /* @__PURE__ */ jsx(AddOutlined, { fontSize: "small" }), t("payment.autoTopup.addFunds") ] } ) ] }); }; const openModal = () => setModalOpen(true); const renderInnerView = () => { if (mode === "custom") { return children && typeof children === "function" ? /* @__PURE__ */ jsx(Fragment, { children: children(openModal, config, paymentData, loading) }) : /* @__PURE__ */ jsxs(Typography, { children: [ "Please provide a valid render function", /* @__PURE__ */ jsx("pre", { children: "(openModal, config, paymentData, loading) => ReactNode" }) ] }); } return /* @__PURE__ */ jsx(Card, { sx: { ...cardStyle, ...sx }, children: /* @__PURE__ */ jsxs(CardContent, { children: [ /* @__PURE__ */ jsxs( Stack, { direction: "row", className: "auto-topup-header", sx: { justifyContent: "space-between", alignItems: "center", borderBottom: "1px solid", borderColor: "divider", pb: 1.5 }, children: [ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", sx: { fontWeight: 600, color: "text.primary" }, children: t("payment.autoTopup.title") }), /* @__PURE__ */ jsx(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: /* @__PURE__ */ jsx( IconButton, { size: "small", onClick: openModal, sx: { p: 0.5, color: "text.secondary", "&:hover": { bgcolor: "grey.50", color: "text.primary" } }, children: /* @__PURE__ */ jsx(SettingsOutlined, { fontSize: "small" }) } ) }) ] } ), config?.enabled ? /* @__PURE__ */ jsxs(Stack, { spacing: 1.5, className: "auto-topup-content", sx: { pt: 1.5 }, children: [ (() => { const threshold = `${formatNumber(config.threshold)} ${config.currency?.symbol || ""}`; const credits = `${formatNumber( Number(config.price.metadata?.credit_config?.credit_amount || 0) * Number(config.quantity) )} ${config.currency?.name || ""}`; return /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary" }, children: [ t("payment.autoTopup.activeDescriptionWithCredits", { threshold, credits }), mode === "simple" && /* @__PURE__ */ jsx( Button, { component: "span", size: "small", variant: "text", onClick: handleToggleExpanded, sx: { color: "primary.main", minWidth: "auto", ml: 1, p: 0, fontSize: "inherit", textTransform: "none", "&:hover": { backgroundColor: "transparent", textDecoration: "underline" } }, children: expanded ? t("payment.autoTopup.hideDetails") : t("payment.autoTopup.showDetails") } ) ] } ); })(), /* @__PURE__ */ jsx(Collapse, { in: mode === "default" || expanded, children: /* @__PURE__ */ jsx( Box, { sx: { bgcolor: "grey.50", borderRadius: 1, p: 1.5 }, children: renderPurchaseDetails() } ) }) ] }) : /* @__PURE__ */ jsx( Stack, { className: "auto-topup-content", sx: { minHeight: 80, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pt: 1.5, gap: 2 }, children: /* @__PURE__ */ jsxs( Typography, { variant: "body2", sx: { color: "text.secondary", textAlign: "left" }, children: [ t("payment.autoTopup.inactiveDescription", { name: config?.currency?.name }), /* @__PURE__ */ jsx( Button, { component: "span", variant: "text", size: "small", onClick: () => { setQuickSetupMode(true); setModalOpen(true); }, sx: { color: "primary.main", minWidth: "auto", ml: 1, p: 0, fontSize: "inherit", textTransform: "none", "&:hover": { backgroundColor: "transparent", textDecoration: "underline" } }, children: t("payment.autoTopup.setup") } ) ] } ) } ) ] }) }); }; return /* @__PURE__ */ jsxs(Fragment, { children: [ renderInnerView(), modalOpen && /* @__PURE__ */ jsx( AutoTopupModal, { open: modalOpen, onClose: () => { setModalOpen(false); setQuickSetupMode(false); }, currencyId, onSuccess: handleConfigSuccess, defaultEnabled: quickSetupMode } ) ] }); }