diff --git a/src/features/position-detail/components/overview-tab.tsx b/src/features/position-detail/components/overview-tab.tsx index c79235c9..c18d51e4 100644 --- a/src/features/position-detail/components/overview-tab.tsx +++ b/src/features/position-detail/components/overview-tab.tsx @@ -22,6 +22,8 @@ const PERIOD_LABELS: Record = { day: '24h', week: '7d', month: '30d', + sixmonth: '6mo', + all: 'All time', }; export function OverviewTab({ @@ -42,10 +44,12 @@ export function OverviewTab({ { @@ -107,7 +109,16 @@ export function YieldAnalysisDistribution({ markets, periodLabel }: YieldAnalysi
- {pieData.length === 0 ? ( + {isLoading ? ( +
+ + Calculating... +
+ ) : pieData.length === 0 ? (
No activity in selected period.
) : (
- {pieData.length === 0 ? ( + {isLoading ? ( +
+ +
+ ) : pieData.length === 0 ? (
No weighted exposure to display.
) : (
diff --git a/src/features/position-detail/components/yield-analysis-yield-breakdown.tsx b/src/features/position-detail/components/yield-analysis-yield-breakdown.tsx index 74c0cb01..17407aae 100644 --- a/src/features/position-detail/components/yield-analysis-yield-breakdown.tsx +++ b/src/features/position-detail/components/yield-analysis-yield-breakdown.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip as RechartsTooltip } from 'recharts'; +import { PulseLoader } from 'react-spinners'; import { Card } from '@/components/ui/card'; import { useChartColors } from '@/constants/chartColors'; import { getColorByIndex, OTHER_COLOR } from '@/features/positions/utils/colors'; @@ -11,6 +12,7 @@ import type { MarketPositionWithEarnings } from '@/utils/types'; type YieldAnalysisYieldBreakdownProps = { markets: MarketPositionWithEarnings[]; periodLabel: string; + isLoading?: boolean; }; type PieEntry = { @@ -24,7 +26,7 @@ type PieEntry = { const MAX_PIE_ITEMS = 6; -export function YieldAnalysisYieldBreakdown({ markets, periodLabel }: YieldAnalysisYieldBreakdownProps) { +export function YieldAnalysisYieldBreakdown({ markets, periodLabel, isLoading = false }: YieldAnalysisYieldBreakdownProps) { const chartColors = useChartColors(); const pieData = useMemo((): PieEntry[] => { @@ -99,7 +101,16 @@ export function YieldAnalysisYieldBreakdown({ markets, periodLabel }: YieldAnaly
- {pieData.length === 0 ? ( + {isLoading ? ( +
+ + Calculating... +
+ ) : pieData.length === 0 ? (
No yield generated in period.
) : (
- {pieData.length === 0 ? ( + {isLoading ? ( +
+ +
+ ) : pieData.length === 0 ? (
No yield breakdown to display.
) : (
diff --git a/src/features/position-detail/position-view.tsx b/src/features/position-detail/position-view.tsx index 82e3f8bc..4b3afdc9 100644 --- a/src/features/position-detail/position-view.tsx +++ b/src/features/position-detail/position-view.tsx @@ -29,6 +29,8 @@ const PERIOD_LABELS: Record = { day: '24h', week: '7d', month: '30d', + sixmonth: '6mo', + all: 'All time', }; type PositionDetailTab = 'analysis' | 'history'; diff --git a/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx b/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx index dd3f16bc..57b99f8a 100644 --- a/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx +++ b/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx @@ -70,11 +70,13 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr return account === address; }, [account, address]); - const periodLabels = { + const periodLabels: Record = { day: '1D', week: '7D', month: '30D', - } as const; + sixmonth: '6M', + all: 'All', + }; const groupedPositions = useMemo(() => groupPositionsByLoanAsset(marketPositions), [marketPositions]); diff --git a/src/features/positions/components/user-vaults-table.tsx b/src/features/positions/components/user-vaults-table.tsx index 63621c6c..a3630ccc 100644 --- a/src/features/positions/components/user-vaults-table.tsx +++ b/src/features/positions/components/user-vaults-table.tsx @@ -23,11 +23,13 @@ import { VaultAllocationDetail } from './vault-allocation-detail'; import { CollateralIconsDisplay } from './collateral-icons-display'; import { VaultActionsDropdown } from './vault-actions-dropdown'; -const periodLabels = { +const periodLabels: Record = { day: '1D', week: '7D', month: '30D', -} as const; + sixmonth: '6M', + all: 'All', +}; const formatRate = (rate: number | null | undefined, isApr: boolean): string => { if (rate === null || rate === undefined) return '-'; diff --git a/src/hooks/useMarketData.ts b/src/hooks/useMarketData.ts index 4aa1c398..fd6f10ca 100644 --- a/src/hooks/useMarketData.ts +++ b/src/hooks/useMarketData.ts @@ -32,7 +32,7 @@ export const useMarketData = (uniqueKey: string | undefined, network: SupportedN console.log(`Attempting fetchMarketSnapshot for market ${uniqueKey}`); let snapshot = null; try { - snapshot = await fetchMarketSnapshot(uniqueKey, network, publicClient, 0); + snapshot = await fetchMarketSnapshot(uniqueKey, network, publicClient); console.log(`Market state (from RPC) result for ${uniqueKey}:`, snapshot ? 'Exists' : 'Null'); } catch (snapshotError) { console.error(`Error fetching market snapshot for ${uniqueKey}:`, snapshotError); diff --git a/src/hooks/usePositionsWithEarnings.ts b/src/hooks/usePositionsWithEarnings.ts index 1389055b..e0d29ab0 100644 --- a/src/hooks/usePositionsWithEarnings.ts +++ b/src/hooks/usePositionsWithEarnings.ts @@ -14,6 +14,10 @@ export const getPeriodTimestamp = (period: EarningsPeriod): number => { return now - 7 * 86_400; case 'month': return now - 30 * 86_400; + case 'sixmonth': + return now - 180 * 86_400; + case 'all': + return 0; default: return now - 86_400; } diff --git a/src/hooks/useUserPosition.ts b/src/hooks/useUserPosition.ts index 06cfe6ca..40b81eae 100644 --- a/src/hooks/useUserPosition.ts +++ b/src/hooks/useUserPosition.ts @@ -49,7 +49,7 @@ const useUserPosition = (user: string | undefined, chainId: SupportedNetworks | console.log(`Attempting fetchPositionSnapshot for ${user} on market ${marketKey}`); let snapshot = null; try { - snapshot = await fetchPositionSnapshot(marketKey, user as Address, chainId, 0, publicClient); + snapshot = await fetchPositionSnapshot(marketKey, user as Address, chainId, undefined, publicClient); console.log(`Snapshot result for ${marketKey}:`, snapshot ? 'Exists' : 'Null'); } catch (snapshotError) { console.error(`Error fetching position snapshot for ${user} on market ${marketKey}:`, snapshotError); diff --git a/src/hooks/useUserPositions.ts b/src/hooks/useUserPositions.ts index 5b40b8de..f8f40363 100644 --- a/src/hooks/useUserPositions.ts +++ b/src/hooks/useUserPositions.ts @@ -187,7 +187,7 @@ const useUserPositions = (user: string | undefined, showEmpty = false, chainIds? } const marketIds = markets.map((m) => m.marketUniqueKey); - const snapshots = await fetchPositionsSnapshots(marketIds, user as Address, chainId, 0, publicClient); + const snapshots = await fetchPositionsSnapshots(marketIds, user as Address, chainId, undefined, publicClient); // Merge into allSnapshots snapshots.forEach((snapshot, marketId) => { diff --git a/src/hooks/useUserPositionsSummaryData.ts b/src/hooks/useUserPositionsSummaryData.ts index 74d31531..062dae3e 100644 --- a/src/hooks/useUserPositionsSummaryData.ts +++ b/src/hooks/useUserPositionsSummaryData.ts @@ -40,11 +40,19 @@ const useUserPositionsSummaryData = (user: string | undefined, period: EarningsP return blocks; }, [period, uniqueChainIds, currentBlocks]); - const { data: actualBlockData } = useBlockTimestamps(snapshotBlocks); + const { + data: actualBlockData, + isLoading: isLoadingBlockTimestamps, + isFetching: isFetchingBlockTimestamps, + } = useBlockTimestamps(snapshotBlocks); const endTimestamp = useMemo(() => Math.floor(Date.now() / 1000), []); - const { data: txData, isLoading: isLoadingTransactions } = useUserTransactionsQuery({ + const { + data: txData, + isLoading: isLoadingTransactions, + isFetching: isFetchingTransactions, + } = useUserTransactionsQuery({ filters: { userAddress: user ? [user] : [], marketUniqueKeys: positions?.map((p) => p.market.uniqueKey) ?? [], @@ -54,7 +62,11 @@ const useUserPositionsSummaryData = (user: string | undefined, period: EarningsP enabled: !!positions && !!user, }); - const { data: allSnapshots, isLoading: isLoadingSnapshots } = usePositionSnapshots({ + const { + data: allSnapshots, + isLoading: isLoadingSnapshots, + isFetching: isFetchingSnapshots, + } = usePositionSnapshots({ positions, user, snapshotBlocks, @@ -95,7 +107,13 @@ const useUserPositionsSummaryData = (user: string | undefined, period: EarningsP } }; - const isEarningsLoading = isLoadingSnapshots || isLoadingTransactions || !actualBlockData; + const isEarningsLoading = + isLoadingSnapshots || + isFetchingSnapshots || + isLoadingTransactions || + isFetchingTransactions || + isLoadingBlockTimestamps || + isFetchingBlockTimestamps; const loadingStates = { positions: positionsLoading, diff --git a/src/stores/usePositionsFilters.ts b/src/stores/usePositionsFilters.ts index 8f2b4e82..8a566283 100644 --- a/src/stores/usePositionsFilters.ts +++ b/src/stores/usePositionsFilters.ts @@ -3,9 +3,8 @@ import { persist } from 'zustand/middleware'; /** * Earnings calculation periods for the positions summary page. - * Removed 'all' to optimize for speed - use report page for comprehensive analysis. */ -export type EarningsPeriod = 'day' | 'week' | 'month'; +export type EarningsPeriod = 'day' | 'week' | 'month' | 'sixmonth' | 'all'; type PositionsFiltersState = { /** Currently selected earnings period */ diff --git a/src/utils/positions.ts b/src/utils/positions.ts index e2fff740..880af99f 100644 --- a/src/utils/positions.ts +++ b/src/utils/positions.ts @@ -68,7 +68,7 @@ function convertSharesToAssets(shares: bigint, totalAssets: bigint, totalShares: * @param marketIds - Array of market unique IDs * @param userAddress - The user's address * @param chainId - The chain ID of the network - * @param blockNumber - The block number to fetch positions at (0 for latest) + * @param blockNumber - The block number to fetch positions at (undefined for latest) * @param client - The viem PublicClient to use for the request * @returns Map of marketId to PositionSnapshot */ @@ -76,7 +76,7 @@ export async function fetchPositionsSnapshots( marketIds: string[], userAddress: Address, chainId: number, - blockNumber: number, + blockNumber: number | undefined, client: PublicClient, ): Promise> { const result = new Map(); @@ -86,7 +86,7 @@ export async function fetchPositionsSnapshots( } try { - const isNow = blockNumber === 0; + const isLatest = blockNumber === undefined; const morphoAddress = getMorphoAddress(chainId as SupportedNetworks); // Step 1: Multicall to get all position data @@ -100,7 +100,7 @@ export async function fetchPositionsSnapshots( const positionResults = await client.multicall({ contracts: positionContracts, allowFailure: true, - blockNumber: isNow ? undefined : BigInt(blockNumber), + blockNumber: isLatest ? undefined : BigInt(blockNumber), }); // Process position results and identify which markets need market data @@ -143,7 +143,7 @@ export async function fetchPositionsSnapshots( const marketResults = await client.multicall({ contracts: marketContracts, allowFailure: true, - blockNumber: isNow ? undefined : BigInt(blockNumber), + blockNumber: isLatest ? undefined : BigInt(blockNumber), }); // Process market results and create final snapshots @@ -192,7 +192,7 @@ export async function fetchPositionsSnapshots( * @param marketId - The unique ID of the market * @param userAddress - The user's address * @param chainId - The chain ID of the network - * @param blockNumber - The block number to fetch the position at (0 for latest) + * @param blockNumber - The block number to fetch the position at (undefined for latest) * @param client - The viem PublicClient to use for the request * @returns The position snapshot or null if there was an error */ @@ -200,7 +200,7 @@ export async function fetchPositionSnapshot( marketId: string, userAddress: Address, chainId: number, - blockNumber: number, + blockNumber: number | undefined, client: PublicClient, ): Promise { const snapshots = await fetchPositionsSnapshots([marketId], userAddress, chainId, blockNumber, client); @@ -212,7 +212,7 @@ export async function fetchPositionSnapshot( * * @param marketId - The unique ID of the market * @param chainId - The chain ID of the network - * @param blockNumber - The block number to fetch the market at (0 for latest) + * @param blockNumber - The block number to fetch the market at (undefined for latest) * @param client - The viem PublicClient to use for the request * @returns The market snapshot or null if there was an error */ @@ -220,10 +220,10 @@ export async function fetchMarketSnapshot( marketId: string, chainId: number, client: PublicClient, - blockNumber = 0, + blockNumber?: number, ): Promise { try { - const isNow = !blockNumber || blockNumber === 0; + const isLatest = blockNumber === undefined; // Get the market data const marketArray = (await client.readContract({ @@ -231,7 +231,7 @@ export async function fetchMarketSnapshot( abi: morphoABI, functionName: 'market', args: [marketId as `0x${string}`], - blockNumber: isNow ? undefined : BigInt(blockNumber!), + blockNumber: isLatest ? undefined : BigInt(blockNumber), })) as readonly bigint[]; // Convert array to market object