diff --git a/src/app/dashboard/reader/page.tsx b/src/app/dashboard/reader/page.tsx index 69cea98c..a53a63be 100644 --- a/src/app/dashboard/reader/page.tsx +++ b/src/app/dashboard/reader/page.tsx @@ -1,8 +1,7 @@ "use client"; -import { useEffect, useState } from "react"; import { useAccount } from "wagmi"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useInfiniteQuery } from "@tanstack/react-query"; import { supabase, type Donation, type TradeHistory } from "../../../../lib/supabase"; import { formatPrice } from "../../../../lib/format"; import { ReaderPortfolio } from "../../../components/ReaderPortfolio"; @@ -24,43 +23,37 @@ function formatTruncated(value: bigint, decimals: number, digits = 6): string { const PAGE_SIZE = 10; -interface DonationPage { - rows: Donation[]; - totalCount: number; -} - -async function fetchDonationPage( - address: string, - _page: number, - limit: number = PAGE_SIZE, -): Promise { - if (!supabase) return { rows: [], totalCount: 0 }; - const from = 0; - const to = limit - 1; - const { data, count, error } = await supabase - .from("donations") - .select("*", { count: "exact" }) - .eq("donor_address", address.toLowerCase()) - .eq("contract_address", STORY_FACTORY.toLowerCase()) - .order("block_timestamp", { ascending: false }) - .range(from, to) - .returns(); - if (error) throw error; - return { rows: data ?? [], totalCount: count ?? 0 }; -} - export default function ReaderDashboard() { const { address, isConnected } = useAccount(); - const [limit, setLimit] = useState(PAGE_SIZE); - // Reset when wallet address changes - useEffect(() => { - setLimit(PAGE_SIZE); - }, [address]); - - const { data, isLoading, error } = useQuery({ - queryKey: ["reader-donations", address, limit], - queryFn: () => fetchDonationPage(address!, 0, limit), + const { + data, + isLoading, + isFetchingNextPage, + fetchNextPage, + hasNextPage, + error, + } = useInfiniteQuery({ + queryKey: ["reader-donations", address], + queryFn: async ({ pageParam = 0 }) => { + if (!supabase) return { rows: [] as Donation[], totalCount: 0 }; + const { data: rows, count, error } = await supabase + .from("donations") + .select("*", { count: "exact" }) + .eq("donor_address", address!.toLowerCase()) + .eq("contract_address", STORY_FACTORY.toLowerCase()) + .order("block_timestamp", { ascending: false }) + .range(pageParam, pageParam + PAGE_SIZE - 1) + .returns(); + if (error) throw error; + return { rows: rows ?? [], totalCount: count ?? 0 }; + }, + initialPageParam: 0, + getNextPageParam: (_lastPage, allPages) => { + const totalFetched = allPages.reduce((sum, p) => sum + p.rows.length, 0); + const totalCount = allPages[0]?.totalCount ?? 0; + return totalFetched < totalCount ? totalFetched : undefined; + }, enabled: isConnected && !!address, }); @@ -76,8 +69,8 @@ export default function ReaderDashboard() { }, }); - const donations = data?.rows ?? []; - const totalCount = data?.totalCount ?? 0; + const donations = data?.pages.flatMap((p) => p.rows) ?? []; + const totalCount = data?.pages[0]?.totalCount ?? 0; if (!isConnected) { return ( @@ -95,8 +88,6 @@ export default function ReaderDashboard() { BigInt(0), ); - const hasMore = donations.length < totalCount; - return (

@@ -121,7 +112,7 @@ export default function ReaderDashboard() { {donations.length > 0 && ( {" "} - · {formatTruncated(totalDonated, reserveDecimals)} {RESERVE_LABEL} on this page + · {formatTruncated(totalDonated, reserveDecimals)} {RESERVE_LABEL} total loaded )}

@@ -145,12 +136,13 @@ export default function ReaderDashboard() { )}

- {hasMore && ( + {hasNextPage && ( )} @@ -197,31 +189,35 @@ function DonationRow({ donation, decimals }: { donation: Donation; decimals: num const TRADE_PAGE_SIZE = 10; function TradingHistory({ address }: { address: string }) { - const [limit, setLimit] = useState(TRADE_PAGE_SIZE); - const [prevAddress, setPrevAddress] = useState(address); - if (address !== prevAddress) { - setPrevAddress(address); - setLimit(TRADE_PAGE_SIZE); - } - - const { data, isLoading } = useQuery({ - queryKey: ["reader-trades", address, limit], - queryFn: async () => { - if (!supabase) return { rows: [], totalCount: 0 }; + const { + data, + isLoading, + isFetchingNextPage, + fetchNextPage, + hasNextPage, + } = useInfiniteQuery({ + queryKey: ["reader-trades", address], + queryFn: async ({ pageParam = 0 }) => { + if (!supabase) return { rows: [] as TradeHistory[], totalCount: 0 }; const { data: rows, count } = await supabase .from("trade_history") .select("*", { count: "exact" }) .eq("user_address", address.toLowerCase()) .order("block_timestamp", { ascending: false }) - .range(0, limit - 1) + .range(pageParam, pageParam + TRADE_PAGE_SIZE - 1) .returns(); return { rows: rows ?? [], totalCount: count ?? 0 }; }, + initialPageParam: 0, + getNextPageParam: (_lastPage, allPages) => { + const totalFetched = allPages.reduce((sum, p) => sum + p.rows.length, 0); + const totalCount = allPages[0]?.totalCount ?? 0; + return totalFetched < totalCount ? totalFetched : undefined; + }, }); - const trades = data?.rows ?? []; - const totalCount = data?.totalCount ?? 0; - const hasMore = trades.length < totalCount; + const trades = data?.pages.flatMap((p) => p.rows) ?? []; + const totalCount = data?.pages[0]?.totalCount ?? 0; return (
@@ -287,12 +283,13 @@ function TradingHistory({ address }: { address: string }) { )} - {hasMore && ( + {hasNextPage && ( )}
diff --git a/src/app/dashboard/writer/page.tsx b/src/app/dashboard/writer/page.tsx index 88b7c385..201ddfda 100644 --- a/src/app/dashboard/writer/page.tsx +++ b/src/app/dashboard/writer/page.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useAccount, useSignMessage } from "wagmi"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery, useQueryClient, useInfiniteQuery } from "@tanstack/react-query"; import { formatUnits } from "viem"; import { supabase, type Storyline, type Donation } from "../../../../lib/supabase"; import { getTokenTVL } from "../../../../lib/price"; @@ -188,27 +188,35 @@ function StorylineDetail({ storyline, writerAddress }: { storyline: Storyline; w const DONATION_PAGE_SIZE = 10; function WriterDonationHistory({ storylineId }: { storylineId: number }) { - const [limit, setLimit] = useState(DONATION_PAGE_SIZE); - - const { data } = useQuery({ - queryKey: ["writer-donations", storylineId, limit], - queryFn: async () => { - if (!supabase) return { rows: [], totalCount: 0 }; + const { + data, + isFetchingNextPage, + fetchNextPage, + hasNextPage, + } = useInfiniteQuery({ + queryKey: ["writer-donations", storylineId], + queryFn: async ({ pageParam = 0 }) => { + if (!supabase) return { rows: [] as Donation[], totalCount: 0 }; const { data: rows, count } = await supabase .from("donations") .select("*", { count: "exact" }) .eq("storyline_id", storylineId) .eq("contract_address", STORY_FACTORY.toLowerCase()) .order("block_timestamp", { ascending: false }) - .range(0, limit - 1) + .range(pageParam, pageParam + DONATION_PAGE_SIZE - 1) .returns(); return { rows: rows ?? [], totalCount: count ?? 0 }; }, + initialPageParam: 0, + getNextPageParam: (_lastPage, allPages) => { + const totalFetched = allPages.reduce((sum, p) => sum + p.rows.length, 0); + const totalCount = allPages[0]?.totalCount ?? 0; + return totalFetched < totalCount ? totalFetched : undefined; + }, }); - const donations = data?.rows ?? []; - const totalCount = data?.totalCount ?? 0; - const hasMore = donations.length < totalCount; + const donations = data?.pages.flatMap((p) => p.rows) ?? []; + const totalCount = data?.pages[0]?.totalCount ?? 0; if (donations.length === 0) return null; @@ -255,12 +263,13 @@ function WriterDonationHistory({ storylineId }: { storylineId: number }) { ))} - {hasMore && ( + {hasNextPage && ( )}