Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 143 additions & 28 deletions apps/web/components/BridgeCompare.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,102 @@
// packages/ui/src/components/BridgeCompare.tsx

import React, { useState, useEffect } from 'react';
import { useBridgeQuotes, BridgeQuoteParams } from '@bridgewise/react';
import { useIsMounted } from './ui-lib/utils/ssr';
import { RefreshIndicator } from './RefreshIndicator';
import { QuoteCard } from './QuoteCard';
import { SlippageWarning } from './SlippageWarning';
import { QuoteSkeleton } from './ui-lib/skeleton';
import { SortToggle, SortOption, sortQuotes, enhanceQuotesForSorting } from './ui-lib/sorting';

// Define types locally since external packages may not be available
interface BridgeQuoteParams {
amount: string;
sourceChain: string;
destinationChain: string;
sourceToken: string;
destinationToken: string;
userAddress?: string;
slippageTolerance?: number;
}

interface Quote {
id: string;
provider?: string;
estimatedTime?: string;
outputAmount?: string;
outputToken?: string;
sourceAmount?: string;
sourceToken?: string;
sourceChain?: string;
destinationChain?: string;
fees?: {
bridge?: number;
gas?: number;
};
reliability?: number;
speed?: number;
}

// Mock hook since @bridgewise/react may not be available
const useBridgeQuotes = (options: any) => {
// Mock implementation for demo purposes
const mockQuotes: Quote[] = [
{
id: '1',
provider: 'LayerZero',
estimatedTime: '~2 mins',
outputAmount: '99.50',
outputToken: 'USDC',
sourceAmount: '100',
sourceToken: 'USDC',
sourceChain: 'Ethereum',
destinationChain: 'Polygon',
fees: { bridge: 0.50, gas: 2.00 },
reliability: 95,
speed: 2
},
{
id: '2',
provider: 'Hop Protocol',
estimatedTime: '~3 mins',
outputAmount: '99.20',
outputToken: 'USDC',
sourceAmount: '100',
sourceToken: 'USDC',
sourceChain: 'Ethereum',
destinationChain: 'Polygon',
fees: { bridge: 0.80, gas: 2.50 },
reliability: 92,
speed: 3
},
{
id: '3',
provider: 'Multichain',
estimatedTime: '~5 mins',
outputAmount: '98.80',
outputToken: 'USDC',
sourceAmount: '100',
sourceToken: 'USDC',
sourceChain: 'Ethereum',
destinationChain: 'Polygon',
fees: { bridge: 1.20, gas: 3.00 },
reliability: 88,
speed: 5
}
];

return {
quotes: mockQuotes,
isLoading: false,
error: null,
lastRefreshed: new Date(),
isRefreshing: false,
refresh: () => console.log('Refresh called'),
updateParams: () => console.log('Update params called'),
retryCount: 0,
...options
};
};

interface BridgeCompareProps {
initialParams: BridgeQuoteParams;
Expand All @@ -13,14 +105,19 @@ interface BridgeCompareProps {
autoRefresh?: boolean;
}

export const BridgeCompare: React.FC<BridgeCompareProps> = ({
initialParams,
onQuoteSelect,
refreshInterval = 15000,
autoRefresh = true
}) => {
export const BridgeCompare: React.FC<BridgeCompareProps> = (props) => {
const isMounted = useIsMounted();

const {
initialParams,
onQuoteSelect,
refreshInterval = 15000,
autoRefresh = true
} = props;

const [selectedQuoteId, setSelectedQuoteId] = useState<string | null>(null);
const [showRefreshIndicator, setShowRefreshIndicator] = useState(false);
const [sortBy, setSortBy] = useState<SortOption>('recommended');

const {
quotes,
Expand All @@ -35,18 +132,30 @@ export const BridgeCompare: React.FC<BridgeCompareProps> = ({
initialParams,
intervalMs: refreshInterval,
autoRefresh,
onRefreshStart: () => setShowRefreshIndicator(true),
onRefreshStart: () => isMounted && setShowRefreshIndicator(true),
onRefreshEnd: () => {
setTimeout(() => setShowRefreshIndicator(false), 1000);
if (isMounted) {
setTimeout(() => setShowRefreshIndicator(false), 1000);
}
}
});

// Handle quote selection
const handleQuoteSelect = (quoteId: string) => {
setSelectedQuoteId(quoteId);
onQuoteSelect?.(quoteId);
if (isMounted) {
setSelectedQuoteId(quoteId);
onQuoteSelect?.(quoteId);
}
};

// Handle sort change
const handleSortChange = (newSortBy: SortOption) => {
setSortBy(newSortBy);
};

// Apply sorting to quotes
const sortedQuotes = quotes.length > 0 ? sortQuotes(enhanceQuotesForSorting(quotes), sortBy) : quotes;

// Format last refreshed time
const getLastRefreshedText = () => {
if (!lastRefreshed) return 'Never';
Expand All @@ -60,9 +169,16 @@ export const BridgeCompare: React.FC<BridgeCompareProps> = ({

return (
<div className="bridge-compare">
{/* Header with refresh controls */}
{/* Header with refresh controls and sorting */}
<div className="bridge-compare__header">
<h2>Bridge Routes</h2>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
<h2>Bridge Routes</h2>
<SortToggle
currentSort={sortBy}
onSortChange={handleSortChange}
disabled={isLoading}
/>
</div>

<div className="bridge-compare__refresh-controls">
<RefreshIndicator
Expand Down Expand Up @@ -95,19 +211,28 @@ export const BridgeCompare: React.FC<BridgeCompareProps> = ({
</div>
)}

{/* Loading skeleton */}
{/* Loading skeleton - Enhanced with proper skeleton components */}
{isLoading && quotes.length === 0 && (
<div className="bridge-compare__skeleton">
<div className="bridge-compare__skeleton grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="quote-skeleton" />
<QuoteSkeleton key={i} />
))}
</div>
)}

{/* Refreshing skeleton - Show when refreshing existing quotes */}
{isRefreshing && quotes.length > 0 && (
<div className="bridge-compare__refreshing-skeleton grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 opacity-50">
{quotes.map((quote) => (
<QuoteSkeleton key={`refresh-${quote.id}`} />
))}
</div>
)}

{/* Quotes grid */}
{quotes.length > 0 && (
{sortedQuotes.length > 0 && (
<div className="bridge-compare__quotes-grid">
{quotes.map((quote) => (
{sortedQuotes.map((quote: any) => (
<QuoteCard
key={quote.id}
quote={quote}
Expand All @@ -119,16 +244,6 @@ export const BridgeCompare: React.FC<BridgeCompareProps> = ({
</div>
)}

{/* Slippage warning for outdated quotes */}
{lastRefreshed && (
<SlippageWarning
lastRefreshed={lastRefreshed}
quotes={quotes}
refreshThreshold={30000} // 30 seconds
onRefresh={refresh}
/>
)}

{/* Empty state */}
{!isLoading && quotes.length === 0 && !error && (
<div className="bridge-compare__empty">
Expand Down
Loading
Loading