adding toast and displaying fees in the swap-form and modal

This commit is contained in:
2025-10-20 18:09:16 -04:00
parent cdbf2a57e6
commit caf6bff469
4 changed files with 259 additions and 74 deletions

View File

@@ -5,25 +5,26 @@ import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { ArrowDownUp, ChevronDown } from 'lucide-react';
import { ArrowDownUp, ChevronDown, Settings } from 'lucide-react';
import { useAccount } from 'wagmi';
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
import { useSwapAmounts, useSwap, selectBestSwapRoute } from '@/hooks/usePartyPool';
import { formatUnits, parseUnits } from 'viem';
import { SwapReviewModal } from './swap-review-modal';
import { useToast } from '@/components/ui/toast';
export function SwapForm() {
const { t } = useTranslation();
const { isConnected, address } = useAccount();
const { addToast, updateToast } = useToast();
const [fromAmount, setFromAmount] = useState('');
const [toAmount, setToAmount] = useState('');
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
const [selectedToToken, setSelectedToToken] = useState<TokenDetails | null>(null);
const [isFromDropdownOpen, setIsFromDropdownOpen] = useState(false);
const [isToDropdownOpen, setIsToDropdownOpen] = useState(false);
const [slippage, setSlippage] = useState<number>(0.5); // Default 0.5%
const [customSlippage, setCustomSlippage] = useState<string>('');
const [isCustomSlippage, setIsCustomSlippage] = useState(false);
const [maxSlippage, setMaxSlippage] = useState<string>('5.5'); // Default 5.5%
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
const fromDropdownRef = useRef<HTMLDivElement>(null);
const toDropdownRef = useRef<HTMLDivElement>(null);
@@ -45,8 +46,8 @@ export function SwapForm() {
return null;
}, [selectedFromToken, selectedToToken, availableTokens]);
// Get the current slippage value (either custom or preset)
const currentSlippage = isCustomSlippage ? parseFloat(customSlippage) || 0.5 : slippage;
// Get the current slippage value
const currentSlippage = parseFloat(maxSlippage) || 5.5;
// Calculate swap amounts for the selected token pair only
const { swapAmounts } = useSwapAmounts(
@@ -94,12 +95,24 @@ export function SwapForm() {
return;
}
// Show swapping toast
const toastId = addToast({
title: 'Swapping',
description: `${fromAmount} ${selectedFromToken.symbol}${toAmount} ${selectedToToken.symbol}`,
type: 'loading',
});
try {
// Use the shared helper to select the best swap route
const bestRoute = selectBestSwapRoute(swapAmounts);
if (!bestRoute) {
console.error('No valid swap route found');
updateToast(toastId, {
title: 'Swap Failed',
description: 'No valid swap route found',
type: 'error',
});
return;
}
@@ -115,8 +128,26 @@ export function SwapForm() {
maxAmountIn,
currentSlippage
);
// Update toast to success
updateToast(toastId, {
title: 'Swap Confirmed',
description: `Successfully swapped ${fromAmount} ${selectedFromToken.symbol} to ${toAmount} ${selectedToToken.symbol}`,
type: 'success',
});
// Clear the form after successful swap
setFromAmount('');
setToAmount('');
setSelectedFromToken(null);
setSelectedToToken(null);
} catch (err) {
console.error('Swap failed:', err);
updateToast(toastId, {
title: 'Swap Failed',
description: err instanceof Error ? err.message : 'Transaction failed',
type: 'error',
});
}
};
@@ -143,7 +174,17 @@ export function SwapForm() {
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>{t('swap.title')}</CardTitle>
<div className="flex justify-between items-center">
<CardTitle>{t('swap.title')}</CardTitle>
<Button
variant="ghost"
size="icon"
onClick={() => setIsSettingsOpen(true)}
className="h-8 w-8"
>
<Settings className="h-5 w-5" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* From Token */}
@@ -281,66 +322,27 @@ export function SwapForm() {
</div>
</div>
{/* Slippage Tolerance */}
<div className="space-y-3 pt-2">
<div className="flex justify-between items-center">
<label className="text-sm font-medium">Slippage Tolerance</label>
<span className="text-sm text-muted-foreground">
{isCustomSlippage ? customSlippage || '0' : slippage}%
</span>
</div>
<div className="flex gap-2 flex-wrap">
{[0.1, 0.2, 0.3, 1, 2, 3].map((percent) => (
<Button
key={percent}
variant={!isCustomSlippage && slippage === percent ? 'default' : 'outline'}
size="sm"
onClick={() => {
setSlippage(percent);
setIsCustomSlippage(false);
}}
className="flex-1 min-w-[60px]"
>
{percent}%
</Button>
))}
<div className="flex-1 min-w-[80px] relative">
<Input
type="number"
placeholder="Custom"
value={customSlippage}
onChange={(e) => {
setCustomSlippage(e.target.value);
setIsCustomSlippage(true);
}}
onFocus={() => setIsCustomSlippage(true)}
className={`h-9 pr-6 ${isCustomSlippage ? 'border-primary' : ''}`}
step="0.01"
/>
{isCustomSlippage && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
%
{/* Gas Estimate, Slippage, and Fees */}
{isConnected && fromAmount && toAmount && (
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Estimated Gas Cost:</span>
<span className="font-medium">
{isEstimatingGas ? 'Calculating...' : gasEstimate ? `$${gasEstimate.estimatedCostUsd}` : '-'}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Max Slippage:</span>
<span className="font-medium">{maxSlippage}%</span>
</div>
{swapAmounts && swapAmounts.length > 0 && selectedToToken && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Fee:</span>
<span className="font-medium">
{(Number(swapAmounts[0].fee) / Math.pow(10, selectedToToken.decimals)).toFixed(6)} {selectedToToken.symbol}
</span>
)}
</div>
</div>
</div>
{/* Gas Estimate */}
{isConnected && fromAmount && toAmount && gasEstimate && !isEstimatingGas && (
<div className="px-4 py-2 bg-muted/30 rounded-lg">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Estimated Gas Cost:</span>
<span className="font-medium">${gasEstimate.estimatedCostUsd}</span>
</div>
</div>
)}
{isConnected && fromAmount && toAmount && isEstimatingGas && (
<div className="px-4 py-2 bg-muted/30 rounded-lg">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Estimated Gas Cost:</span>
<span className="text-muted-foreground">Calculating...</span>
</div>
</div>
)}
</div>
)}
@@ -356,6 +358,54 @@ export function SwapForm() {
</Button>
</CardContent>
{/* Settings Modal */}
{isSettingsOpen && (
<>
<div
className="fixed inset-0 z-50 bg-black/80 animate-in fade-in-0"
onClick={() => setIsSettingsOpen(false)}
/>
<div className="fixed left-[50%] top-[50%] z-50 w-full max-w-sm translate-x-[-50%] translate-y-[-50%] animate-in fade-in-0 zoom-in-95">
<div className="bg-background border rounded-lg shadow-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold">Settings</h2>
<button
onClick={() => setIsSettingsOpen(false)}
className="rounded-sm opacity-70 hover:opacity-100 transition-opacity"
>
<ChevronDown className="h-4 w-4" />
</button>
</div>
<div className="space-y-4">
<div>
<label className="text-sm font-medium block mb-2">
Max Slippage
</label>
<div className="relative">
<Input
type="number"
value={maxSlippage}
onChange={(e) => setMaxSlippage(e.target.value)}
className="pr-8"
step="0.1"
min="0"
max="100"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground">
%
</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
Your transaction will revert if the price changes unfavorably by more than this percentage.
</p>
</div>
</div>
</div>
</div>
</>
)}
{/* Review Modal */}
<SwapReviewModal
open={isReviewModalOpen}
@@ -366,6 +416,7 @@ export function SwapForm() {
toAmount={toAmount}
slippage={currentSlippage}
gasEstimate={gasEstimate}
fee={swapAmounts && swapAmounts.length > 0 ? swapAmounts[0].fee : null}
onConfirm={async () => {
await handleSwap();
setIsReviewModalOpen(false);