From 1cee16faf9626c18e975f9f6d15aa349c168a91c Mon Sep 17 00:00:00 2001 From: antoncoding Date: Tue, 4 Nov 2025 16:05:52 +0800 Subject: [PATCH 1/7] feat: customizable tables --- .../components/MarketSettingsModal.tsx | 37 ++++- app/markets/components/MarketTableBody.tsx | 91 ++++++++--- app/markets/components/MarketTableUtils.tsx | 20 +-- app/markets/components/columnVisibility.ts | 37 +++++ app/markets/components/constants.ts | 3 + app/markets/components/markets.tsx | 49 +++++- app/markets/components/marketsTable.tsx | 91 +++++++---- app/markets/components/utils.ts | 3 + .../common/MarketsTableWithSameLoanAsset.tsx | 153 ++++++++++++------ src/utils/storageKeys.ts | 8 +- 10 files changed, 381 insertions(+), 111 deletions(-) create mode 100644 app/markets/components/columnVisibility.ts diff --git a/app/markets/components/MarketSettingsModal.tsx b/app/markets/components/MarketSettingsModal.tsx index b2d97e41..230dc0ea 100644 --- a/app/markets/components/MarketSettingsModal.tsx +++ b/app/markets/components/MarketSettingsModal.tsx @@ -11,6 +11,7 @@ import { Divider, } from '@heroui/react'; import { useMarkets } from '@/hooks/useMarkets'; +import { ColumnVisibility, COLUMN_LABELS, COLUMN_DESCRIPTIONS } from './columnVisibility'; type MarketSettingsModalProps = { isOpen: boolean; @@ -37,6 +38,9 @@ type MarketSettingsModalProps = { // Pagination entriesPerPage: number; onEntriesPerPageChange: (value: number) => void; + // Column Visibility + columnVisibility: ColumnVisibility; + setColumnVisibility: (visibility: ColumnVisibility) => void; }; // Reusable component for consistent setting layout @@ -81,6 +85,8 @@ export default function MarketSettingsModal({ setMinLiquidityEnabled, entriesPerPage, onEntriesPerPageChange, + columnVisibility, + setColumnVisibility, }: MarketSettingsModalProps) { const [customEntries, setCustomEntries] = React.useState(entriesPerPage.toString()); const { @@ -129,7 +135,7 @@ export default function MarketSettingsModal({ {(onClose) => ( <> Market View Settings - + {/* --- Filter Settings Section --- */}
{/* Section Header: Adjusted style & position */} @@ -334,6 +340,35 @@ export default function MarketSettingsModal({
+ {/* --- Column Visibility Section --- */} +
+

+ Visible Columns +

+

+ Choose which columns to display in the markets table. +

+
+ {(Object.keys(columnVisibility) as Array).map((key) => ( +
+ + + setColumnVisibility({ ...columnVisibility, [key]: value }) + } + size="sm" + color="primary" + /> +
+ ))} +
+
+ {/* --- View Options Section --- */}
{/* Section Header: Adjusted style & position */} diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 0f28a1a9..1aac6860 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -5,8 +5,10 @@ import { Button } from '@/components/common/Button'; import { MarketIdBadge } from '@/components/MarketIdBadge'; import { MarketIndicators } from '@/components/MarketIndicators'; import OracleVendorBadge from '@/components/OracleVendorBadge'; +import { formatBalance } from '@/utils/balance'; import { Market } from '@/utils/types'; import { APYCell } from './APYBreakdownTooltip'; +import { ColumnVisibility } from './columnVisibility'; import { ExpandedMarketDetail } from './MarketRowDetail'; import { TDAsset, TDTotalSupplyOrBorrow } from './MarketTableUtils'; import { MarketAssetIndicator, MarketOracleIndicator, MarketDebtIndicator } from './RiskIndicator'; @@ -21,6 +23,7 @@ type MarketTableBodyProps = { starMarket: (id: string) => void; unstarMarket: (id: string) => void; onMarketClick: (market: Market) => void; + columnVisibility: ColumnVisibility; }; export function MarketTableBody({ @@ -33,7 +36,18 @@ export function MarketTableBody({ starMarket, unstarMarket, onMarketClick, + columnVisibility, }: MarketTableBodyProps) { + // Calculate colspan for expanded row based on visible columns + const visibleColumnsCount = + 9 + // Base columns: Star, ID, Loan, Collateral, Oracle, LLTV, Risk, Indicators, Actions + (columnVisibility.totalSupply ? 1 : 0) + + (columnVisibility.totalBorrow ? 1 : 0) + + (columnVisibility.liquidity ? 1 : 0) + + (columnVisibility.supplyAPY ? 1 : 0) + + (columnVisibility.borrowAPY ? 1 : 0) + + (columnVisibility.rateAtTarget ? 1 : 0); + return ( {currentEntries.map((item, index) => { @@ -53,7 +67,7 @@ export function MarketTableBody({ item.uniqueKey === expandedRowId ? 'table-body-focused ' : '' }'`} > - + - + + +
+ + + {/* Table Section - can expand beyond container in expanded mode */} +
{loading ? ( - +
+ +
) : rawMarkets == null ? ( -
No data
+
No data
) : ( -
+
{filteredMarkets.length > 0 ? ( ) : ( void; onMarketClick: (market: Market) => void; + columnVisibility: ColumnVisibility; }; function MarketsTable({ @@ -36,6 +38,7 @@ function MarketsTable({ entriesPerPage, setCurrentPage, onMarketClick, + columnVisibility, }: MarketsTableProps) { const [expandedRowId, setExpandedRowId] = useState(null); @@ -48,7 +51,7 @@ function MarketsTable({ return (
- +
- + - + - - - - - - + {columnVisibility.totalSupply && ( + + )} + {columnVisibility.totalBorrow && ( + + )} + {columnVisibility.liquidity && ( + + )} + {columnVisibility.supplyAPY && ( + + )} + {columnVisibility.borrowAPY && ( + + )} + {columnVisibility.rateAtTarget && ( + + )} + + +
Id Id OracleOracle Risk Indicators Actions Risk Indicators Actions
diff --git a/app/markets/components/utils.ts b/app/markets/components/utils.ts index fed66bcb..79391d77 100644 --- a/app/markets/components/utils.ts +++ b/app/markets/components/utils.ts @@ -14,6 +14,9 @@ export const sortProperties = { [SortColumn.Supply]: 'state.supplyAssetsUsd', [SortColumn.Borrow]: 'state.borrowAssetsUsd', [SortColumn.SupplyAPY]: 'state.supplyApy', + [SortColumn.Liquidity]: 'state.liquidityAssets', + [SortColumn.BorrowAPY]: 'state.borrowApy', + [SortColumn.RateAtTarget]: 'state.rateAtUTarget', }; export const getNestedProperty = (obj: Market, path: string | ((item: Market) => number)) => { diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx index 6ad1ff37..d75ff854 100644 --- a/src/components/common/MarketsTableWithSameLoanAsset.tsx +++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx @@ -19,6 +19,7 @@ import { parsePriceFeedVendors, PriceFeedVendors, OracleVendorIcons } from '@/ut import * as keys from "@/utils/storageKeys" import { ERC20Token, UnknownERC20Token, infoToKey } from '@/utils/tokens'; import { Market } from '@/utils/types'; +import { DEFAULT_COLUMN_VISIBILITY, ColumnVisibility } from 'app/markets/components/columnVisibility'; import MarketSettingsModal from 'app/markets/components/MarketSettingsModal'; import { Pagination } from '../../../app/markets/components/Pagination'; import { MarketIdBadge } from '../MarketIdBadge'; @@ -70,8 +71,9 @@ function HTSortable({ const isSorting = sortColumn === column; return ( onSort(column)} + style={{ padding: '0.5rem' }} >
{label}
@@ -369,11 +371,13 @@ function MarketRow({ onToggle, disabled, showSelectColumn, + columnVisibility, }: { marketWithSelection: MarketWithSelection; onToggle: () => void; disabled: boolean; showSelectColumn: boolean; + columnVisibility: ColumnVisibility; }) { const { market, isSelected } = marketWithSelection; @@ -403,10 +407,10 @@ function MarketRow({
)} - + - + - -

- {formatReadable(formatBalance(market.state.supplyAssets, market.loanAsset.decimals))} -

- - -

- {market.state.supplyApy ? `${(market.state.supplyApy * 100).toFixed(2)}` : '—'} -

- { market.state.supplyApy && % } - - -

- {formatReadable(formatBalance(market.state.liquidityAssets, market.loanAsset.decimals))} -

- - + {columnVisibility.totalSupply && ( + +

+ {formatReadable(formatBalance(market.state.supplyAssets, market.loanAsset.decimals))} +

+ + )} + {columnVisibility.totalBorrow && ( + +

+ {formatReadable(formatBalance(market.state.borrowAssets, market.loanAsset.decimals))} +

+ + )} + {columnVisibility.liquidity && ( + +

+ {formatReadable(formatBalance(market.state.liquidityAssets, market.loanAsset.decimals))} +

+ + )} + {columnVisibility.supplyAPY && ( + +
+

+ {market.state.supplyApy ? `${(market.state.supplyApy * 100).toFixed(2)}` : '—'} +

+ {market.state.supplyApy && % } +
+ + )} + {columnVisibility.borrowAPY && ( + +

+ {market.state.borrowApy ? `${(market.state.borrowApy * 100).toFixed(2)}%` : '—'} +

+ + )} + {columnVisibility.rateAtTarget && ( + +

+ {market.state.rateAtUTarget ? `${(market.state.rateAtUTarget * 100).toFixed(2)}%` : '—'} +

+ + )} + @@ -496,6 +529,12 @@ export function MarketsTableWithSameLoanAsset({ false, ); + // Column visibility state + const [columnVisibility, setColumnVisibility] = useLocalStorage( + keys.MarketsColumnVisibilityKey, + DEFAULT_COLUMN_VISIBILITY, + ); + // Create memoized usdFilters object from individual localStorage values const usdFilters = useMemo( () => ({ @@ -770,11 +809,11 @@ export function MarketsTableWithSameLoanAsset({ {/* Table */}
- +
- {showSelectColumn && } - + {showSelectColumn && } + - - - - + {columnVisibility.totalSupply && ( + + )} + {columnVisibility.totalBorrow && ( + + )} + {columnVisibility.liquidity && ( + + )} + {columnVisibility.supplyAPY && ( + + )} + {columnVisibility.borrowAPY && ( + + )} + {columnVisibility.rateAtTarget && ( + + )} + @@ -821,6 +881,7 @@ export function MarketsTableWithSameLoanAsset({ onToggle={() => onToggleMarket(marketWithSelection.market.uniqueKey)} disabled={disabled} showSelectColumn={showSelectColumn} + columnVisibility={columnVisibility} /> )) )} @@ -857,6 +918,8 @@ export function MarketsTableWithSameLoanAsset({ setMinLiquidityEnabled={setMinLiquidityEnabled} entriesPerPage={entriesPerPage} onEntriesPerPageChange={setEntriesPerPage} + columnVisibility={columnVisibility} + setColumnVisibility={setColumnVisibility} /> )} diff --git a/src/utils/storageKeys.ts b/src/utils/storageKeys.ts index 3a44b067..6a0e8a77 100644 --- a/src/utils/storageKeys.ts +++ b/src/utils/storageKeys.ts @@ -23,4 +23,10 @@ export const CacheMarketPositionKeys = 'monarch_cache_market_unique_keys'; // Deprecated: Use MarketsMinSupplyEnabledKey instead export const MarketsShowSmallMarkets = 'monarch_show_small_markets' export const MarketsShowUnknownTokens = 'includeUnknownTokens'; -export const MarketsShowUnknownOracle = 'showUnknownOracle'; \ No newline at end of file +export const MarketsShowUnknownOracle = 'showUnknownOracle'; + +// Column visibility settings +export const MarketsColumnVisibilityKey = 'monarch_marketsColumnVisibility'; + +// Table view mode +export const MarketsTableViewModeKey = 'monarch_marketsTableViewMode'; \ No newline at end of file From ad881079ca3afdf8141f0eebf89acee79f64db9f Mon Sep 17 00:00:00 2001 From: antoncoding Date: Tue, 4 Nov 2025 17:32:32 +0800 Subject: [PATCH 2/7] feat: expandable table and adjust layout --- .env.local.example | 14 ++- .../[chainId]/[vaultAddress]/content.tsx | 2 +- app/autovault/components/AutovaultContent.tsx | 4 +- app/global.css | 5 +- app/history/components/HistoryContent.tsx | 2 +- app/markets/components/MarketTableBody.tsx | 8 +- app/markets/components/MarketTableUtils.tsx | 4 +- app/markets/components/markets.tsx | 76 +++++++++------- app/markets/components/marketsTable.tsx | 32 +++++-- app/positions/components/PositionsContent.tsx | 2 +- app/rewards/components/RewardContent.tsx | 2 +- app/settings/page.tsx | 2 +- .../SearchOrConnect/SearchOrConnect.tsx | 2 +- .../common/MarketsTableWithSameLoanAsset.tsx | 23 +++-- .../SuppliedAssetFilterCompactSwitch.tsx | 89 +++++++++++++++++++ tailwind.config.ts | 6 +- 16 files changed, 201 insertions(+), 72 deletions(-) create mode 100644 src/components/common/SuppliedAssetFilterCompactSwitch.tsx diff --git a/.env.local.example b/.env.local.example index 15b545c8..409d0ed5 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,8 +1,14 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS_ID= -NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="GET_ID_FROM_WALLET_CONNET" # See https://cloud.walletconnect.com -ENVIRONMENT=localhost +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID= +ENVIRONMENT= NEXT_PUBLIC_ALCHEMY_API_KEY= +NEXT_PUBLIC_INFURA_API_KEY= -# used for querying block with given timestamp -ETHERSCAN_API_KEY= \ No newline at end of file +NEXT_PUBLIC_THEGRAPH_API_KEY= + +# Used for balance API +ALCHEMY_API_KEY= + +# used for getting block +ETHERSCAN_API_KEY= diff --git a/app/autovault/[chainId]/[vaultAddress]/content.tsx b/app/autovault/[chainId]/[vaultAddress]/content.tsx index 3cc7ed1f..41ba63b6 100644 --- a/app/autovault/[chainId]/[vaultAddress]/content.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/content.tsx @@ -117,7 +117,7 @@ export default function VaultContent() { return (
-
+

Vault data unavailable

diff --git a/app/autovault/components/AutovaultContent.tsx b/app/autovault/components/AutovaultContent.tsx index 8824a91c..41fb7b75 100644 --- a/app/autovault/components/AutovaultContent.tsx +++ b/app/autovault/components/AutovaultContent.tsx @@ -25,7 +25,7 @@ export default function AutovaultContent() { return (

-
+

Autovault

@@ -53,7 +53,7 @@ export default function AutovaultContent() { return (
-
+

Autovault

diff --git a/app/global.css b/app/global.css index 47f12f44..1fbb0b3b 100644 --- a/app/global.css +++ b/app/global.css @@ -112,7 +112,8 @@ h1 { } .table-header th { - padding: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; text-align: center; } @@ -123,6 +124,8 @@ h1 { .table-body td { padding: 1rem; + padding-left: 1rem; + padding-right: 1rem; text-align: center; } diff --git a/app/history/components/HistoryContent.tsx b/app/history/components/HistoryContent.tsx index df5b6bdd..899c27de 100644 --- a/app/history/components/HistoryContent.tsx +++ b/app/history/components/HistoryContent.tsx @@ -13,7 +13,7 @@ export default function HistoryContent({ account }: { account: string }) { return (
-
+

Transaction History

diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 1aac6860..cd9d4117 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -120,7 +120,7 @@ export function MarketTableBody({ />
-
{columnVisibility.totalSupply && ( @@ -151,19 +151,19 @@ export function MarketTableBody({ /> )} {columnVisibility.supplyAPY && ( - )} {columnVisibility.borrowAPY && ( - )} {columnVisibility.rateAtTarget && ( -
SelectIdSelectId IndicatorsBorrow APYRate at TargetIndicators
+ {Number(item.lltv) / 1e16}% + +

{item.state.borrowApy ? `${(item.state.borrowApy * 100).toFixed(2)}%` : '—'}

+

{item.state.rateAtUTarget ? `${(item.state.rateAtUTarget * 100).toFixed(2)}%` : '—'}

diff --git a/app/markets/components/MarketTableUtils.tsx b/app/markets/components/MarketTableUtils.tsx index 9006d1a8..4232f83a 100644 --- a/app/markets/components/MarketTableUtils.tsx +++ b/app/markets/components/MarketTableUtils.tsx @@ -25,11 +25,11 @@ export function HTSortable({ return (
titleOnclick(targetColumn)} style={{ padding: '0.5rem' }} > -
+
{label}
{showDirection && (sortingCurrent ? sortDirection === 1 ? : : null)} diff --git a/app/markets/components/markets.tsx b/app/markets/components/markets.tsx index 748f2498..aa6c5319 100644 --- a/app/markets/components/markets.tsx +++ b/app/markets/components/markets.tsx @@ -1,6 +1,6 @@ 'use client'; import { useCallback, useEffect, useState, useMemo } from 'react'; -import { useDisclosure, Checkbox } from '@heroui/react'; +import { useDisclosure, Tooltip } from '@heroui/react'; import { ReloadIcon } from '@radix-ui/react-icons'; import { Chain } from '@rainbow-me/rainbowkit'; import { useRouter } from 'next/navigation'; @@ -14,13 +14,13 @@ import { useTokens } from '@/components/providers/TokenProvider'; import EmptyScreen from '@/components/Status/EmptyScreen'; import LoadingScreen from '@/components/Status/LoadingScreen'; import { SupplyModalV2 } from '@/components/SupplyModalV2'; +import { TooltipContent } from '@/components/TooltipContent'; import { DEFAULT_MIN_SUPPLY_USD, DEFAULT_MIN_LIQUIDITY_USD } from '@/constants/markets'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useMarkets } from '@/hooks/useMarkets'; import { usePagination } from '@/hooks/usePagination'; import { useStaredMarkets } from '@/hooks/useStaredMarkets'; import { useStyledToast } from '@/hooks/useStyledToast'; -import { formatReadable } from '@/utils/balance'; import { filterMarkets, sortMarkets, createPropertySort, createStarredSort } from '@/utils/marketFilters'; import { parseNumericThreshold } from '@/utils/markets'; import { SupportedNetworks } from '@/utils/networks'; @@ -31,6 +31,7 @@ import { Market } from '@/utils/types'; import AdvancedSearchBar, { ShortcutType } from './AdvancedSearchBar'; import AssetFilter from './AssetFilter'; +import { SuppliedAssetFilterCompactSwitch } from '@/components/common/SuppliedAssetFilterCompactSwitch'; import { DEFAULT_COLUMN_VISIBILITY, ColumnVisibility } from './columnVisibility'; import { SortColumn } from './constants'; import MarketSettingsModal from './MarketSettingsModal'; @@ -384,7 +385,7 @@ export default function Markets({ return (
-
+

Markets

{showSupplyModal && selectedMarket && ( @@ -470,16 +471,11 @@ export default function Markets({ {/* Settings */}
-
- - - Hide markets below ${formatReadable(effectiveMinSupply)} - -
+ - + +