@blocklet/payment-react
Version:
Reusable react components for payment kit v2
183 lines (182 loc) • 7.59 kB
JavaScript
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
}
) })
] })
] });
}