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'; import { whiteTooltipSx } from '../../utils/format'; const ping = keyframes` 75%, 100% { transform: scale(2); opacity: 0; } `; function formatDateTime(ts: number | null): string { if (!ts) return '—'; const d = new Date(ts); const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } interface ExchangeRateFooterProps { hasDynamicPricing: boolean; rate: { value: string | null; display: string | null; provider: string | null; providerDisplay: string | null; fetchedAt: number | null; status: 'loading' | 'available' | 'unavailable'; }; slippage: { percent: number; set: (config: { mode: string; percent: number; base_currency?: string }) => Promise; }; currencySymbol: string; isSubscription: boolean; } export default function ExchangeRateFooter({ hasDynamicPricing, rate, slippage, currencySymbol, isSubscription, }: ExchangeRateFooterProps) { const { t } = useLocaleContext(); const [dialogOpen, setDialogOpen] = useState(false); const [pendingConfig, setPendingConfig] = useState<{ mode: 'percent' | 'rate'; percent: number } | null>(null); const [submitting, setSubmitting] = useState(false); // Optimistic local slippage — sync from prop, update immediately on save 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; // Use pre-formatted display (e.g. "$0.19") — no redundant "USD" suffix 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: number) => { setPendingConfig((prev) => (prev ? { ...prev, percent: value } : { mode: 'percent' as const, percent: value })); }; const handleConfigChange = (config: { mode: 'percent' | 'rate'; percent: number }) => { setPendingConfig(config); }; const handleSubmit = async () => { if (!pendingConfig || !slippage?.set) return; setSubmitting(true); try { // Optimistic update — show new value immediately setLocalSlippage(pendingConfig.percent); await slippage.set({ ...pendingConfig, base_currency: 'USD' }); setDialogOpen(false); setPendingConfig(null); } catch (err) { // Revert on error 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', }; // Hover tooltip content: full rate + provider + update time const providerName = rate.providerDisplay || rate.provider || '—'; 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 = ( {/* Full precision rate */} 1 {currencySymbol} = {fullRate} {t('payment.checkout.quote.detailProvider')} {providerName} {t('payment.checkout.quote.detailUpdatedAt')} {updatedAt} ); // Slippage tooltip const slippageTooltip = t('payment.checkout.quote.slippage.tooltip'); return ( <> {/* Rate with hover tooltip showing full rate + provider + update time */} {/* Ping dot */} 1 {currencySymbol} ≈ {rateDisplay} {/* Slippage with tooltip + click to configure */} {showSlippage && ( {t('payment.checkout.quote.detailSlippage')} {localSlippage}% )} {t('payment.checkout.quote.slippage.title')} ); }