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
24 changes: 20 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createServerClient, type Storyline } from "../../lib/supabase";
import { getTrendingStorylines, getRisingStorylines } from "../../lib/ranking";
import { StoryCard } from "../components/StoryCard";
import { TabNav } from "../components/TabNav";
import { SortDropdown } from "../components/SortDropdown";
import { WriterFilter, type WriterFilterValue } from "../components/WriterFilter";
import Link from "next/link";

Expand Down Expand Up @@ -30,11 +30,25 @@
const supabase = createServerClient();

let storylines: Storyline[] = [];
const previews: Record<number, string> = {};
if (supabase) {
storylines = await queryTab(supabase, tab, writer);
// Fetch genesis plot previews
if (storylines.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { data: plots } = await (supabase.from("plots") as any)
.select("storyline_id, content")
.in("storyline_id", storylines.map((s) => s.storyline_id))
.eq("plot_index", 0);
if (plots) {
for (const p of plots as { storyline_id: number; content: string }[]) {
previews[p.storyline_id] = p.content.slice(0, 120);
}
}
}
}

const extraParams = writer !== "all" ? { writer } : undefined;

Check warning on line 51 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint-and-typecheck

'extraParams' is assigned a value but never used

return (
<div className="mx-auto max-w-5xl px-6 py-10">
Expand All @@ -49,13 +63,15 @@
</header>

{/* Filter bar */}
<TabNav tabs={TABS} active={tab} basePath="/" extraParams={extraParams} />
<WriterFilter active={writer} tab={tab} basePath="/" className="mt-4" />
<div className="flex flex-wrap items-center justify-between gap-3">
<WriterFilter active={writer} tab={tab} basePath="/" />
<SortDropdown active={tab} writer={writer} basePath="/" />
</div>

{/* Story grid */}
<div className="mt-6 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{storylines.map((s) => (
<StoryCard key={s.id} storyline={s} />
<StoryCard key={s.id} storyline={s} preview={previews[s.storyline_id]} />
))}
</div>

Expand Down
37 changes: 37 additions & 0 deletions src/components/SortDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import { useRouter } from "next/navigation";
import { Select } from "./Select";

const SORT_OPTIONS = [
{ value: "new", label: "Recent" },
{ value: "trending", label: "Trending" },
{ value: "rising", label: "Rising" },
{ value: "completed", label: "Completed" },
];

interface SortDropdownProps {
active: string;
writer: string;
basePath?: string;
}

export function SortDropdown({ active, writer, basePath = "/" }: SortDropdownProps) {
const router = useRouter();

return (
<div className="flex items-center gap-2">
<span className="text-muted text-xs">Sort:</span>
<Select
value={active}
onChange={(v) => {
const params = new URLSearchParams({ tab: v });
if (writer && writer !== "all") params.set("writer", writer);
router.push(`${basePath}?${params.toString()}`);
}}
options={SORT_OPTIONS}
className="w-36"
/>
</div>
);
}
47 changes: 41 additions & 6 deletions src/components/StoryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,75 @@ import Link from "next/link";
import type { Storyline } from "../../lib/supabase";
import { truncateAddress } from "../../lib/utils";
import { AgentBadge } from "./AgentBadge";
import { RatingSummary } from "./RatingSummary";
import { StoryCardStats } from "./StoryCardStats";

export function StoryCard({
storyline,
genre,
preview,
}: {
storyline: Storyline;
genre?: string;
preview?: string;
}) {
const dateStr = storyline.block_timestamp
? new Date(storyline.block_timestamp).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
: null;

return (
<Link
href={`/story/${storyline.storyline_id}`}
className="border-border hover:border-accent-dim block rounded border px-4 py-3 transition-colors"
className="border-border hover:border-accent-dim hover:bg-surface/50 flex flex-col rounded border px-4 py-3 transition-colors"
>
<div className="flex items-start justify-between gap-3">
{/* Title + completion badge */}
<div className="flex items-start justify-between gap-2">
<h3 className="text-foreground text-sm font-medium leading-snug">
{storyline.title}
</h3>
{storyline.sunset && (
<span className="text-muted shrink-0 text-[10px]">complete</span>
<span className="text-muted bg-surface shrink-0 rounded px-1.5 py-0.5 text-[10px]">
complete
</span>
)}
</div>
<div className="text-muted mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs">

{/* Author + meta */}
<div className="text-muted mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
<span>{truncateAddress(storyline.writer_address)}</span>
<span>
{storyline.plot_count}{" "}
{storyline.plot_count === 1 ? "plot" : "plots"}
{storyline.plot_count} {storyline.plot_count === 1 ? "plot" : "plots"}
</span>
{dateStr && <span>{dateStr}</span>}
{genre && (
<span className="border-border rounded border px-1.5 py-0.5 text-[10px]">
{genre}
</span>
)}
{storyline.writer_type === 1 && <AgentBadge />}
</div>

{/* Stats row: price, TVL */}
{storyline.token_address && (
<div className="mt-2">
<StoryCardStats tokenAddress={storyline.token_address} />
</div>
)}

{/* Genesis preview */}
{preview && (
<p className="text-muted mt-2 line-clamp-2 text-[11px] leading-relaxed">
{preview}
</p>
)}

{/* Rating */}
<div className="mt-auto pt-2">
<RatingSummary storylineId={storyline.storyline_id} />
</div>
</Link>
);
}
47 changes: 47 additions & 0 deletions src/components/StoryCardStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { useQuery } from "@tanstack/react-query";
import { formatUnits, type Address } from "viem";

Check warning on line 4 in src/components/StoryCardStats.tsx

View workflow job for this annotation

GitHub Actions / lint-and-typecheck

'formatUnits' is defined but never used
import { getTokenTVL, getTokenPrice } from "../../lib/price";
import { IS_TESTNET } from "../../lib/contracts/constants";

const reserveLabel = IS_TESTNET ? "WETH" : "$PLOT";

function formatCompact(value: string): string {
const num = parseFloat(value);
if (num === 0) return "0";
if (num < 0.0001) return "<0.0001";
if (num < 1) return num.toPrecision(3);
if (num >= 1000) return `${(num / 1000).toFixed(1)}k`;
return num.toFixed(2);
}

export function StoryCardStats({ tokenAddress }: { tokenAddress: string }) {
const addr = tokenAddress as Address;

const { data: priceInfo } = useQuery({
queryKey: ["card-price", tokenAddress],
queryFn: () => getTokenPrice(addr),
staleTime: 60000,
});

const { data: tvlData } = useQuery({
queryKey: ["card-tvl", tokenAddress],
queryFn: () => getTokenTVL(addr),
staleTime: 60000,
});

const price = priceInfo
? formatCompact(priceInfo.pricePerToken)
: "—";
const tvl = tvlData
? formatCompact(tvlData.tvl)
: "—";

return (
<div className="text-muted flex flex-wrap gap-x-3 gap-y-0.5 text-[10px]">
<span>Price: <span className="text-foreground">{price} {reserveLabel}</span></span>
<span>TVL: <span className="text-foreground">{tvl} {reserveLabel}</span></span>
</div>
);
}
4 changes: 2 additions & 2 deletions src/components/WriterFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Link from "next/link";

const WRITER_OPTIONS = [
{ value: "all", label: "All" },
{ value: "human", label: "Human only" },
{ value: "agent", label: "Agent only" },
{ value: "human", label: "Human" },
{ value: "agent", label: "AI" },
] as const;

export type WriterFilterValue = (typeof WRITER_OPTIONS)[number]["value"];
Expand Down
Loading