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
82 changes: 82 additions & 0 deletions src/app/discover/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createServerClient, type Storyline } from "../../../lib/supabase";
import { StoryCard } from "../../components/StoryCard";
import { TabNav } from "../../components/TabNav";

type SearchParams = Promise<{ tab?: string }>;

const TABS = ["new", "trending", "rising", "completed"] as const;
type Tab = (typeof TABS)[number];

export default async function DiscoverPage({
searchParams,
}: {
searchParams: SearchParams;
}) {
const { tab: rawTab } = await searchParams;
const tab: Tab = TABS.includes(rawTab as Tab) ? (rawTab as Tab) : "new";

const supabase = createServerClient();
if (!supabase) {
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-muted text-sm">Database unavailable</p>
</div>
);
}

let storylines: Storyline[] = [];

if (tab === "completed") {
const { data } = await supabase
.from("storylines")
.select("*")
.eq("hidden", false)
.eq("sunset", true)
.order("plot_count", { ascending: false })
.limit(50)
.returns<Storyline[]>();
storylines = data ?? [];
} else {
// "new" is the default; "trending" and "rising" fall back to "new" ordering
// until trading data is available (Phase 5)
const { data } = await supabase
.from("storylines")
.select("*")
.eq("hidden", false)
.eq("sunset", false)
.order("block_timestamp", { ascending: false })
.limit(50)
.returns<Storyline[]>();
storylines = data ?? [];
}

return (
<div className="mx-auto max-w-2xl px-6 py-12">
<h1 className="text-accent text-2xl font-bold tracking-tight">
Discover
</h1>
<p className="text-muted mt-2 text-sm">
Browse stories on PlotLink
</p>

<TabNav tabs={TABS} active={tab} className="mt-6" />

{(tab === "trending" || tab === "rising") && (
<p className="text-muted mt-4 text-xs italic">
Ranking by recency — trading-based ranking available after Phase 5.
</p>
)}

<div className="mt-6 space-y-3">
{storylines.map((s) => (
<StoryCard key={s.id} storyline={s} genre="fiction" />
))}
{storylines.length === 0 && (
<p className="text-muted py-8 text-center text-sm">
No stories found.
</p>
)}
</div>
</div>
);
}
47 changes: 47 additions & 0 deletions src/components/StoryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Link from "next/link";
import type { Storyline } from "../../lib/supabase";

function truncateAddress(address: string): string {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}

export function StoryCard({
storyline,
genre,
}: {
storyline: Storyline;
genre?: string;
}) {
return (
<Link
href={`/story/${storyline.storyline_id}`}
className="border-border hover:border-accent-dim block rounded border px-4 py-3 transition-colors"
>
<div className="flex items-start justify-between gap-3">
<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>
)}
</div>
<div className="text-muted mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs">
<span>{truncateAddress(storyline.writer_address)}</span>
<span>
{storyline.plot_count}{" "}
{storyline.plot_count === 1 ? "plot" : "plots"}
</span>
{genre && (
<span className="border-border rounded border px-1.5 py-0.5 text-[10px]">
{genre}
</span>
)}
{storyline.writer_type === 1 && (
<span className="border-accent-dim text-accent-dim rounded border px-1.5 py-0.5 text-[10px]">
agent
</span>
)}
</div>
</Link>
);
}
29 changes: 29 additions & 0 deletions src/components/TabNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Link from "next/link";

export function TabNav({
tabs,
active,
className,
}: {
tabs: readonly string[];
active: string;
className?: string;
}) {
return (
<nav className={`border-border flex gap-1 border-b ${className ?? ""}`}>
{tabs.map((tab) => (
<Link
key={tab}
href={`/discover?tab=${tab}`}
className={`px-3 py-2 text-xs transition-colors ${
tab === active
? "border-accent text-accent -mb-px border-b-2 font-medium"
: "text-muted hover:text-foreground"
}`}
>
{tab}
</Link>
))}
</nav>
);
}
Loading