From 20a1aa05dfd481a0f14283d202a3c88e4548a119 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Tue, 31 Mar 2026 13:19:37 +0100 Subject: [PATCH 1/2] [#672] Add USD values across dashboards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add muted (≈ $X.XX) USD equivalents next to PLOT values using the existing usePlotUsdPrice() hook and formatUsdValue() utility. - WriterTradingStats: token price and TVL - ClaimRoyalties: unclaimed royalties amount - ReaderPortfolio: total value and per-holding value - TradingHistory: trade amounts Single price fetch per component via usePlotUsdPrice(), no per-row API calls. USD shown only when price data is available. Fixes realproject7/plotlink#672 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/dashboard/reader/page.tsx | 8 ++++++++ src/components/ClaimRoyalties.tsx | 8 ++++++++ src/components/ReaderPortfolio.tsx | 13 +++++++++++++ src/components/WriterTradingStats.tsx | 13 +++++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/app/dashboard/reader/page.tsx b/src/app/dashboard/reader/page.tsx index bbc2adb3..7650adb3 100644 --- a/src/app/dashboard/reader/page.tsx +++ b/src/app/dashboard/reader/page.tsx @@ -11,6 +11,8 @@ import { formatUnits } from "viem"; import { ConnectWallet } from "../../../components/ConnectWallet"; import { RESERVE_LABEL, PLOT_TOKEN, STORY_FACTORY, EXPLORER_URL } from "../../../../lib/contracts/constants"; import { browserClient as publicClient } from "../../../../lib/rpc"; +import { formatUsdValue } from "../../../../lib/usd-price"; +import { usePlotUsdPrice } from "../../../hooks/usePlotUsdPrice"; import { type Address } from "viem"; /** Truncate formatUnits output to at most `digits` decimal places */ @@ -189,6 +191,7 @@ function DonationRow({ donation, decimals }: { donation: Donation; decimals: num const TRADE_PAGE_SIZE = 10; function TradingHistory({ address }: { address: string }) { + const { data: plotUsd } = usePlotUsdPrice(); const { data, isLoading, @@ -287,6 +290,11 @@ function TradingHistory({ address }: { address: string }) {
{formatPrice(t.reserve_amount)} {RESERVE_LABEL} + {plotUsd && ( + + ({formatUsdValue(t.reserve_amount * plotUsd)}) + + )} {t.tx_hash && ( BigInt(0) ? "text-accent" : "text-foreground"}`}> {formatTruncated(unclaimed, decimals)} {RESERVE_LABEL} + {unclaimed > BigInt(0) && plotUsd && ( + + ({formatUsdValue(parseFloat(formatUnits(unclaimed, decimals)) * plotUsd)}) + + )} {totalClaimed > BigInt(0) && ( (claimed: {formatTruncated(totalClaimed, decimals)} {RESERVE_LABEL} so far) diff --git a/src/components/ReaderPortfolio.tsx b/src/components/ReaderPortfolio.tsx index abc27c8c..b92fa5e6 100644 --- a/src/components/ReaderPortfolio.tsx +++ b/src/components/ReaderPortfolio.tsx @@ -9,6 +9,8 @@ import { erc20Abi, mcv2BondAbi, get24hPriceChange, getTokenTVL } from "../../lib import { MCV2_BOND, RESERVE_LABEL, STORY_FACTORY } from "../../lib/contracts/constants"; import { supabase, type Storyline } from "../../lib/supabase"; import Link from "next/link"; +import { formatUsdValue } from "../../lib/usd-price"; +import { usePlotUsdPrice } from "../hooks/usePlotUsdPrice"; interface Holding { storyline: Storyline; @@ -21,6 +23,7 @@ interface Holding { export function ReaderPortfolio() { const { address, isConnected } = useAccount(); + const { data: plotUsd } = usePlotUsdPrice(); const { data: holdings, isLoading } = useQuery({ queryKey: ["reader-portfolio", address], queryFn: async (): Promise => { @@ -128,6 +131,11 @@ export function ReaderPortfolio() { {formatPrice(formatUnits(totalValue, reserveDecimals))} {RESERVE_LABEL} + {plotUsd && ( + + ({formatUsdValue(parseFloat(formatUnits(totalValue, reserveDecimals)) * plotUsd)}) + + )}
{bestPick && bestPick.priceChange !== null && (
@@ -166,6 +174,11 @@ export function ReaderPortfolio() {
{formatPrice(formatUnits(h.value, h.reserveDecimals))} {RESERVE_LABEL} + {plotUsd && ( + + ({formatUsdValue(parseFloat(formatUnits(h.value, h.reserveDecimals)) * plotUsd)}) + + )}
{h.priceChange !== null && (
{data ? `${formatPrice(data.price)} ${RESERVE_LABEL}` : "—"} + {data && plotUsd && ( + + ({formatUsdValue(parseFloat(data.price) * plotUsd)}) + + )}
@@ -57,6 +65,11 @@ export function WriterTradingStats({ storyline }: WriterTradingStatsProps) { {data ? `${formatPrice(data.tvl)} ${RESERVE_LABEL}` : "—"} + {data && plotUsd && ( + + ({formatUsdValue(parseFloat(data.tvl) * plotUsd)}) + + )}
); From d17ac30bc45e7afd695257d7dcda74c5f1440e12 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Tue, 31 Mar 2026 13:26:23 +0100 Subject: [PATCH 2/2] =?UTF-8?q?[#672]=20Add=20=E2=89=88=20prefix=20and=20l?= =?UTF-8?q?ift=20price=20fetch=20to=20dashboard=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - All USD tags now show (≈ $X.XX) format per spec - Price fetched once via usePlotUsdPrice() at WriterDashboard and ReaderDashboard level, passed as plotUsd prop to child components - Removed per-component usePlotUsdPrice() calls from WriterTradingStats, ClaimRoyalties, ReaderPortfolio, TradingHistory Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/dashboard/reader/page.tsx | 10 +++++----- src/app/dashboard/writer/page.tsx | 9 ++++++--- src/components/ClaimRoyalties.tsx | 7 +++---- src/components/ReaderPortfolio.tsx | 8 +++----- src/components/WriterTradingStats.tsx | 9 ++++----- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/app/dashboard/reader/page.tsx b/src/app/dashboard/reader/page.tsx index 7650adb3..0532b28e 100644 --- a/src/app/dashboard/reader/page.tsx +++ b/src/app/dashboard/reader/page.tsx @@ -27,6 +27,7 @@ const PAGE_SIZE = 10; export default function ReaderDashboard() { const { address, isConnected } = useAccount(); + const { data: plotUsd } = usePlotUsdPrice(); const { data, @@ -99,10 +100,10 @@ export default function ReaderDashboard() {

- + {/* --- Trading History --- */} - + {/* --- Donation History --- */}
@@ -190,8 +191,7 @@ function DonationRow({ donation, decimals }: { donation: Donation; decimals: num const TRADE_PAGE_SIZE = 10; -function TradingHistory({ address }: { address: string }) { - const { data: plotUsd } = usePlotUsdPrice(); +function TradingHistory({ address, plotUsd }: { address: string; plotUsd?: number | null }) { const { data, isLoading, @@ -292,7 +292,7 @@ function TradingHistory({ address }: { address: string }) { {formatPrice(t.reserve_amount)} {RESERVE_LABEL} {plotUsd && ( - ({formatUsdValue(t.reserve_amount * plotUsd)}) + (≈ {formatUsdValue(t.reserve_amount * plotUsd)}) )} diff --git a/src/app/dashboard/writer/page.tsx b/src/app/dashboard/writer/page.tsx index cffb5348..ff2415ac 100644 --- a/src/app/dashboard/writer/page.tsx +++ b/src/app/dashboard/writer/page.tsx @@ -18,6 +18,7 @@ import { truncateAddress } from "../../../../lib/utils"; import { formatPrice } from "../../../../lib/format"; import Link from "next/link"; import { ConnectWallet } from "../../../components/ConnectWallet"; +import { usePlotUsdPrice } from "../../../hooks/usePlotUsdPrice"; import { type Address } from "viem"; function formatViewCountDashboard(n: number): string { @@ -51,6 +52,7 @@ const languageOptions = LANGUAGES.map((l) => ({ value: l, label: l })); export default function WriterDashboard() { const { address, isConnected } = useAccount(); + const { data: plotUsd } = usePlotUsdPrice(); const { data: storylines = [], isLoading, error } = useQuery({ queryKey: ["writer-storylines", address], @@ -91,7 +93,7 @@ export default function WriterDashboard() {
{storylines.map((s) => ( - + ))} {!isLoading && !error && storylines.length === 0 && (

@@ -103,7 +105,7 @@ export default function WriterDashboard() { ); } -function StorylineDetail({ storyline, writerAddress }: { storyline: Storyline; writerAddress: Address }) { +function StorylineDetail({ storyline, writerAddress, plotUsd }: { storyline: Storyline; writerAddress: Address; plotUsd?: number | null }) { return (

@@ -172,11 +174,12 @@ function StorylineDetail({ storyline, writerAddress }: { storyline: Storyline; w {storyline.token_address && (
- +
)} diff --git a/src/components/ClaimRoyalties.tsx b/src/components/ClaimRoyalties.tsx index ab00004d..53bf69b8 100644 --- a/src/components/ClaimRoyalties.tsx +++ b/src/components/ClaimRoyalties.tsx @@ -8,7 +8,6 @@ import { browserClient } from "../../lib/rpc"; import { mcv2BondAbi, getTokenTVL } from "../../lib/price"; import { MCV2_BOND, RESERVE_LABEL, EXPLORER_URL, PLOT_TOKEN } from "../../lib/contracts/constants"; import { formatUsdValue } from "../../lib/usd-price"; -import { usePlotUsdPrice } from "../hooks/usePlotUsdPrice"; function formatTruncated(value: bigint, decimals: number, digits = 10): string { const raw = formatUnits(value, decimals); @@ -24,9 +23,10 @@ interface ClaimRoyaltiesProps { tokenAddress: Address; plotCount: number; beneficiary: Address; + plotUsd?: number | null; } -export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary }: ClaimRoyaltiesProps) { +export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary, plotUsd }: ClaimRoyaltiesProps) { const [txState, setTxState] = useState("idle"); const [error, setError] = useState(null); const [claimedAmount, setClaimedAmount] = useState(BigInt(0)); @@ -34,7 +34,6 @@ export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary }: ClaimRo const [showTooltip, setShowTooltip] = useState(false); const { writeContractAsync } = useWriteContract(); - const { data: plotUsd } = usePlotUsdPrice(); // Fetch unclaimed royalty balance + cumulative claimed const { data: royaltyInfo } = useQuery({ @@ -151,7 +150,7 @@ export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary }: ClaimRo {unclaimed > BigInt(0) && plotUsd && ( - ({formatUsdValue(parseFloat(formatUnits(unclaimed, decimals)) * plotUsd)}) + (≈ {formatUsdValue(parseFloat(formatUnits(unclaimed, decimals)) * plotUsd)}) )} {totalClaimed > BigInt(0) && ( diff --git a/src/components/ReaderPortfolio.tsx b/src/components/ReaderPortfolio.tsx index b92fa5e6..c3880fa9 100644 --- a/src/components/ReaderPortfolio.tsx +++ b/src/components/ReaderPortfolio.tsx @@ -10,7 +10,6 @@ import { MCV2_BOND, RESERVE_LABEL, STORY_FACTORY } from "../../lib/contracts/con import { supabase, type Storyline } from "../../lib/supabase"; import Link from "next/link"; import { formatUsdValue } from "../../lib/usd-price"; -import { usePlotUsdPrice } from "../hooks/usePlotUsdPrice"; interface Holding { storyline: Storyline; @@ -21,9 +20,8 @@ interface Holding { reserveDecimals: number; } -export function ReaderPortfolio() { +export function ReaderPortfolio({ plotUsd }: { plotUsd?: number | null }) { const { address, isConnected } = useAccount(); - const { data: plotUsd } = usePlotUsdPrice(); const { data: holdings, isLoading } = useQuery({ queryKey: ["reader-portfolio", address], queryFn: async (): Promise => { @@ -133,7 +131,7 @@ export function ReaderPortfolio() { {plotUsd && ( - ({formatUsdValue(parseFloat(formatUnits(totalValue, reserveDecimals)) * plotUsd)}) + (≈ {formatUsdValue(parseFloat(formatUnits(totalValue, reserveDecimals)) * plotUsd)}) )}
@@ -176,7 +174,7 @@ export function ReaderPortfolio() { {formatPrice(formatUnits(h.value, h.reserveDecimals))} {RESERVE_LABEL} {plotUsd && ( - ({formatUsdValue(parseFloat(formatUnits(h.value, h.reserveDecimals)) * plotUsd)}) + (≈ {formatUsdValue(parseFloat(formatUnits(h.value, h.reserveDecimals)) * plotUsd)}) )}
diff --git a/src/components/WriterTradingStats.tsx b/src/components/WriterTradingStats.tsx index 722dccfa..3837d004 100644 --- a/src/components/WriterTradingStats.tsx +++ b/src/components/WriterTradingStats.tsx @@ -7,16 +7,15 @@ import { mcv2BondAbi, getTokenTVL } from "../../lib/price"; import { MCV2_BOND, RESERVE_LABEL } from "../../lib/contracts/constants"; import { formatPrice } from "../../lib/format"; import { formatUsdValue } from "../../lib/usd-price"; -import { usePlotUsdPrice } from "../hooks/usePlotUsdPrice"; import type { Storyline } from "../../lib/supabase"; interface WriterTradingStatsProps { storyline: Storyline; + plotUsd?: number | null; } -export function WriterTradingStats({ storyline }: WriterTradingStatsProps) { +export function WriterTradingStats({ storyline, plotUsd }: WriterTradingStatsProps) { const tokenAddress = storyline.token_address as Address; - const { data: plotUsd } = usePlotUsdPrice(); // Fetch price + TVL together so they succeed/fail atomically const { data } = useQuery({ @@ -54,7 +53,7 @@ export function WriterTradingStats({ storyline }: WriterTradingStatsProps) { {data && plotUsd && ( - ({formatUsdValue(parseFloat(data.price) * plotUsd)}) + (≈ {formatUsdValue(parseFloat(data.price) * plotUsd)}) )}
@@ -67,7 +66,7 @@ export function WriterTradingStats({ storyline }: WriterTradingStatsProps) { {data && plotUsd && ( - ({formatUsdValue(parseFloat(data.tvl) * plotUsd)}) + (≈ {formatUsdValue(parseFloat(data.tvl) * plotUsd)}) )}