From fc6f968de382081c7a53e485ae9b7cbe9dae562a Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Wed, 1 Apr 2026 12:13:55 +0100 Subject: [PATCH 1/2] =?UTF-8?q?[#700]=20Redesign=20Portfolio=20tab=20?= =?UTF-8?q?=E2=80=94=20compact=20summary,=20consistent=20cards,=20collapsi?= =?UTF-8?q?ble=20sections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Portfolio summary: compact horizontal row with value, token count, best 24h pick - Token holdings: inline stats per card (balance, price, entry, date) with middot separators - Donations: unified card for received + given, donations given collapsible - Removed confusing "total loaded" text from donations given header - Trading history: collapsible with compact inline rows (buy/sell, title, amount, date) - Consistent border-border card styling throughout Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 454 ++++++++++++++--------------- 1 file changed, 219 insertions(+), 235 deletions(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index 81b4965a..a7e26d44 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -1389,195 +1389,186 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile const totalDonated = donationsGiven.reduce((sum, d) => sum + BigInt(d.amount), BigInt(0)); return ( -
- {/* Portfolio summary */} +
+ {/* Portfolio summary — compact horizontal row */} {hasHoldings && ( - <> -
-
-
- Portfolio Value - - {formatPrice(formatUnits(totalValue, reserveDecimals))} {RESERVE_LABEL} +
+
+ Portfolio + + {formatPrice(formatUnits(totalValue, reserveDecimals))} {RESERVE_LABEL} + + {plotUsd && ( + ≈ {formatUsdValue(Number(formatUnits(totalValue, reserveDecimals)) * plotUsd)} + )} + · + {holdings!.length} + {holdings!.length === 1 ? "token" : "tokens"} + {bestPick && bestPick.priceChange !== null && ( + <> + · + best 24h: + + {bestPick.storyline.title.slice(0, 20)} + {bestPick.storyline.title.length > 20 ? "..." : ""} - {plotUsd && ( - - ≈ {formatUsdValue(Number(formatUnits(totalValue, reserveDecimals)) * plotUsd)} - - )} - - across {holdings!.length} {holdings!.length === 1 ? "token" : "tokens"} + = 0 ? "text-accent" : "text-error"}> + {bestPick.priceChange >= 0 ? "+" : ""}{bestPick.priceChange.toFixed(1)}% -
- {bestPick && bestPick.priceChange !== null && ( -
- Best Pick (24h) - - {bestPick.storyline.title.slice(0, 20)} - {bestPick.storyline.title.length > 20 ? "..." : ""}{" "} - = 0 ? "text-accent" : "text-error"}> - {bestPick.priceChange >= 0 ? "+" : ""} - {bestPick.priceChange.toFixed(1)}% - - -
- )} -
+ + )}
+
+ )} - {/* Token holdings */} -
-

Token Holdings

