UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

183 lines (182 loc) 7.59 kB
import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { useEffect, useState } from "react"; import TuneIcon from "@mui/icons-material/Tune"; import { Box, Dialog, DialogContent, DialogTitle, Fade, Stack, Tooltip, Typography, keyframes } from "@mui/material"; import { useLocaleContext } from "@arcblock/ux/lib/Locale/context"; import SlippageConfig from "../../../components/slippage-config.js"; import { whiteTooltipSx } from "../../utils/format.js"; const ping = keyframes` 75%, 100% { transform: scale(2); opacity: 0; } `; function formatDateTime(ts) { if (!ts) return "\u2014"; const d = new Date(ts); const pad = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } export default function ExchangeRateFooter({ hasDynamicPricing, rate, slippage, currencySymbol, isSubscription }) { const { t } = useLocaleContext(); const [dialogOpen, setDialogOpen] = useState(false); const [pendingConfig, setPendingConfig] = useState(null); const [submitting, setSubmitting] = useState(false); const [localSlippage, setLocalSlippage] = useState(slippage?.percent ?? 0.5); useEffect(() => { setLocalSlippage(slippage?.percent ?? 0.5); }, [slippage?.percent]); if (!hasDynamicPricing || !rate.value || rate.status === "unavailable") return null; const rateDisplay = rate.display || `$${Number(rate.value).toFixed(2)}`; const showSlippage = isSubscription && typeof slippage?.set === "function"; const handleOpenDialog = () => { setPendingConfig({ mode: "percent", percent: localSlippage }); setDialogOpen(true); }; const handleCloseDialog = () => { setDialogOpen(false); setPendingConfig(null); }; const handleSlippageChange = (value) => { setPendingConfig((prev) => prev ? { ...prev, percent: value } : { mode: "percent", percent: value }); }; const handleConfigChange = (config) => { setPendingConfig(config); }; const handleSubmit = async () => { if (!pendingConfig || !slippage?.set) return; setSubmitting(true); try { setLocalSlippage(pendingConfig.percent); await slippage.set({ ...pendingConfig, base_currency: "USD" }); setDialogOpen(false); setPendingConfig(null); } catch (err) { setLocalSlippage(slippage?.percent ?? 0.5); console.error("Failed to update slippage", err); } finally { setSubmitting(false); } }; const labelSx = { fontSize: 11, fontWeight: 700, color: "primary.main", letterSpacing: "0.02em" }; const providerName = rate.providerDisplay || rate.provider || "\u2014"; const updatedAt = formatDateTime(rate.fetchedAt); const fullRate = `$${rate.value}`; const tooltipSx = whiteTooltipSx; const rowSx = { fontSize: 12, color: "text.secondary", lineHeight: 1.4 }; const valSx = { fontSize: 12, fontWeight: 600, color: "text.primary", lineHeight: 1.4 }; const tooltipContent = /* @__PURE__ */ jsxs(Stack, { spacing: 0.75, children: [ /* @__PURE__ */ jsxs(Typography, { sx: { fontSize: 13, fontWeight: 700, color: "text.primary", mb: 0.25 }, children: [ "1 ", currencySymbol, " = ", fullRate ] }), /* @__PURE__ */ jsx(Box, { sx: { borderTop: "1px solid", borderColor: "divider", pt: 0.75 }, children: /* @__PURE__ */ jsxs(Stack, { spacing: 0.5, children: [ /* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", spacing: 2, children: [ /* @__PURE__ */ jsx(Typography, { sx: rowSx, children: t("payment.checkout.quote.detailProvider") }), /* @__PURE__ */ jsx(Typography, { sx: valSx, children: providerName }) ] }), /* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", spacing: 2, children: [ /* @__PURE__ */ jsx(Typography, { sx: rowSx, children: t("payment.checkout.quote.detailUpdatedAt") }), /* @__PURE__ */ jsx(Typography, { sx: valSx, children: updatedAt }) ] }) ] }) }) ] }); const slippageTooltip = t("payment.checkout.quote.slippage.tooltip"); return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx(Box, { sx: { width: "100%", pt: 3, pb: 1, borderTop: "1px solid", borderColor: "divider" }, children: /* @__PURE__ */ jsx(Fade, { in: true, timeout: 300, children: /* @__PURE__ */ jsxs(Stack, { direction: "row", sx: { alignItems: "center", justifyContent: "space-between" }, children: [ /* @__PURE__ */ jsx(Tooltip, { title: tooltipContent, placement: "top", arrow: true, slotProps: { popper: { sx: tooltipSx } }, children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, sx: { cursor: "default" }, children: [ /* @__PURE__ */ jsxs(Box, { sx: { position: "relative", width: 8, height: 8, flexShrink: 0 }, children: [ /* @__PURE__ */ jsx( Box, { sx: { position: "absolute", inset: 0, borderRadius: "50%", bgcolor: "#60a5fa", opacity: 0.6, animation: `${ping} 1s cubic-bezier(0, 0, 0.2, 1) infinite` } } ), /* @__PURE__ */ jsx( Box, { sx: { position: "relative", width: 8, height: 8, borderRadius: "50%", bgcolor: "primary.main" } } ) ] }), /* @__PURE__ */ jsxs(Typography, { sx: labelSx, children: [ "1 ", currencySymbol, " \u2248 ", rateDisplay ] }) ] }) }), showSlippage && /* @__PURE__ */ jsx(Tooltip, { title: slippageTooltip, placement: "top", arrow: true, slotProps: { popper: { sx: whiteTooltipSx } }, children: /* @__PURE__ */ jsxs( Stack, { direction: "row", alignItems: "center", spacing: 0.75, onClick: handleOpenDialog, sx: { cursor: "pointer", "&:hover": { opacity: 1 }, "&:hover .tune-icon": { transform: "rotate(90deg)" }, transition: "opacity 0.2s" }, children: [ /* @__PURE__ */ jsx( TuneIcon, { className: "tune-icon", sx: { fontSize: 16, color: "primary.main", transition: "transform 0.5s" } } ), /* @__PURE__ */ jsxs(Typography, { sx: labelSx, children: [ t("payment.checkout.quote.detailSlippage"), " ", localSlippage, "%" ] }) ] } ) }) ] }) }) }), /* @__PURE__ */ jsxs(Dialog, { open: dialogOpen, onClose: handleCloseDialog, maxWidth: "sm", fullWidth: true, children: [ /* @__PURE__ */ jsx(DialogTitle, { children: t("payment.checkout.quote.slippage.title") }), /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx( SlippageConfig, { value: pendingConfig?.percent ?? localSlippage, onChange: handleSlippageChange, config: pendingConfig || { mode: "percent", percent: localSlippage }, onConfigChange: handleConfigChange, exchangeRate: rate.value ? String(rate.value) : null, baseCurrency: "USD", disabled: submitting, sx: { mt: 1 }, onCancel: handleCloseDialog, onSave: handleSubmit } ) }) ] }) ] }); }