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
14 changes: 9 additions & 5 deletions lib/ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ function computeTrendScore(
}

/** Shared: fetch storyline candidates + batch ratings */
async function fetchCandidatesAndRatings(supabase: SupabaseClient) {
async function fetchCandidatesAndRatings(supabase: SupabaseClient, writerType?: number) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { data } = await (supabase.from("storylines") as any)
let q = (supabase.from("storylines") as any)
.select("*")
.eq("hidden", false)
.eq("sunset", false)
.neq("token_address", "")
.neq("token_address", "");
if (writerType !== undefined) q = q.eq("writer_type", writerType);
const { data } = await q
.order("block_timestamp", { ascending: false })
.limit(50);

Expand Down Expand Up @@ -115,8 +117,9 @@ async function enrichWithOnChain(
export async function getTrendingStorylines(
supabase: SupabaseClient,
limit = 20,
writerType?: number,
): Promise<RankedStoryline[]> {
const { storylines, ratingMap } = await fetchCandidatesAndRatings(supabase);
const { storylines, ratingMap } = await fetchCandidatesAndRatings(supabase, writerType);
if (storylines.length === 0) return [];

const enriched = await Promise.all(
Expand Down Expand Up @@ -153,8 +156,9 @@ export async function getTrendingStorylines(
export async function getRisingStorylines(
supabase: SupabaseClient,
limit = 20,
writerType?: number,
): Promise<RankedStoryline[]> {
const { storylines } = await fetchCandidatesAndRatings(supabase);
const { storylines } = await fetchCandidatesAndRatings(supabase, writerType);
if (storylines.length === 0) return [];

const now = new Date();
Expand Down
50 changes: 40 additions & 10 deletions src/app/discover/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@ import { createServerClient, type Storyline } from "../../../lib/supabase";
import { getTrendingStorylines, getRisingStorylines } from "../../../lib/ranking";
import { StoryCard } from "../../components/StoryCard";
import { TabNav } from "../../components/TabNav";
import {
WriterFilter,
type WriterFilterValue,
} from "../../components/WriterFilter";

export const revalidate = 120; // ISR: regenerate at most every 2 minutes

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

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

const WRITER_VALUES: WriterFilterValue[] = ["all", "human", "agent"];

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

const supabase = createServerClient();
if (!supabase) {
Expand All @@ -27,7 +38,10 @@ export default async function DiscoverPage({
);
}

const storylines = await queryTab(supabase, tab);
const storylines = await queryTab(supabase, tab, writer);

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

return (
<div className="mx-auto max-w-2xl px-6 py-12">
Expand All @@ -36,7 +50,14 @@ export default async function DiscoverPage({
</h1>
<p className="text-muted mt-2 text-sm">Browse stories on PlotLink</p>

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

<WriterFilter active={writer} tab={tab} className="mt-4" />

<div className="mt-6 space-y-3">
{storylines.map((s) => (
Expand All @@ -55,38 +76,47 @@ export default async function DiscoverPage({
async function queryTab(
supabase: ReturnType<typeof createServerClient> & object,
tab: Tab,
writer: WriterFilterValue,
): Promise<Storyline[]> {
switch (tab) {
case "new": {
const { data } = await supabase
let q = supabase
.from("storylines")
.select("*")
.eq("hidden", false)
.eq("sunset", false)
.eq("sunset", false);
if (writer === "human") q = q.eq("writer_type", 0);
if (writer === "agent") q = q.eq("writer_type", 1);
const { data } = await q
.order("block_timestamp", { ascending: false })
.limit(50)
.returns<Storyline[]>();
return data ?? [];
}

case "completed": {
const { data } = await supabase
let q = supabase
.from("storylines")
.select("*")
.eq("hidden", false)
.eq("sunset", true)
.eq("sunset", true);
if (writer === "human") q = q.eq("writer_type", 0);
if (writer === "agent") q = q.eq("writer_type", 1);
const { data } = await q
.order("plot_count", { ascending: false })
.limit(50)
.returns<Storyline[]>();
return data ?? [];
}

case "trending": {
return getTrendingStorylines(supabase, 20);
const wt = writer === "human" ? 0 : writer === "agent" ? 1 : undefined;
return getTrendingStorylines(supabase, 20, wt);
}

case "rising": {
return getRisingStorylines(supabase, 20);
const wt = writer === "human" ? 0 : writer === "agent" ? 1 : undefined;
return getRisingStorylines(supabase, 20, wt);
}
}
}
7 changes: 2 additions & 5 deletions src/app/story/[storylineId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getTokenPrice, type TokenPriceInfo } from "../../../../lib/price";
import { IS_TESTNET } from "../../../../lib/contracts/constants";
import { type Address } from "viem";
import { truncateAddress } from "../../../../lib/utils";
import { AgentBadge } from "../../../components/AgentBadge";

type Params = Promise<{ storylineId: string }>;

Expand Down Expand Up @@ -115,11 +116,7 @@ function StoryHeader({
<span>
{storyline.plot_count} {storyline.plot_count === 1 ? "plot" : "plots"}
</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>
)}
{storyline.writer_type === 1 && <AgentBadge />}
<RatingSummary storylineId={storyline.storyline_id} />
</div>

Expand Down
9 changes: 9 additions & 0 deletions src/components/AgentBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function AgentBadge({ className }: { className?: string }) {
return (
<span
className={`border-accent-dim text-accent-dim rounded border px-1.5 py-0.5 text-[10px] ${className ?? ""}`}
>
agent
</span>
);
}
7 changes: 2 additions & 5 deletions src/components/StoryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Link from "next/link";
import type { Storyline } from "../../lib/supabase";
import { truncateAddress } from "../../lib/utils";
import { AgentBadge } from "./AgentBadge";

export function StoryCard({
storyline,
Expand Down Expand Up @@ -33,11 +34,7 @@ export function StoryCard({
{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>
)}
{storyline.writer_type === 1 && <AgentBadge />}
</div>
</Link>
);
Expand Down
14 changes: 13 additions & 1 deletion src/components/TabNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,29 @@ export function TabNav({
tabs,
active,
className,
extraParams,
}: {
tabs: readonly string[];
active: string;
className?: string;
extraParams?: Record<string, string>;
}) {
function buildHref(tab: string) {
const params = new URLSearchParams({ tab });
if (extraParams) {
for (const [k, v] of Object.entries(extraParams)) {
params.set(k, v);
}
}
return `/discover?${params.toString()}`;
}

return (
<nav className={`border-border flex gap-1 border-b ${className ?? ""}`}>
{tabs.map((tab) => (
<Link
key={tab}
href={`/discover?tab=${tab}`}
href={buildHref(tab)}
className={`px-3 py-2 text-xs transition-colors ${
tab === active
? "border-accent text-accent -mb-px border-b-2 font-medium"
Expand Down
38 changes: 38 additions & 0 deletions src/components/WriterFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Link from "next/link";

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

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

export function WriterFilter({
active,
tab,
className,
}: {
active: WriterFilterValue;
tab: string;
className?: string;
}) {
return (
<div className={`flex items-center gap-1 ${className ?? ""}`}>
<span className="text-muted mr-1 text-xs">Writer:</span>
{WRITER_OPTIONS.map(({ value, label }) => (
<Link
key={value}
href={`/discover?tab=${tab}&writer=${value}`}
className={`rounded px-2 py-1 text-xs transition-colors ${
value === active
? "bg-accent/10 text-accent font-medium"
: "text-muted hover:text-foreground"
}`}
>
{label}
</Link>
))}
</div>
);
}
Loading