- {holdings!.map((h) => ( -
-
-
- - {h.storyline.title} - - {h.storyline.genre && ( - - {h.storyline.genre} - - )} -
-
- - {formatPrice(formatUnits(h.value, h.reserveDecimals))} {RESERVE_LABEL} + {/* Token holdings */} + {hasHoldings && ( +
+ {holdings!.map((h) => ( +
+ {/* Row 1: Title + value */} +
+
+ + {h.storyline.title} + + {h.storyline.genre && ( + + {h.storyline.genre} - {plotUsd && ( - - ({formatUsdValue(Number(formatUnits(h.value, h.reserveDecimals)) * plotUsd)}) - - )} - {h.priceChange !== null && ( -
= 0 ? "text-accent" : "text-error"}`}> - {h.priceChange >= 0 ? "+" : ""}{h.priceChange.toFixed(1)}% -
- )} -
+ )}
-
- - Balance: {formatPrice(formatUnits(h.balance, 18))} tokens - - - Price: {formatPrice(formatUnits(h.price, 18))} {RESERVE_LABEL} - {plotUsd != null && ( - (≈ {formatUsdValue(Number(formatUnits(h.price, 18)) * plotUsd)}) - )} +
+ + {formatPrice(formatUnits(h.value, h.reserveDecimals))} {RESERVE_LABEL} - {h.entryPrice !== null && h.entryPrice > 0 && ( - - Entry: {formatPrice(h.entryPrice)} {RESERVE_LABEL} - {plotUsd != null && ( - (≈ {formatUsdValue(h.entryPrice * plotUsd)}) - )} + {plotUsd && ( + + ({formatUsdValue(Number(formatUnits(h.value, h.reserveDecimals)) * plotUsd)}) )} - {h.lastTraded && ( - - Last traded:{" "} - - {new Date(h.lastTraded).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - })} - + {h.priceChange !== null && ( + = 0 ? "text-accent" : "text-error"}`}> + {h.priceChange >= 0 ? "+" : ""}{h.priceChange.toFixed(1)}% )}
- ))} -
- + {/* Row 2: Inline stats */} +
+ {formatPrice(formatUnits(h.balance, 18))} tokens + · + + price {formatPrice(formatUnits(h.price, 18))} + {plotUsd != null && (≈ {formatUsdValue(Number(formatUnits(h.price, 18)) * plotUsd)})} + + {h.entryPrice !== null && h.entryPrice > 0 && ( + <> + · + + entry {formatPrice(h.entryPrice)} + {plotUsd != null && (≈ {formatUsdValue(h.entryPrice * plotUsd)})} + + + )} + {h.lastTraded && ( + <> + · + + {new Date(h.lastTraded).toLocaleDateString("en-US", { month: "short", day: "numeric" })} + + + )} +
+
+ ))} +
)} - {/* Donations received as writer */} - {hasDonationsReceived && ( -
-

Donations Received

- - {formatPrice(formatUnits(donationsReceived!.total, 18))} {RESERVE_LABEL} - - {plotUsd != null && ( - (≈ {formatUsdValue(Number(formatUnits(donationsReceived!.total, 18)) * plotUsd)}) + {/* Donations — consistent card styling for both received and given */} + {(hasDonationsReceived || (isOwnProfile && hasDonationsGiven)) && ( +
+ {hasDonationsReceived && ( +
+ Received + + {formatPrice(formatUnits(donationsReceived!.total, 18))} {RESERVE_LABEL} + + {plotUsd != null && ( + (≈ {formatUsdValue(Number(formatUnits(donationsReceived!.total, 18)) * plotUsd)}) + )} + from {donationsReceived!.count} {donationsReceived!.count === 1 ? "donation" : "donations"} +
)} - - from {donationsReceived!.count} {donationsReceived!.count === 1 ? "donation" : "donations"} - -
- )} - {/* Donations given as reader — own profile only, paginated */} - {isOwnProfile && hasDonationsGiven && ( -
-

- Donations Given - {donationTotalCount > 0 && ( - - {donationTotalCount} {donationTotalCount === 1 ? "donation" : "donations"} - {totalDonated > BigInt(0) && ( - <> · {formatPrice(formatUnits(totalDonated, 18))} {RESERVE_LABEL}{plotUsd != null && ` (≈ ${formatUsdValue(Number(formatUnits(totalDonated, 18)) * plotUsd)})`} total loaded + {isOwnProfile && hasDonationsGiven && ( +

+ + Given + + {formatPrice(formatUnits(totalDonated, 18))} {RESERVE_LABEL} + + {plotUsd != null && totalDonated > BigInt(0) && ( + (≈ {formatUsdValue(Number(formatUnits(totalDonated, 18)) * plotUsd)}) )} - - )} -

-
- {donationsGiven.map((d) => ( -
-
- {donationTotalCount} {donationTotalCount === 1 ? "donation" : "donations"} + + +
+
+ {donationsGiven.map((d) => ( +
+
+ + Story #{d.storyline_id} + + {d.block_timestamp && ( + + )} +
+
+ + {formatPrice(formatUnits(BigInt(d.amount), 18))} {RESERVE_LABEL} + + {plotUsd != null && ( + (≈ {formatUsdValue(Number(formatUnits(BigInt(d.amount), 18)) * plotUsd)}) + )} + {d.tx_hash && ( + + ↗ + + )} +
+
+ ))} + {donHasNext && ( +
-
- - {formatPrice(formatUnits(BigInt(d.amount), 18))} {RESERVE_LABEL} - - {plotUsd != null && ( - (≈ {formatUsdValue(Number(formatUnits(BigInt(d.amount), 18)) * plotUsd)}) - )} - {d.tx_hash && ( - - ↗ - - )} -
+ {donFetchingNext ? "Loading..." : `Load more (${donationTotalCount - donationsGiven.length} remaining)`} + + )}
- ))} -
- {donHasNext && ( - + )}
)} @@ -1646,15 +1637,16 @@ function PortfolioTradingHistory({ address, plotUsd }: { address: string; plotUs if (trades.length === 0) return null; return ( -
-

- Trading History - - {totalCount} {totalCount === 1 ? "trade" : "trades"} - -

- -
+
+ + Trades + {totalCount} + {totalCount === 1 ? "trade" : "trades"} + + + + +
{trades.map((t) => { const isBuy = t.event_type === "mint"; const title = storylineTitles?.[t.storyline_id]; @@ -1662,58 +1654,48 @@ function PortfolioTradingHistory({ address, plotUsd }: { address: string; plotUs return (
-
-
-
- - {isBuy ? "Buy" : "Sell"} - - - {title || `Story #${t.storyline_id}`} - -
-
- {tokenCount > 0 && ( - {formatSupply(tokenCount)} tokens - )} - {t.block_timestamp && ( - - )} -
-
-
- - {formatPrice(t.reserve_amount)} {RESERVE_LABEL} - {plotUsd && ( - - (≈ {formatUsdValue(t.reserve_amount * plotUsd)}) - - )} +
+ + {isBuy ? "Buy" : "Sell"} + + + {title || `Story #${t.storyline_id}`} + + {tokenCount > 0 && ( + {formatSupply(tokenCount)} tokens + )} + {t.block_timestamp && ( + + )} +
+
+ + {formatPrice(t.reserve_amount)} {RESERVE_LABEL} + + {plotUsd && ( + + (≈ {formatUsdValue(t.reserve_amount * plotUsd)}) - {t.tx_hash && ( - - ↗ - - )} -
+ )} + {t.tx_hash && ( + + ↗ + + )}
); @@ -1721,15 +1703,17 @@ function PortfolioTradingHistory({ address, plotUsd }: { address: string; plotUs
{hasNextPage && ( - +
+ +
)} -
+
); } From 908927e96a1d0741873f9ac3e5367af5e434f479 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Wed, 1 Apr 2026 12:16:36 +0100 Subject: [PATCH 2/2] [#700] Fix T2a review: restore RESERVE_LABEL on price and entry fields Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/profile/[address]/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx index a7e26d44..6cf9abed 100644 --- a/src/app/profile/[address]/page.tsx +++ b/src/app/profile/[address]/page.tsx @@ -1465,14 +1465,14 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile {formatPrice(formatUnits(h.balance, 18))} tokens · - price {formatPrice(formatUnits(h.price, 18))} + price {formatPrice(formatUnits(h.price, 18))} {RESERVE_LABEL} {plotUsd != null && (≈ {formatUsdValue(Number(formatUnits(h.price, 18)) * plotUsd)})} {h.entryPrice !== null && h.entryPrice > 0 && ( <> · - entry {formatPrice(h.entryPrice)} + entry {formatPrice(h.entryPrice)} {RESERVE_LABEL} {plotUsd != null && (≈ {formatUsdValue(h.entryPrice * plotUsd)})}