import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import SettingsIcon from '@mui/icons-material/Settings'; import { Box, Collapse, Dialog, DialogTitle, DialogContent, Fade, IconButton, Stack, Tooltip, Typography, } from '@mui/material'; import type { ReactNode } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useLocaleContext } from '@arcblock/ux/lib/Locale/context'; import SlippageConfig from './slippage-config'; import type { SlippageConfigValue } from './slippage-config'; type QuoteDetailRow = { label: string; value: ReactNode; isSlippage?: boolean; tooltip?: string; }; type QuoteDetailsPanelProps = { rateLine: string; rows: QuoteDetailRow[]; isSubscription?: boolean; slippageValue?: number; onSlippageChange?: (value: SlippageConfigValue) => void | Promise; slippageConfig?: SlippageConfigValue; exchangeRate?: string | null; baseCurrency?: string; disabled?: boolean; }; export default function QuoteDetailsPanel({ rateLine, rows, isSubscription = false, slippageValue = 0.5, onSlippageChange = undefined, slippageConfig = undefined, exchangeRate = null, baseCurrency = 'USD', disabled = false, }: QuoteDetailsPanelProps) { const { t } = useLocaleContext(); const [open, setOpen] = useState(false); const [showContent, setShowContent] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [pendingConfig, setPendingConfig] = useState(null); const [submitting, setSubmitting] = useState(false); const hasRows = rows.length > 0; const handleOpenDialog = useCallback(() => { setPendingConfig(slippageConfig || { mode: 'percent', percent: slippageValue }); setDialogOpen(true); }, [slippageValue, slippageConfig]); const handleCloseDialog = () => { setDialogOpen(false); setPendingConfig(null); }; const handleSlippageChange = (value: number) => { setPendingConfig((prev) => (prev ? { ...prev, percent: value } : { mode: 'percent', percent: value })); }; const handleConfigChange = (config: SlippageConfigValue) => { setPendingConfig(config); }; const handleSubmit = async () => { if (!pendingConfig || !onSlippageChange) return; setSubmitting(true); try { const configToSave: SlippageConfigValue = { ...pendingConfig, ...(baseCurrency ? { base_currency: baseCurrency } : {}), }; await onSlippageChange(configToSave); setDialogOpen(false); setPendingConfig(null); } catch (err) { console.error('Failed to update slippage', err); } finally { setSubmitting(false); } }; // Trigger fade effect when rateLine changes useEffect(() => { if (!rateLine) return undefined; setShowContent(false); const timer = setTimeout(() => { setShowContent(true); }, 150); return () => clearTimeout(timer); }, [rateLine]); const renderedRows = useMemo( () => rows.map((row) => { const isSlippageRow = row.isSlippage && isSubscription && onSlippageChange; return ( {row.label} {row.tooltip && ( )} {row.value} {isSlippageRow && !disabled && ( )} ); }), [rows, isSubscription, onSlippageChange, disabled, handleOpenDialog] ); // For subscriptions with slippage settings, show panel even without rateLine // This allows metered subscriptions to configure slippage before first usage const showSlippageOnly = !rateLine && isSubscription && onSlippageChange && rows.some((r) => r.isSlippage); if (!rateLine && !showSlippageOnly) { return null; } return ( <> {rateLine ? ( {rateLine} ) : ( // Empty placeholder when no rate line )} {hasRows && ( setOpen((prev) => !prev)} aria-label={open ? 'collapse' : 'expand'}> )} {hasRows && ( {renderedRows} )} {t('payment.checkout.quote.slippage.title')} ); }