Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 61 additions & 64 deletions src/app/dashboard/reader/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<DonationPage> {
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<Donation[]>();
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<Donation[]>();
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,
});

Expand All @@ -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 (
Expand All @@ -95,8 +88,6 @@ export default function ReaderDashboard() {
BigInt(0),
);

const hasMore = donations.length < totalCount;

return (
<div className="mx-auto max-w-2xl px-6 py-12">
<h1 className="text-accent text-2xl font-bold tracking-tight">
Expand All @@ -121,7 +112,7 @@ export default function ReaderDashboard() {
{donations.length > 0 && (
<span>
{" "}
&middot; {formatTruncated(totalDonated, reserveDecimals)} {RESERVE_LABEL} on this page
&middot; {formatTruncated(totalDonated, reserveDecimals)} {RESERVE_LABEL} total loaded
</span>
)}
</p>
Expand All @@ -145,12 +136,13 @@ export default function ReaderDashboard() {
)}
</div>

{hasMore && (
{hasNextPage && (
<button
onClick={() => setLimit((l) => l + PAGE_SIZE)}
className="text-accent hover:text-foreground mt-4 w-full text-center text-xs transition-colors"
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
className="text-accent hover:text-foreground mt-4 w-full text-center text-xs transition-colors disabled:opacity-50"
>
Load more ({totalCount - donations.length} remaining)
{isFetchingNextPage ? "Loading..." : `Load more (${totalCount - donations.length} remaining)`}
</button>
)}
</section>
Expand Down Expand Up @@ -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<TradeHistory[]>();
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 (
<section className="mt-8">
Expand Down Expand Up @@ -287,12 +283,13 @@ function TradingHistory({ address }: { address: string }) {
)}
</div>

{hasMore && (
{hasNextPage && (
<button
onClick={() => setLimit((l) => l + TRADE_PAGE_SIZE)}
className="text-accent hover:text-foreground mt-4 w-full text-center text-xs transition-colors"
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
className="text-accent hover:text-foreground mt-4 w-full text-center text-xs transition-colors disabled:opacity-50"
>
Load more ({totalCount - trades.length} remaining)
{isFetchingNextPage ? "Loading..." : `Load more (${totalCount - trades.length} remaining)`}
</button>
)}
</section>
Expand Down
39 changes: 24 additions & 15 deletions src/app/dashboard/writer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<Donation[]>();
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;

Expand Down Expand Up @@ -255,12 +263,13 @@ function WriterDonationHistory({ storylineId }: { storylineId: number }) {
</div>
))}
</div>
{hasMore && (
{hasNextPage && (
<button
onClick={() => setLimit((l) => l + DONATION_PAGE_SIZE)}
className="text-accent hover:text-foreground mt-2 w-full text-center text-[10px] transition-colors"
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
className="text-accent hover:text-foreground mt-2 w-full text-center text-[10px] transition-colors disabled:opacity-50"
>
Load more ({totalCount - donations.length} remaining)
{isFetchingNextPage ? "Loading..." : `Load more (${totalCount - donations.length} remaining)`}
</button>
)}
</div>
Expand Down
Loading