diff --git a/app/admin/stats/components/AssetMetricsTable.tsx b/app/admin/stats/components/AssetMetricsTable.tsx
index 1ddd279f..0225ed05 100644
--- a/app/admin/stats/components/AssetMetricsTable.tsx
+++ b/app/admin/stats/components/AssetMetricsTable.tsx
@@ -1,5 +1,6 @@
import { useState, useMemo } from 'react';
import { FiChevronUp, FiChevronDown } from 'react-icons/fi';
+import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
import { TokenIcon } from '@/components/TokenIcon';
import { formatReadable } from '@/utils/balance';
import { calculateHumanReadableVolumes } from '@/utils/statsDataProcessing';
@@ -25,7 +26,7 @@ type SortableHeaderProps = {
function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, onSort }: SortableHeaderProps) {
return (
-
onSort(sortKeyValue)}
style={{ padding: '0.5rem' }}
@@ -35,7 +36,7 @@ function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, on
{currentSortKey === sortKeyValue &&
(sortDirection === 'asc' ? : )}
-
+
);
}
@@ -92,10 +93,10 @@ export function AssetMetricsTable({ data }: AssetMetricsTableProps) {
{processedData.length === 0 ? (
No asset data available
) : (
-
-
-
- Asset
+
+
+
+ Asset
-
-
-
+
+
+
{sortedData.map((asset) => {
// Use a determined chainId for display purposes
const displayChainId = asset.chainId ?? BASE_CHAIN_ID;
return (
-
-
{asset.assetSymbol ?? 'Unknown'}
-
-
+
{asset.totalVolumeFormatted ? `${formatReadable(Number(asset.totalVolumeFormatted))} ${asset.assetSymbol}` : '—'}
-
-
+
{(asset.supplyCount + asset.withdrawCount).toLocaleString()}
-
-
+
{asset.supplyCount.toLocaleString()}
-
-
+
{asset.withdrawCount.toLocaleString()}
-
-
+
{asset.uniqueUsers.toLocaleString()}
-
-
+
+
);
})}
-
-
+
+
)}
diff --git a/app/admin/stats/components/TransactionTableBody.tsx b/app/admin/stats/components/TransactionTableBody.tsx
index 01fb35ca..63ee7be9 100644
--- a/app/admin/stats/components/TransactionTableBody.tsx
+++ b/app/admin/stats/components/TransactionTableBody.tsx
@@ -1,5 +1,6 @@
import Link from 'next/link';
import { formatUnits } from 'viem';
+import { TableBody, TableRow, TableCell } from '@/components/ui/table';
import { AccountIdentity } from '@/components/common/AccountIdentity';
import { TransactionIdentity } from '@/components/common/TransactionIdentity';
import { MarketIdBadge } from '@/components/MarketIdBadge';
@@ -66,17 +67,17 @@ const formatAmount = (amount: string, side: 'Supply' | 'Withdraw', loanAddress:
export function TransactionTableBody({ operations, selectedNetwork }: TransactionTableBodyProps) {
return (
-
+
{operations.map((op) => {
const marketPath = op.market ? `/market/${selectedNetwork}/${op.market.uniqueKey}` : null;
return (
-
{/* User Address */}
-
-
+
{/* Loan Asset */}
-
{getTruncatedAssetName(op.loanSymbol)}
-
+
{/* Market */}
- —
)}
-
+
{/* Side */}
-
{op.side}
-
+
{/* Amount */}
-
{formatAmount(op.amount, op.side, op.loanAddress, selectedNetwork)}
-
+
{/* Transaction Hash */}
-
-
+
{/* Time */}
-
{formatTimeAgo(op.timestamp)}
-
-
+
+
);
})}
-
+
);
}
diff --git a/app/admin/stats/components/TransactionsTable.tsx b/app/admin/stats/components/TransactionsTable.tsx
index dfa3bc2c..8af052d9 100644
--- a/app/admin/stats/components/TransactionsTable.tsx
+++ b/app/admin/stats/components/TransactionsTable.tsx
@@ -1,5 +1,6 @@
import { useState, useMemo } from 'react';
import { FiChevronUp, FiChevronDown } from 'react-icons/fi';
+import { Table, TableHeader, TableRow, TableHead } from '@/components/ui/table';
import { TablePagination } from '@/components/common/TablePagination';
import type { SupportedNetworks } from '@/utils/networks';
import type { Transaction } from '@/utils/statsUtils';
@@ -41,7 +42,7 @@ type SortableHeaderProps = {
function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, onSort }: SortableHeaderProps) {
return (
- onSort(sortKeyValue)}
style={{ padding: '0.5rem' }}
@@ -51,7 +52,7 @@ function SortableHeader({ label, sortKeyValue, currentSortKey, sortDirection, on
{currentSortKey === sortKeyValue &&
(sortDirection === 'asc' ? : )}
-
+
);
}
@@ -196,13 +197,13 @@ export function TransactionsTable({
No transaction data available
) : (
<>
-
-
-
- User
- Loan Asset
- Market
- Side
+
+
+
+ User
+ Loan Asset
+ Market
+ Side
- Tx Hash
+ Tx Hash
-
-
+
+
-
+
-
-
-
- Collateral
- Amount
- Allocation
-
-
-
-
+
+
+
+ Collateral
+ Amount
+ Allocation
+
+
+
+
{sortedItems.map((item) => {
const percentage = totalAllocation > 0n ? Number.parseFloat(calculateAllocationPercent(item.allocation, totalAllocation)) : 0;
const hasAllocation = item.allocation > 0n;
return (
-
-
+
{item.collateralSymbol}
-
-
+
+
{hasAllocation ? `${formatAllocationAmount(item.allocation, vaultAssetDecimals)} ${vaultAssetSymbol}` : '-'}
-
-
+
+
{hasAllocation ? `${percentage.toFixed(2)}%` : '-'}
-
-
+
+
-
-
+
+
);
})}
-
-
+
+
);
}
diff --git a/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx b/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx
index 8dad6722..3cf86c33 100644
--- a/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx
+++ b/app/autovault/[chainId]/[vaultAddress]/components/allocations/MarketView.tsx
@@ -1,3 +1,4 @@
+import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
import { MarketIdentity, MarketIdentityFocus } from '@/components/MarketIdentity';
import { useMarkets } from '@/hooks/useMarkets';
import { useRateLabel } from '@/hooks/useRateLabel';
@@ -29,19 +30,19 @@ export function MarketView({ allocations, totalAllocation, vaultAssetSymbol, vau
return (
-
-
-
- Market
- {rateLabel}
- Total Supply
- Liquidity
- Amount
- Allocation
-
-
-
-
+
+
+
+ Market
+ {rateLabel}
+ Total Supply
+ Liquidity
+ Amount
+ Allocation
+
+
+
+
{sortedItems.map((item) => {
const { market, allocation } = item;
const percentage = totalAllocation > 0n ? Number.parseFloat(calculateAllocationPercent(allocation, totalAllocation)) : 0;
@@ -54,12 +55,12 @@ export function MarketView({ allocations, totalAllocation, vaultAssetSymbol, vau
);
return (
-
{/* Market Info Column */}
-
+
-
+
{/* APY/APR */}
- {supplyRate}%
+ {supplyRate}%
{/* Total Supply */}
- {totalSupply}
+ {totalSupply}
{/* Liquidity */}
- {liquidity}
+ {liquidity}
{/* Allocation Amount */}
-
+
{hasAllocation ? `${formatAllocationAmount(allocation, vaultAssetDecimals)} ${vaultAssetSymbol}` : '-'}
-
+
{/* Allocation Percentage */}
-
+
{hasAllocation ? `${percentage.toFixed(2)}%` : '-'}
-
+
{/* Pie Chart */}
-
+
-
-
+
+
);
})}
-
-
+
+
);
}
diff --git a/app/autovault/components/VaultListV2.tsx b/app/autovault/components/VaultListV2.tsx
index 9bb11fa8..f615a745 100644
--- a/app/autovault/components/VaultListV2.tsx
+++ b/app/autovault/components/VaultListV2.tsx
@@ -1,6 +1,7 @@
import Image from 'next/image';
import Link from 'next/link';
import { formatUnits } from 'viem';
+import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/common/Spinner';
import { useTokens } from '@/components/providers/TokenProvider';
@@ -51,17 +52,17 @@ export function VaultListV2({ vaults, loading }: VaultListV2Props) {
Your Vaults
-
-
-
- ID
- Asset
- {rateLabel}
- Collaterals
- Action
-
-
-
+
+
+
+ ID
+ Asset
+ {rateLabel}
+ Collaterals
+ Action
+
+
+
{vaults.map((vault) => {
const token = findToken(vault.asset, vault.networkId);
const networkImg = getNetworkImg(vault.networkId);
@@ -71,9 +72,9 @@ export function VaultListV2({ vaults, loading }: VaultListV2Props) {
.filter((collat) => collat !== undefined);
return (
-
+
{/* ID */}
-
+
{networkImg && (
{vault.address.slice(2, 8)}
-
+
{/* Asset */}
-
+
{vault.balance && token ? formatReadable(formatUnits(BigInt(vault.balance), token.decimals)) : '0'}
@@ -101,17 +102,17 @@ export function VaultListV2({ vaults, loading }: VaultListV2Props) {
height={16}
/>
-
+
{/* APY/APR */}
-
+
{vault.avgApy != null ? `${((isAprDisplay ? convertApyToApr(vault.avgApy) : vault.avgApy) * 100).toFixed(2)}%` : '—'}
-
+
{/* Collaterals */}
-
+
{collaterals.map((tokenAddress) => (
))}
-
+
{/* Action */}
-
+
-
-
+
+
);
})}
-
-
+
+
);
diff --git a/app/global.css b/app/global.css
index 44053dee..a6971053 100644
--- a/app/global.css
+++ b/app/global.css
@@ -303,35 +303,35 @@ h1 {
padding-bottom: 0.5rem;
}
-.table-header {
+/* Table styles - applied automatically to thead and tbody */
+thead {
background-color: var(--color-background-secondary);
font-size: 0.8em;
color: grey;
}
-.table-header th {
- padding-top: 1rem;
- padding-bottom: 1rem;
- text-align: center;
+thead th {
+ padding: 1rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
}
-.table-body {
+tbody {
background-color: var(--color-background-secondary);
border-left: 2px solid var(--color-background-secondary);
}
-.table-body td {
+tbody td {
padding: 1rem;
padding-left: 1rem;
padding-right: 1rem;
- text-align: center;
}
-.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr) {
+tbody tr:not(.no-hover-effect tr) {
border-left: 2px solid transparent;
}
-.table-body tr:not(.no-hover-effect tr, .no-hover-effect tr):hover {
+tbody tr:not(.no-hover-effect tr):hover {
background-color: var(--palette-bg-hovered);
border-left: 2px solid var(--palette-orange);
}
@@ -341,6 +341,13 @@ h1 {
border-left: 2px solid var(--palette-orange) !important;
}
+/* Compact table variant - less padding for simpler tables */
+.table-body-compact td {
+ padding: 0.625rem;
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
svg {
display: block;
overflow: visible;
diff --git a/app/history/components/HistoryTable.tsx b/app/history/components/HistoryTable.tsx
index 666b6ed6..8a3c3b6e 100644
--- a/app/history/components/HistoryTable.tsx
+++ b/app/history/components/HistoryTable.tsx
@@ -1,12 +1,14 @@
import type React from 'react';
import { useMemo, useState, useRef, useEffect } from 'react';
-import { Chip, Link, Pagination, Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react';
+import { Chip, Link } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import { ChevronDownIcon, TrashIcon } from '@radix-ui/react-icons';
import moment from 'moment';
import Image from 'next/image';
import { RiRobot2Line } from 'react-icons/ri';
import { formatUnits } from 'viem';
import { Badge } from '@/components/common/Badge';
+import { TablePagination } from '@/components/common/TablePagination';
import { TransactionIdentity } from '@/components/common/TransactionIdentity';
import LoadingScreen from '@/components/Status/LoadingScreen';
import { TokenIcon } from '@/components/TokenIcon';
@@ -252,146 +254,152 @@ export function HistoryTable({ account, positions, rebalancerInfos }: HistoryTab
{!isInitialized || loading ? (
) : (
- 1 ? (
-
- ) : null
- }
- >
-
- Asset & Network
- Market Details
- Action & Amount
- Time
- Transaction
-
-
- {history
- .filter((tx) => tx.data.market !== undefined)
- .map((tx, index) => {
- // safely cast here because we only fetch txs for unique id in "markets"
- const market = allMarkets.find((m) => m.uniqueKey === tx.data.market.uniqueKey) as Market;
+ <>
+
+
+
+ Asset & Network
+ Market Details
+ Action & Amount
+ Time
+ Transaction
+
+
+
+ {history.filter((tx) => tx.data.market !== undefined).length === 0 ? (
+
+
+ No transactions found
+
+
+ ) : (
+ history
+ .filter((tx) => tx.data.market !== undefined)
+ .map((tx, index) => {
+ // safely cast here because we only fetch txs for unique id in "markets"
+ const market = allMarkets.find((m) => m.uniqueKey === tx.data.market.uniqueKey) as Market;
- const networkImg = getNetworkImg(market.morphoBlue.chain.id);
- const networkName = getNetworkName(market.morphoBlue.chain.id);
- const sign = tx.type === UserTxTypes.MarketSupply ? '+' : '-';
- const lltv = Number(formatUnits(BigInt(market.lltv), 18)) * 100;
+ const networkImg = getNetworkImg(market.morphoBlue.chain.id);
+ const networkName = getNetworkName(market.morphoBlue.chain.id);
+ const sign = tx.type === UserTxTypes.MarketSupply ? '+' : '-';
+ const lltv = Number(formatUnits(BigInt(market.lltv), 18)) * 100;
- // Find the rebalancer info for the specific network of the transaction
- const networkRebalancerInfo = rebalancerInfos.find((info) => info.network === market.morphoBlue.chain.id);
- // Check if the transaction hash exists in the transactions of the found rebalancer info
- const isAgent = networkRebalancerInfo?.transactions.some((agentTx) => agentTx.transactionHash === tx.hash);
+ // Find the rebalancer info for the specific network of the transaction
+ const networkRebalancerInfo = rebalancerInfos.find((info) => info.network === market.morphoBlue.chain.id);
+ // Check if the transaction hash exists in the transactions of the found rebalancer info
+ const isAgent = networkRebalancerInfo?.transactions.some((agentTx) => agentTx.transactionHash === tx.hash);
- return (
-
- {/* Network & Asset */}
-
-
-
-
- {market.loanAsset.symbol}
-
-
- {networkImg && (
-
- )}
- {networkName}
-
-
-
+ return (
+
+ {/* Network & Asset */}
+
+
+
+
+ {market.loanAsset.symbol}
+
+
+ {networkImg && (
+
+ )}
+ {networkName}
+
+
+
- {/* Market Details */}
-
-
-
- {market.uniqueKey.slice(2, 8)}
-
-
-
- {market.collateralAsset.symbol}
-
-
- {formatReadable(lltv)}%
-
-
-
+ {/* Market Details */}
+
+
+
+ {market.uniqueKey.slice(2, 8)}
+
+
+
+ {market.collateralAsset.symbol}
+
+
+ {formatReadable(lltv)}%
+
+
+
- {/* Action & Amount */}
-
-
- {actionTypeToText(tx.type)}
-
- {sign}
- {formatReadable(Number(formatUnits(BigInt(tx.data.assets), market.loanAsset.decimals)))} {market.loanAsset.symbol}
-
- {isAgent && (
-
-
-
- )}
-
-
+ {/* Action & Amount */}
+
+
+ {actionTypeToText(tx.type)}
+
+ {sign}
+ {formatReadable(Number(formatUnits(BigInt(tx.data.assets), market.loanAsset.decimals)))}{' '}
+ {market.loanAsset.symbol}
+
+ {isAgent && (
+
+
+
+ )}
+
+
- {/* Time */}
-
- {moment.unix(tx.timestamp).fromNow()}
-
+ {/* Time */}
+
+ {moment.unix(tx.timestamp).fromNow()}
+
- {/* Transaction */}
-
-
-
-
-
-
- );
- })}
-
-
+ {/* Transaction */}
+
+
+
+
+
+
+ );
+ })
+ )}
+
+
+ {totalPages > 1 && (
+
+ )}
+ >
)}
);
diff --git a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx
index f536d884..d170542c 100644
--- a/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx
+++ b/app/market/[chainId]/[marketid]/components/BorrowersTable.tsx
@@ -1,5 +1,6 @@
import { useState, useMemo } from 'react';
-import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react';
+import { Tooltip } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import { FiFilter } from 'react-icons/fi';
import type { Address } from 'viem';
import { formatUnits } from 'viem';
@@ -108,76 +109,83 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen
)}
-
-
- ACCOUNT
- BORROWED
- COLLATERAL
- LTV
- % OF BORROW
-
-
+
- {borrowersWithLTV.map((borrower) => {
- const totalBorrow = BigInt(market.state.borrowAssets);
- const borrowerAssets = BigInt(borrower.borrowAssets);
- const percentOfBorrow = totalBorrow > 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0;
- const percentDisplay = percentOfBorrow < 0.01 && percentOfBorrow > 0 ? '<0.01%' : `${percentOfBorrow.toFixed(2)}%`;
-
- return (
-
-
-
+
+
+ ACCOUNT
+ BORROWED
+ COLLATERAL
+ LTV
+ % OF BORROW
+
+
+
+ {borrowersWithLTV.length === 0 && !isLoading ? (
+
+
+ No borrowers found for this market
-
- {formatSimple(Number(formatUnits(BigInt(borrower.borrowAssets), market.loanAsset.decimals)))}
- {market?.loanAsset?.symbol && (
-
-
-
- )}
-
-
- {formatSimple(Number(formatUnits(BigInt(borrower.collateral), market.collateralAsset.decimals)))}
- {market?.collateralAsset?.symbol && (
-
-
-
- )}
-
- {borrower.ltv.toFixed(2)}%
- {percentDisplay}
- );
- })}
-
-
+ ) : (
+ borrowersWithLTV.map((borrower) => {
+ const totalBorrow = BigInt(market.state.borrowAssets);
+ const borrowerAssets = BigInt(borrower.borrowAssets);
+ const percentOfBorrow = totalBorrow > 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0;
+ const percentDisplay = percentOfBorrow < 0.01 && percentOfBorrow > 0 ? '<0.01%' : `${percentOfBorrow.toFixed(2)}%`;
+
+ return (
+
+
+
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(borrower.borrowAssets), market.loanAsset.decimals)))}
+ {market?.loanAsset?.symbol && (
+
+ )}
+
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(borrower.collateral), market.collateralAsset.decimals)))}
+ {market?.collateralAsset?.symbol && (
+
+ )}
+
+
+ {borrower.ltv.toFixed(2)}%
+ {percentDisplay}
+
+ );
+ })
+ )}
+
+
+
{totalCount > 0 && (
diff --git a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx
index 54f758e7..6ea60051 100644
--- a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx
+++ b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
-import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react';
+import { Tooltip } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import moment from 'moment';
import { FiFilter } from 'react-icons/fi';
import type { Address } from 'viem';
@@ -92,70 +93,72 @@ export function BorrowsTable({ chainId, market, minAssets, onOpenFiltersModal }:
)}
-
-
- ACCOUNT
- TYPE
- AMOUNT
- TIME
-
- TRANSACTION
-
-
-
+
- {borrows.map((borrow) => (
-
-
-
-
-
-
- {borrow.type === 'MarketBorrow' ? 'Borrow' : 'Repay'}
-
-
-
- {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))}
- {market?.loanAsset?.symbol && (
-
-
-
- )}
-
- {moment.unix(borrow.timestamp).fromNow()}
-
-
-
+
+
+ ACCOUNT
+ TYPE
+ AMOUNT
+ TIME
+ TRANSACTION
- ))}
-
-
+
+
+ {borrows.length === 0 && !isLoading ? (
+
+
+ No borrow activities found for this market
+
+
+ ) : (
+ borrows.map((borrow) => (
+
+
+
+
+
+
+ {borrow.type === 'MarketBorrow' ? 'Borrow' : 'Repay'}
+
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(borrow.amount), market.loanAsset.decimals)))}
+ {market?.loanAsset?.symbol && (
+
+ )}
+
+
+ {moment.unix(borrow.timestamp).fromNow()}
+
+
+
+
+ ))
+ )}
+
+
+
{totalCount > 0 && (
diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx
index 9b9cf4b1..ca43bdc9 100644
--- a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx
+++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react';
-import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import moment from 'moment';
import { type Address, formatUnits } from 'viem';
import { AccountIdentity } from '@/components/common/AccountIdentity';
@@ -8,6 +8,7 @@ import { TablePagination } from '@/components/common/TablePagination';
import { TransactionIdentity } from '@/components/common/TransactionIdentity';
import { TokenIcon } from '@/components/TokenIcon';
import { useMarketLiquidations } from '@/hooks/useMarketLiquidations';
+import { formatSimple } from '@/utils/balance';
import type { Market, MarketLiquidationTransaction } from '@/utils/types';
type LiquidationsTableProps = {
@@ -51,83 +52,53 @@ export function LiquidationsTable({ chainId, market }: LiquidationsTableProps) {
)}
-
-
- LIQUIDATOR
- REPAID ({market?.loanAsset?.symbol ?? 'Loan'})
- SEIZED ({market?.collateralAsset?.symbol ?? 'Collateral'})
- BAD DEBT ({market?.loanAsset?.symbol ?? 'Loan'})
- TIME
-
- TRANSACTION
-
-
-
+
- {paginatedLiquidations.map((liquidation: MarketLiquidationTransaction) => {
- const hasBadDebt = BigInt(liquidation.badDebtAssets) !== BigInt(0);
- const isLiquidatorAddress = liquidation.liquidator?.startsWith('0x');
-
- return (
-
-
- {isLiquidatorAddress ? (
-
- ) : (
- {liquidation.liquidator}
- )}
-
-
- {formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)}
- {market?.loanAsset?.symbol && (
-
-
-
- )}
-
-
- {formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)}
- {market?.collateralAsset?.symbol && (
-
-
-
- )}
+
+
+ LIQUIDATOR
+ REPAID ({market?.loanAsset?.symbol ?? 'Loan'})
+ SEIZED ({market?.collateralAsset?.symbol ?? 'Collateral'})
+ BAD DEBT ({market?.loanAsset?.symbol ?? 'Loan'})
+ TIME
+ TRANSACTION
+
+
+
+ {paginatedLiquidations.length === 0 && !isLoading ? (
+
+
+ No liquidations found for this market
-
- {hasBadDebt ? (
- <>
- {formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)}
- {market?.loanAsset?.symbol && (
-
+
+ ) : (
+ paginatedLiquidations.map((liquidation: MarketLiquidationTransaction) => {
+ const hasBadDebt = BigInt(liquidation.badDebtAssets) !== BigInt(0);
+ const isLiquidatorAddress = liquidation.liquidator?.startsWith('0x');
+
+ return (
+
+
+ {isLiquidatorAddress ? (
+
+ ) : (
+ {liquidation.liquidator}
+ )}
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(liquidation.repaidAssets), market.loanAsset.decimals)))}
+ {market?.loanAsset?.symbol && (
+ )}
+
+
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(liquidation.seizedAssets), market.collateralAsset.decimals)))}
+ {market?.collateralAsset?.symbol && (
+
+ )}
+
+
+
+ {hasBadDebt ? (
+
+ {formatSimple(Number(formatUnits(BigInt(liquidation.badDebtAssets), market.loanAsset.decimals)))}
+ {market?.loanAsset?.symbol && (
+
+ )}
+
+ ) : (
+ -
)}
- >
- ) : (
- ' - '
- )}
-
- {moment.unix(liquidation.timestamp).fromNow()}
-
-
-
-
- );
- })}
-
-
+
+ {moment.unix(liquidation.timestamp).fromNow()}
+
+
+
+
+ );
+ })
+ )}
+
+
+
{totalCount > 0 && (
diff --git a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx
index d73af9a1..bb12d7ee 100644
--- a/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx
+++ b/app/market/[chainId]/[marketid]/components/SuppliersTable.tsx
@@ -1,5 +1,6 @@
import { useState, useMemo } from 'react';
-import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react';
+import { Tooltip } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import { FiFilter } from 'react-icons/fi';
import type { Address } from 'viem';
import { formatUnits } from 'viem';
@@ -101,59 +102,66 @@ export function SuppliersTable({ chainId, market, minShares, onOpenFiltersModal
)}
-
-
- ACCOUNT
- SUPPLIED
- % OF SUPPLY
-
-
+
- {suppliersWithAssets.map((supplier) => {
- const totalSupply = BigInt(market.state.supplyAssets);
- const supplierAssets = BigInt(supplier.supplyAssets);
- const percentOfSupply = totalSupply > 0n ? (Number(supplierAssets) / Number(totalSupply)) * 100 : 0;
- const percentDisplay = percentOfSupply < 0.01 && percentOfSupply > 0 ? '<0.01%' : `${percentOfSupply.toFixed(2)}%`;
-
- return (
-
-
-
+
+
+ ACCOUNT
+ SUPPLIED
+ % OF SUPPLY
+
+
+
+ {suppliersWithAssets.length === 0 && !isLoading ? (
+
+
+ No suppliers found for this market
-
- {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))}
- {market?.loanAsset?.symbol && (
-
-
-
- )}
-
- {percentDisplay}
- );
- })}
-
-
+ ) : (
+ suppliersWithAssets.map((supplier) => {
+ const totalSupply = BigInt(market.state.supplyAssets);
+ const supplierAssets = BigInt(supplier.supplyAssets);
+ const percentOfSupply = totalSupply > 0n ? (Number(supplierAssets) / Number(totalSupply)) * 100 : 0;
+ const percentDisplay = percentOfSupply < 0.01 && percentOfSupply > 0 ? '<0.01%' : `${percentOfSupply.toFixed(2)}%`;
+
+ return (
+
+
+
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(supplier.supplyAssets), market.loanAsset.decimals)))}
+ {market?.loanAsset?.symbol && (
+
+ )}
+
+
+ {percentDisplay}
+
+ );
+ })
+ )}
+
+
+
{totalCount > 0 && (
diff --git a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx
index 12a2acb1..2f8090a7 100644
--- a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx
+++ b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
-import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Tooltip } from '@heroui/react';
+import { Tooltip } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import moment from 'moment';
import { FiFilter } from 'react-icons/fi';
import type { Address } from 'viem';
@@ -87,70 +88,72 @@ export function SuppliesTable({ chainId, market, minAssets, onOpenFiltersModal }
)}
-
-
- ACCOUNT
- TYPE
- AMOUNT
- TIME
-
- TRANSACTION
-
-
-
+
- {supplies.map((supply) => (
-
-
-
-
-
-
- {supply.type === 'MarketSupply' ? 'Supply' : 'Withdraw'}
-
-
-
- {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))}
- {market?.loanAsset?.symbol && (
-
-
-
- )}
-
- {moment.unix(supply.timestamp).fromNow()}
-
-
-
+
+
+ ACCOUNT
+ TYPE
+ AMOUNT
+ TIME
+ TRANSACTION
- ))}
-
-
+
+
+ {supplies.length === 0 && !isLoading ? (
+
+
+ No supply activities found for this market
+
+
+ ) : (
+ supplies.map((supply) => (
+
+
+
+
+
+
+ {supply.type === 'MarketSupply' ? 'Supply' : 'Withdraw'}
+
+
+
+
+ {formatSimple(Number(formatUnits(BigInt(supply.amount), market.loanAsset.decimals)))}
+ {market?.loanAsset?.symbol && (
+
+ )}
+
+
+ {moment.unix(supply.timestamp).fromNow()}
+
+
+
+
+ ))
+ )}
+
+
+
{totalCount > 0 && (
diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx
index 573eb58a..ba7d7c4a 100644
--- a/app/markets/components/MarketTableBody.tsx
+++ b/app/markets/components/MarketTableBody.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { GoStarFill, GoStar } from 'react-icons/go';
+import { TableBody, TableRow, TableCell } from '@/components/ui/table';
import { RateFormatted } from '@/components/common/RateFormatted';
import { MarketIdBadge } from '@/components/MarketIdBadge';
import { MarketIndicators } from '@/components/MarketIndicators';
@@ -93,19 +94,19 @@ export function MarketTableBody({
};
return (
-
+
{currentEntries.map((item, index) => {
const collatToShow = item.collateralAsset.symbol.slice(0, 6).concat(item.collateralAsset.symbol.length > 6 ? '...' : '');
const isStared = staredIds.includes(item.uniqueKey);
return (
- setExpandedRowId(item.uniqueKey === expandedRowId ? null : item.uniqueKey)}
- className={`hover:cursor-pointer ${item.uniqueKey === expandedRowId ? 'table-body-focused ' : ''}'`}
+ className={`hover:cursor-pointer ${item.uniqueKey === expandedRowId ? 'table-body-focused ' : ''}`}
>
-
{isStared ? : }
-
-
+
-
+
-
-
-
+
{Number(item.lltv) / 1e16}%
-
+
{columnVisibility.trustedBy && (
-
-
+
)}
{columnVisibility.totalSupply && (
)}
{columnVisibility.supplyAPY && (
-
-
+
)}
{columnVisibility.borrowAPY && (
-
{item.state.borrowApy ? : '—'}
-
+
)}
{columnVisibility.rateAtTarget && (
-
{item.state.apyAtTarget ? : '—'}
-
+
)}
{columnVisibility.utilizationRate && (
-
{`${(item.state.utilization * 100).toFixed(2)}%`}
-
+
)}
-
+
-
-
+
-
-
+
@@ -280,12 +281,12 @@ export function MarketTableBody({
isBlacklisted={isBlacklisted}
/>
-
-
+
+
{expandedRowId === item.uniqueKey && (
-
-
+
@@ -301,13 +302,13 @@ export function MarketTableBody({
-
-
+
+
)}
);
})}
-
+
);
}
diff --git a/app/markets/components/MarketTableUtils.tsx b/app/markets/components/MarketTableUtils.tsx
index 13de6127..7df7f300 100644
--- a/app/markets/components/MarketTableUtils.tsx
+++ b/app/markets/components/MarketTableUtils.tsx
@@ -1,4 +1,5 @@
import { ArrowDownIcon, ArrowUpIcon, ExternalLinkIcon } from '@radix-ui/react-icons';
+import { TableHead, TableCell } from '@/components/ui/table';
import { TokenIcon } from '@/components/TokenIcon';
import { formatBalance, formatReadable } from '@/utils/balance';
import { getAssetURL } from '@/utils/external';
@@ -17,7 +18,7 @@ export function HTSortable({ label, sortColumn, titleOnclick, sortDirection, tar
const sortingCurrent = sortColumn === targetColumn;
return (
- titleOnclick(targetColumn)}
style={{ padding: '0.5rem' }}
@@ -26,13 +27,13 @@ export function HTSortable({ label, sortColumn, titleOnclick, sortDirection, tar
{label}
{showDirection && (sortingCurrent ? sortDirection === 1 ? : : null)}
-
+
);
}
export function TDAsset({ asset, chainId, symbol, dataLabel }: { asset: string; chainId: number; symbol: string; dataLabel?: string }) {
return (
-
-
+
);
}
@@ -75,13 +76,13 @@ export function TDTotalSupplyOrBorrow({
symbol: string;
}) {
return (
-
${`${formatReadable(Number(assetsUSD))} `}
{`${formatReadable(formatBalance(assets, decimals))} ${symbol}`}
-
+
);
}
diff --git a/app/markets/components/marketsTable.tsx b/app/markets/components/marketsTable.tsx
index 3cfb3731..33538880 100644
--- a/app/markets/components/marketsTable.tsx
+++ b/app/markets/components/marketsTable.tsx
@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react';
import { FaRegStar, FaStar } from 'react-icons/fa';
+import { Table, TableHeader, TableRow, TableHead } from '@/components/ui/table';
import { TablePagination } from '@/components/common/TablePagination';
import type { TrustedVault } from '@/constants/vaults/known_vaults';
import { useRateLabel } from '@/hooks/useRateLabel';
@@ -68,15 +69,17 @@ function MarketsTable({
const totalPages = Math.ceil(markets.length / entriesPerPage);
const containerClassName = ['flex flex-col gap-2 pb-4', className].filter((value): value is string => Boolean(value)).join(' ');
- const tableWrapperClassName = ['overflow-x-auto', wrapperClassName].filter((value): value is string => Boolean(value)).join(' ');
- const tableClassNames = ['responsive rounded-md font-zen', tableClassName].filter((value): value is string => Boolean(value)).join(' ');
+ const tableWrapperClassName = ['bg-surface shadow-sm rounded overflow-hidden', wrapperClassName]
+ .filter((value): value is string => Boolean(value))
+ .join(' ');
+ const tableClassNames = ['responsive', tableClassName].filter((value): value is string => Boolean(value)).join(' ');
return (
-
-
-
+
+
+
: }
sortColumn={sortColumn}
@@ -85,7 +88,7 @@ function MarketsTable({
targetColumn={SortColumn.Starred}
showDirection={false}
/>
- Id
+ Id
- Oracle
+ Oracle
)}
-
{' '}
Risk{' '}
-
-
+
{' '}
Indicators{' '}
-
-
+
{' '}
Actions{' '}
-
-
-
+
+
+
-
+
-
+
-
-
- Market
- {rateLabel}
- Util
- Supplied Amount
-
-
-
+
+
+ Market
+ {rateLabel}
+ Util
+ Supplied Amount
+
+
+
{paginatedPositions.map((position) => {
const userConfirmedSupply = BigInt(position.state.supplyAssets);
const pendingDeltaBigInt = BigInt(position.pendingDelta);
@@ -77,14 +78,14 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM
const isSelected = position.market.uniqueKey === selectedMarketUniqueKey;
const apyPreview = getApyPreview(position);
return (
- onSelectMarket(position.market.uniqueKey)}
className={`cursor-pointer border-b border-l-2 border-l-transparent border-gray-200 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800 ${
isSelected ? 'bg-primary/5 border-l-primary' : ''
}`}
>
-
+
-
-
+
+
{apyPreview ? (
@@ -121,8 +122,8 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM
%
)}
-
-
+
+
{apyPreview ? (
{formatReadable(position.market.state.utilization * 100)}%
@@ -134,8 +135,8 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM
{formatReadable(position.market.state.utilization * 100)}%
)}
-
-
+
+
{formatReadable(
@@ -160,24 +161,22 @@ export function FromMarketsTable({ positions, selectedMarketUniqueKey, onSelectM
Max
-
-
+
+
);
})}
-
-
+
+
{totalPages > 1 && (
-
+
)}
>
)}
diff --git a/app/positions/components/PositionsSummaryTable.tsx b/app/positions/components/PositionsSummaryTable.tsx
index ee04e668..df3474fe 100644
--- a/app/positions/components/PositionsSummaryTable.tsx
+++ b/app/positions/components/PositionsSummaryTable.tsx
@@ -10,6 +10,7 @@ import { IoChevronDownOutline } from 'react-icons/io5';
import { PiHandCoins } from 'react-icons/pi';
import { PulseLoader } from 'react-spinners';
import { useConnection } from 'wagmi';
+import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { TokenIcon } from '@/components/TokenIcon';
import { TooltipContent } from '@/components/TooltipContent';
@@ -281,15 +282,15 @@ export function PositionsSummaryTable({
-
-
-
-
-
- Network
- Size
- {rateLabel} (now)
-
+
+
+
+
+
+ Network
+ Size
+ {rateLabel} (now)
+
Interest Accrued ({earningsPeriod})
-
- Collateral
- Warnings
- Actions
-
-
-
+
+ Collateral
+ Warnings
+ Actions
+
+
+
{processedPositions.map((groupedPosition) => {
const rowKey = `${groupedPosition.loanAssetAddress}-${groupedPosition.chainId}`;
const isExpanded = expandedRows.has(rowKey);
@@ -326,12 +327,12 @@ export function PositionsSummaryTable({
return (
- toggleRow(rowKey)}
>
- {isExpanded ? : }
-
+ {isExpanded ? : }
+
-
-
+
+
{formatReadable(groupedPosition.totalSupply)}
{groupedPosition.loanAsset}
@@ -353,13 +354,13 @@ export function PositionsSummaryTable({
height={16}
/>
-
-
+
+
{formatReadable((isAprDisplay ? convertApyToApr(avgApy) : avgApy) * 100)}%
-
-
+
+
{isLoadingEarnings ? (
@@ -382,8 +383,8 @@ export function PositionsSummaryTable({
)}
-
-
+
+
{groupedPosition.collaterals.length > 0 ? (
groupedPosition.collaterals
@@ -403,16 +404,16 @@ export function PositionsSummaryTable({
No known collaterals
)}
-
-
+
-
-
+
@@ -434,12 +435,12 @@ export function PositionsSummaryTable({
Rebalance
-
-
+
+
{expandedRows.has(rowKey) && (
-
-
+
@@ -459,15 +460,15 @@ export function PositionsSummaryTable({
showCollateralExposure={showCollateralExposure}
/>
-
-
+
+
)}
);
})}
-
-
+
+
{showRebalanceModal && selectedGroupedPosition && (
0 ? (suppliedAmount / totalSupply) * 100 : 0;
return (
-
-
@@ -51,8 +52,8 @@ function MarketRow({
showNetworkIcon={false}
/>
-
-
+
@@ -63,20 +64,20 @@ function MarketRow({
chainId={position.market.morphoBlue.chain.id}
wide
/>
-
-
+
-
-
+
{formatReadable(suppliedAmount)} {position.market.loanAsset.symbol}
-
-
+
@@ -89,8 +90,8 @@ function MarketRow({
{formatReadable(percentageOfPortfolio)}%
-
-
+
@@ -98,8 +99,8 @@ function MarketRow({
market={position.market}
showRisk
/>
-
-
+
@@ -125,8 +126,8 @@ function MarketRow({
Supply
-
-
+
+
);
}
@@ -203,19 +204,19 @@ export function SuppliedMarketsDetail({
)}
{/* Markets Table - Always visible */}
-
-
-
- Market
- Collateral & Parameters
- {rateLabel}
- Supplied
- % of Portfolio
- Indicators
- Actions
-
-
-
+
+
+
+ Market
+ Collateral & Parameters
+ {rateLabel}
+ Supplied
+ % of Portfolio
+ Indicators
+ Actions
+
+
+
{filteredMarkets.map((position) => (
))}
-
-
+
+
);
diff --git a/app/positions/components/onboarding/SetupPositions.tsx b/app/positions/components/onboarding/SetupPositions.tsx
index 1dc8f7c1..41e8a45a 100644
--- a/app/positions/components/onboarding/SetupPositions.tsx
+++ b/app/positions/components/onboarding/SetupPositions.tsx
@@ -4,6 +4,7 @@ import { LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons';
import Image from 'next/image';
import { formatUnits, parseUnits } from 'viem';
import { Button } from '@/components/ui/button';
+import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
import { ExecuteTransactionButton } from '@/components/ui/ExecuteTransactionButton';
import Input from '@/components/Input/Input';
import { MarketIdentity, MarketIdentityMode, MarketIdentityFocus } from '@/components/MarketIdentity';
@@ -252,26 +253,26 @@ export function SetupPositions() {
{/* Markets Distribution */}
-
-
-
- Market
- {rateLabel}
- Distribution
-
-
-
+
+
+
+ Market
+ {rateLabel}
+ Distribution
+
+
+
{selectedMarkets.map((market) => {
const currentPercentage = percentages[market.uniqueKey] ?? 0;
const isLocked = lockedAmounts.has(market.uniqueKey);
return (
-
{/* Market Identity */}
-
-
+
{/* APY/APR */}
-
+
-
+
{/* Distribution Controls */}
-
@@ -335,12 +336,12 @@ export function SetupPositions() {
-
-
+
+
);
})}
-
-
+
+
{error && {error}
}
diff --git a/app/positions/report/components/ReportTable.tsx b/app/positions/report/components/ReportTable.tsx
index b3d89a8b..12e2921f 100644
--- a/app/positions/report/components/ReportTable.tsx
+++ b/app/positions/report/components/ReportTable.tsx
@@ -2,7 +2,7 @@
import { useState } from 'react';
import type { DateValue } from '@heroui/react';
-import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import { getLocalTimeZone } from '@internationalized/date';
import { ExternalLinkIcon } from '@radix-ui/react-icons';
import { useDateFormatter } from '@react-aria/i18n';
@@ -262,19 +262,15 @@ export function ReportTable({ report, asset, startDate, endDate, chainId }: Repo
{/* Transactions Table */}
{marketReport.transactions.length > 0 && (
-
-
+
+
- TYPE
- DATE
- AMOUNT
- TX
+
+ TYPE
+ DATE
+ AMOUNT
+ TX
+
{marketReport.transactions.map((tx) => (
diff --git a/app/rewards/components/RewardTable.tsx b/app/rewards/components/RewardTable.tsx
index 8fa866e4..a05c8f57 100644
--- a/app/rewards/components/RewardTable.tsx
+++ b/app/rewards/components/RewardTable.tsx
@@ -2,7 +2,7 @@
import { useCallback, useMemo, useState } from 'react';
import { ExternalLinkIcon } from '@radix-ui/react-icons';
-import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@heroui/react';
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableHead } from '@/components/ui/table';
import Image from 'next/image';
import Link from 'next/link';
import type { Address } from 'viem';
@@ -15,8 +15,8 @@ import { useClaimMerklRewards } from '@/hooks/useClaimMerklRewards';
import { useStyledToast } from '@/hooks/useStyledToast';
import { useTransactionWithToast } from '@/hooks/useTransactionWithToast';
import { formatBalance, formatSimple } from '@/utils/balance';
-import { getAssetURL, getMerklCampaignURL } from '@/utils/external';
-import { getNetworkImg } from '@/utils/networks';
+import { getMerklCampaignURL } from '@/utils/external';
+import { getNetworkImg, getNetworkName } from '@/utils/networks';
import { findToken } from '@/utils/tokens';
import type { AggregatedRewardType } from '@/utils/types';
@@ -137,147 +137,147 @@ export default function RewardTable({ rewards, distributions, merklRewardsWithPr
return (
-
-
- ASSET
- CHAIN
- CLAIMABLE
- CAMPAIGN
- ACTIONS
-
-
- {filteredRewardTokens
- .filter((tokenReward) => tokenReward !== null && tokenReward !== undefined)
- .map((tokenReward, index) => {
- // try find the reward token, default to 18 decimals for unknown tokens
- const matchedToken = findToken(tokenReward.asset.address, tokenReward.asset.chain_id) ?? {
- symbol: 'Unknown',
- img: undefined,
- decimals: 18,
- };
+
+
+
+
+ ASSET
+ CHAIN
+ CLAIMABLE
+ CAMPAIGN
+ ACTIONS
+
+
+
+ {filteredRewardTokens
+ .filter((tokenReward) => tokenReward !== null && tokenReward !== undefined)
+ .map((tokenReward, index) => {
+ // try find the reward token, default to 18 decimals for unknown tokens
+ const matchedToken = findToken(tokenReward.asset.address, tokenReward.asset.chain_id) ?? {
+ symbol: 'Unknown',
+ img: undefined,
+ decimals: 18,
+ };
- const distribution = distributions.find(
- (d) =>
- d.asset.address.toLowerCase() === tokenReward.asset.address.toLowerCase() &&
- d.asset.chain_id === tokenReward.asset.chain_id,
- );
+ const distribution = distributions.find(
+ (d) =>
+ d.asset.address.toLowerCase() === tokenReward.asset.address.toLowerCase() &&
+ d.asset.chain_id === tokenReward.asset.chain_id,
+ );
- const isMerklReward = tokenReward.programs.includes('merkl');
+ const isMerklReward = tokenReward.programs.includes('merkl');
- // Find matching campaign for this reward
- const matchedCampaign = campaigns.find(
- (c) =>
- c.rewardToken.address.toLowerCase() === tokenReward.asset.address.toLowerCase() &&
- c.chainId === tokenReward.asset.chain_id,
- );
+ // Find matching campaign for this reward
+ const matchedCampaign = campaigns.find(
+ (c) =>
+ c.rewardToken.address.toLowerCase() === tokenReward.asset.address.toLowerCase() &&
+ c.chainId === tokenReward.asset.chain_id,
+ );
- // Create unique key for tracking claim status
- const rewardKey = `${tokenReward.asset.address.toLowerCase()}-${tokenReward.asset.chain_id}`;
- const isThisRewardClaiming = claimingRewardKey === rewardKey;
+ // Create unique key for tracking claim status
+ const rewardKey = `${tokenReward.asset.address.toLowerCase()}-${tokenReward.asset.chain_id}`;
+ const isThisRewardClaiming = claimingRewardKey === rewardKey;
- return (
-
-
- e.stopPropagation()}
- >
- {matchedToken.symbol}
-
-
-
-
- {getNetworkImg(tokenReward.asset.chain_id) ? (
-
- ) : (
-
- )}
-
-
-
- {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))}
-
-
-
-
- {matchedCampaign ? (
-
+
+
+
+ {matchedToken.symbol}
+
+
+
+
+
+ {networkImg && (
+
+ )}
+ {networkName}
+
+
+
+
+
+
+ {formatSimple(formatBalance(tokenReward.total.claimable, matchedToken.decimals))}
+
+
+
+
+ {matchedCampaign ? (
+
+ view
+
+
+ ) : (
+ -
+ )}
+
+
+
+
+ {isMerklReward ? (
+
handleMerklClaim(tokenReward.asset.address, tokenReward.asset.chain_id)}
+ variant="surface"
+ size="sm"
+ disabled={tokenReward.total.claimable === BigInt(0) || isThisRewardClaiming}
+ isLoading={isThisRewardClaiming}
+ >
+ {isThisRewardClaiming && claimStatus === 'switching'
+ ? 'Switching...'
+ : isThisRewardClaiming && (claimStatus === 'pending' || claimStatus === 'preparing')
+ ? 'Claiming...'
+ : 'Claim'}
+
+ ) : (
+
handleClaim(distribution)}
+ variant="surface"
+ size="sm"
+ disabled={tokenReward.total.claimable === BigInt(0) || distribution === undefined}
+ >
+ Claim
+
)}
- target="_blank"
- rel="noopener noreferrer"
- className="inline-flex items-center gap-1 text-sm hover:opacity-80 no-underline"
- >
- Details
-
-
- ) : (
-
-
- )}
-
-
- {isMerklReward ? (
- handleMerklClaim(tokenReward.asset.address, tokenReward.asset.chain_id)}
- variant="surface"
- size="sm"
- disabled={tokenReward.total.claimable === BigInt(0) || isThisRewardClaiming}
- isLoading={isThisRewardClaiming}
- >
- {isThisRewardClaiming && claimStatus === 'switching'
- ? 'Switching...'
- : isThisRewardClaiming && (claimStatus === 'pending' || claimStatus === 'preparing')
- ? 'Claiming...'
- : 'Claim'}
-
- ) : (
- handleClaim(distribution)}
- variant="surface"
- size="sm"
- disabled={tokenReward.total.claimable === BigInt(0) || distribution === undefined}
- >
- Claim
-
- )}
-
-
- );
- })}
-
-
+
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/docs/Styling.md b/docs/Styling.md
index 7fdd7cae..5572674a 100644
--- a/docs/Styling.md
+++ b/docs/Styling.md
@@ -465,6 +465,61 @@ Add an action link (like explorer) in the top-right corner:
- Render token avatars with `TokenIcon` (`@/components/TokenIcon`) so chain-specific fallbacks, glyph sizing, and tooltips stay consistent.
- Display oracle provenance data with `OracleVendorBadge` (`@/components/OracleVendorBadge`) instead of plain text to benefit from vendor icons, warnings, and tooltips.
+### Table Component
+
+**Always use Table components from `@/components/ui/table`** - never use raw HTML `` tags.
+
+**Components:**
+```tsx
+import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
+import { TablePagination } from '@/components/common/TablePagination';
+```
+
+**Basic Usage:**
+```tsx
+
+
+
+ Name
+ Amount
+
+
+
+
+ John
+ $100
+
+
+
+
+{/* Pagination - always use TablePagination */}
+
+```
+
+**Key Rules:**
+
+1. **Variants**: Use `table-body-compact` on `` for activity/transaction tables (adds 6px cushion vs standard padding)
+2. **Alignment**: Headers and cells must match - use `text-left`, `text-right`, or `text-center`
+3. **Amount Columns**: Use flexbox with `justify-end` for right-aligned token icons, and use `text-sm` for text:
+ ```tsx
+
+
+ {amount}
+
+
+
+ ```
+4. **Pagination**: Always use `TablePagination` component (not HeroUI `Pagination`)
+
+**Styling:** All styling applied via `app/global.css` - don't add inline styles or override padding.
+
### TablePagination Component
**TablePagination** (`@/components/common/TablePagination`)
diff --git a/package.json b/package.json
index 372b5515..24811f9f 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,6 @@
"@heroui/input": "^2.2.2",
"@heroui/react": "^2.4.2",
"@heroui/system": "^2.2.2",
- "@heroui/table": "^2.0.36",
"@heroui/theme": "^2.2.6",
"@heroui/tooltip": "^2.0.36",
"@internationalized/date": "^3.8.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e694ab6c..8d36b41e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,9 +26,6 @@ importers:
'@heroui/system':
specifier: ^2.2.2
version: 2.4.20(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@heroui/table':
- specifier: ^2.0.36
- version: 2.2.24(@heroui/system@2.4.20(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@heroui/theme@2.4.20(tailwindcss@4.1.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@heroui/theme':
specifier: ^2.2.6
version: 2.4.20(tailwindcss@4.1.17)
@@ -6506,7 +6503,7 @@ snapshots:
'@babel/traverse': 7.28.3
'@babel/types': 7.28.2
convert-source-map: 2.0.0
- debug: 4.4.1
+ debug: 4.4.3
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -9150,7 +9147,7 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/base': 1.2.6
'@types/debug': 4.1.12
- debug: 4.4.1
+ debug: 4.4.3
lodash.memoize: 4.1.2
pony-cause: 2.1.11
semver: 7.7.2
@@ -9166,7 +9163,7 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/base': 1.2.6
'@types/debug': 4.1.12
- debug: 4.4.1
+ debug: 4.4.3
pony-cause: 2.1.11
semver: 7.7.2
uuid: 9.0.1
@@ -9181,7 +9178,7 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/base': 1.2.6
'@types/debug': 4.1.12
- debug: 4.4.1
+ debug: 4.4.3
pony-cause: 2.1.11
semver: 7.7.2
uuid: 9.0.1
diff --git a/src/components/common/MarketInfoBlock.tsx b/src/components/common/MarketInfoBlock.tsx
index 0d76d538..481fc5c0 100644
--- a/src/components/common/MarketInfoBlock.tsx
+++ b/src/components/common/MarketInfoBlock.tsx
@@ -71,7 +71,7 @@ export function MarketInfoBlockCompact({ market, amount, className }: MarketInfo
/>
-
{market.collateralAsset.symbol}
+
{market.collateralAsset.symbol}
{formatUnits(BigInt(market.lltv), 16)}% LTV
diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx
index 11094620..43c51956 100644
--- a/src/components/common/MarketsTableWithSameLoanAsset.tsx
+++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx
@@ -1033,7 +1033,7 @@ export function MarketsTableWithSameLoanAsset({
{/* Table */}
-
+
{showSelectColumn && (
+ * {items.map((item) => (
+ *
+ * toggleExpanded(item.id)}>
+ * ... main row content ...
+ *
+ *
+ * ... expanded content ...
+ *
+ *
+ * ))}
+ *
+ * ```
+ */
+export function ExpandableRow({ isExpanded, colSpan, children, className }: ExpandableRowProps) {
+ return (
+
+ {isExpanded && (
+
+
+
+ {children}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/ui/data-table/SortableHead.tsx b/src/components/ui/data-table/SortableHead.tsx
new file mode 100644
index 00000000..fac22395
--- /dev/null
+++ b/src/components/ui/data-table/SortableHead.tsx
@@ -0,0 +1,29 @@
+import type * as React from 'react';
+import { ArrowDownIcon, ArrowUpIcon } from '@radix-ui/react-icons';
+import { TableHead } from '../table';
+
+type SortableHeadProps = {
+ label: string | React.ReactNode;
+ sortKey: string;
+ currentSortKey: string;
+ direction: 'asc' | 'desc';
+ onSort: (key: string) => void;
+ showDirection?: boolean;
+ className?: string;
+};
+
+export function SortableHead({ label, sortKey, currentSortKey, direction, onSort, showDirection = true, className }: SortableHeadProps) {
+ const isSorting = currentSortKey === sortKey;
+
+ return (
+ onSort(sortKey)}
+ >
+
+
{label}
+ {showDirection && isSorting && (direction === 'desc' ?
:
)}
+
+
+ );
+}
diff --git a/src/components/ui/data-table/index.ts b/src/components/ui/data-table/index.ts
new file mode 100644
index 00000000..66ca92ff
--- /dev/null
+++ b/src/components/ui/data-table/index.ts
@@ -0,0 +1,2 @@
+export { SortableHead } from './SortableHead';
+export { ExpandableRow } from './ExpandableRow';
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
deleted file mode 100644
index e42a7759..00000000
--- a/src/components/ui/pagination.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { forwardRef } from 'react';
-import { ChevronLeftIcon, ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons';
-import { type ButtonProps, buttonVariants } from '@/components/ui/button';
-import { cn } from '@/lib/utils';
-
-function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
- return (
-
- );
-}
-Pagination.displayName = 'Pagination';
-
-const PaginationContent = forwardRef>(({ className, ...props }, ref) => (
-
-));
-PaginationContent.displayName = 'PaginationContent';
-
-const PaginationItem = forwardRef>(({ className, ...props }, ref) => (
-
-));
-PaginationItem.displayName = 'PaginationItem';
-
-type PaginationLinkProps = {
- isActive?: boolean;
-} & Pick &
- React.ComponentProps<'a'>;
-
-function PaginationLink({ className, isActive, size = 'icon', children, ...props }: PaginationLinkProps) {
- return (
-
- {children}
-
- );
-}
-PaginationLink.displayName = 'PaginationLink';
-
-function PaginationPrevious({ className, ...props }: React.ComponentProps) {
- return (
-
-
- Previous
-
- );
-}
-PaginationPrevious.displayName = 'PaginationPrevious';
-
-function PaginationNext({ className, ...props }: React.ComponentProps) {
- return (
-
- Next
-
-
- );
-}
-PaginationNext.displayName = 'PaginationNext';
-
-function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
- return (
-
-
- More pages
-
- );
-}
-PaginationEllipsis.displayName = 'PaginationEllipsis';
-
-export { Pagination, PaginationContent, PaginationLink, PaginationItem, PaginationPrevious, PaginationNext, PaginationEllipsis };
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 00000000..fa298b74
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,78 @@
+import { forwardRef } from 'react';
+import type { HTMLAttributes, ThHTMLAttributes, TdHTMLAttributes } from 'react';
+
+import { cn } from '@/lib/utils';
+
+const Table = forwardRef>(({ className, ...props }, ref) => (
+
+));
+Table.displayName = 'Table';
+
+const TableHeader = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableHeader.displayName = 'TableHeader';
+
+const TableBody = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableBody.displayName = 'TableBody';
+
+const TableFooter = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableFooter.displayName = 'TableFooter';
+
+const TableRow = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableRow.displayName = 'TableRow';
+
+const TableHead = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableHead.displayName = 'TableHead';
+
+const TableCell = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableCell.displayName = 'TableCell';
+
+const TableCaption = forwardRef>(({ className, ...props }, ref) => (
+
+));
+TableCaption.displayName = 'TableCaption';
+
+export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
diff --git a/src/hooks/useTableSort.ts b/src/hooks/useTableSort.ts
new file mode 100644
index 00000000..ae49e0af
--- /dev/null
+++ b/src/hooks/useTableSort.ts
@@ -0,0 +1,58 @@
+import { useState, useMemo } from 'react';
+
+type SortDirection = 'asc' | 'desc';
+
+type SortFn = (a: T, b: T) => number;
+
+type UseTableSortProps = {
+ data: T[];
+ sortFns: Record>;
+ initialSortKey?: string;
+ initialDirection?: SortDirection;
+};
+
+type UseTableSortReturn = {
+ sortKey: string;
+ sortDirection: SortDirection;
+ handleSort: (key: string) => void;
+ sortedData: T[];
+};
+
+export function useTableSort({
+ data,
+ sortFns,
+ initialSortKey = '',
+ initialDirection = 'desc',
+}: UseTableSortProps): UseTableSortReturn {
+ const [sortKey, setSortKey] = useState(initialSortKey);
+ const [sortDirection, setSortDirection] = useState(initialDirection);
+
+ const handleSort = (key: string) => {
+ if (sortKey === key) {
+ // Toggle direction if clicking the same column
+ setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc'));
+ } else {
+ // New column, default to descending
+ setSortKey(key);
+ setSortDirection('desc');
+ }
+ };
+
+ const sortedData = useMemo(() => {
+ if (!sortKey || !sortFns[sortKey]) {
+ return data;
+ }
+
+ const sortFn = sortFns[sortKey];
+ const sorted = [...data].sort(sortFn);
+
+ return sortDirection === 'asc' ? sorted : sorted.reverse();
+ }, [data, sortKey, sortDirection, sortFns]);
+
+ return {
+ sortKey,
+ sortDirection,
+ handleSort,
+ sortedData,
+ };
+}