diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index d7aa6678..2108efc5 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -1441,13 +1441,24 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile const totalValue = holdings?.reduce((sum, h) => sum + h.value, BigInt(0)) ?? BigInt(0); const reserveDecimals = holdings && holdings.length > 0 ? holdings[0].reserveDecimals : 18; - const bestPick = holdings && holdings.length > 0 - ? holdings.reduce((best, h) => - (h.priceChange ?? -Infinity) > (best.priceChange ?? -Infinity) ? h : best - ) - : null; const totalDonated = donationsGiven.reduce((sum, d) => sum + BigInt(d.amount), BigInt(0)); + // Compute portfolio-level cost basis % change (only if all holdings have entry prices) + const portfolioCostPct = (() => { + if (!holdings || holdings.length === 0 || plotUsd == null) return null; + if (holdings.some(h => h.entryPrice === null || h.entryPrice <= 0)) return null; + let totalCurrentUsd = 0; + let totalCostUsd = 0; + for (const h of holdings) { + const currentPrice = Number(formatUnits(h.price, 18)); + const balanceNum = Number(formatUnits(h.balance, 18)); + totalCurrentUsd += currentPrice * balanceNum * plotUsd; + totalCostUsd += h.entryPrice! * balanceNum * plotUsd; + } + if (totalCostUsd === 0) return null; + return ((totalCurrentUsd - totalCostUsd) / totalCostUsd) * 100; + })(); + return (
{/* Portfolio summary */} @@ -1455,25 +1466,22 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile <>

Portfolio

-
-
-
{formatPrice(formatUnits(totalValue, reserveDecimals))}
-
{RESERVE_LABEL}
-
+
-
{plotUsd ? formatUsdValue(Number(formatUnits(totalValue, reserveDecimals)) * plotUsd) : "—"}
-
USD
+
+ {plotUsd ? formatUsdValue(Number(formatUnits(totalValue, reserveDecimals)) * plotUsd) : "—"} + {portfolioCostPct !== null && ( + = 0 ? "text-accent" : "text-error"}`}> + {portfolioCostPct >= 0 ? "+" : ""}{portfolioCostPct.toFixed(1)}% + + )} +
+
Value
{holdings!.length}
Holdings
-
-
= 0 ? "text-accent" : "text-error") : "text-foreground"}`}> - {bestPick && bestPick.priceChange !== null ? `${bestPick.priceChange >= 0 ? "+" : ""}${bestPick.priceChange.toFixed(1)}%` : "—"} -
-
Best 24h
-
@@ -1527,11 +1535,16 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
{plotUsd ? formatUsdValue(Number(formatUnits(h.value, h.reserveDecimals)) * plotUsd) : "—"} - {h.priceChange !== null && ( - = 0 ? "text-accent" : "text-error"}`}> - {h.priceChange >= 0 ? "+" : ""}{h.priceChange.toFixed(1)}% - - )} + {(() => { + if (h.entryPrice == null || h.entryPrice <= 0 || plotUsd == null) return null; + const currentPrice = Number(formatUnits(h.price, 18)); + const costPct = ((currentPrice - h.entryPrice) / h.entryPrice) * 100; + return ( + = 0 ? "text-accent" : "text-error"}`}> + {costPct >= 0 ? "+" : ""}{costPct.toFixed(1)}% + + ); + })()}
Value
@@ -1540,33 +1553,9 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
{formatCompact(Number(formatUnits(h.balance, 18)))}
Balance
- {/* PnL */} -
- {h.entryPrice !== null && h.entryPrice > 0 && plotUsd != null ? (() => { - const currentPrice = Number(formatUnits(h.price, 18)); - const balanceNum = Number(formatUnits(h.balance, 18)); - const pnlUsd = (currentPrice - h.entryPrice) * balanceNum * plotUsd; - const pnlPct = ((currentPrice - h.entryPrice) / h.entryPrice) * 100; - const isPositive = pnlUsd >= 0; - return ( -
- {isPositive ? "+" : "-"}{formatUsdValue(Math.abs(pnlUsd))} - {isPositive ? "+" : ""}{pnlPct.toFixed(1)}% -
- ); - })() : ( -
- )} -
PnL
-
- {/* First Traded */} -
-
- {h.firstTraded ? new Date(h.firstTraded).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—"} -
-
First Traded
-
+ {/* Recent transactions */} + @@ -1579,6 +1568,48 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile ); } +// --------------------------------------------------------------------------- +// Holding Recent Trades — last 5 transactions for a specific story token +// --------------------------------------------------------------------------- + +function HoldingRecentTrades({ address, storylineId, plotUsd }: { address: string; storylineId: number; plotUsd?: number | null }) { + const { data: trades, isLoading } = useQuery({ + queryKey: ["holding-recent-trades", address, storylineId], + queryFn: async () => { + if (!supabase) return []; + const { data } = await supabase + .from("trade_history") + .select("event_type, reserve_amount, block_timestamp") + .eq("user_address", address) + .eq("storyline_id", storylineId) + .eq("contract_address", MCV2_BOND.toLowerCase()) + .order("block_timestamp", { ascending: false }) + .limit(5); + return data ?? []; + }, + staleTime: 60000, + }); + + if (isLoading || !trades || trades.length === 0) return null; + + return ( +
+ {trades.map((t, i) => { + const isBuy = t.event_type === "mint"; + const date = new Date(t.block_timestamp).toLocaleDateString("en-US", { month: "short", day: "numeric" }); + const amount = plotUsd != null ? formatUsdValue(t.reserve_amount * plotUsd) : `${formatPrice(t.reserve_amount)} ${RESERVE_LABEL}`; + return ( +
+ {isBuy ? "Buy" : "Sell"} + {amount} + {date} +
+ ); + })} +
+ ); +} + // --------------------------------------------------------------------------- // Portfolio Trading History — paginated trades // ---------------------------------------------------------------------------