Skip to content
Merged
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
148 changes: 91 additions & 57 deletions src/app/(public)/tools/subreddit-analyzer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { FormEvent, useState } from "react";
import { MaxWidth } from "@/components/public/MaxWidth";

type AnalyzerResponse = {
queued?: boolean;
message?: string;
subreddit?: {
name: string;
title: string | null;
Expand All @@ -17,22 +15,30 @@ type AnalyzerResponse = {
isQuarantined: boolean;
};
policy?: {
promoAllowed: boolean;
linkPolicy: string;
promoAllowed: boolean | null;
linkPolicy: string | null;
flairRequired: boolean;
noLinksInPosts: boolean;
textOnly: boolean;
} | null;
Comment on lines 17 to 23
rules?: string[];
topTimeWindows?: Array<{ dayOfWeek: number; hourUtc: number; score: number }>;
staleHours?: number;
queuedRefresh?: boolean;
source?: string;
};

function toDayLabel(dayOfWeek: number) {
const labels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
return labels[dayOfWeek] ?? `Day ${dayOfWeek}`;
}

function formatNumber(n: number | null | undefined): string {
if (n == null || !Number.isFinite(n)) return "N/A";
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
return String(n);
}

export default function SubredditAnalyzerPage() {
const [subreddit, setSubreddit] = useState("");
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -70,6 +76,9 @@ export default function SubredditAnalyzerPage() {
}
}

const hasRules = (result?.rules?.length ?? 0) > 0;
const hasPolicy = result?.policy && result.policy.promoAllowed != null;

return (
<div className="pb-20 pt-16">
<MaxWidth>
Expand All @@ -80,8 +89,8 @@ export default function SubredditAnalyzerPage() {
</p>
<h1 className="mt-4 text-4xl font-semibold">Subreddit analyzer</h1>
<p className="mt-4 text-sm text-muted-foreground">
Check rules, activity, and best-time windows before posting so you
avoid low-fit or high-risk subreddits.
Check rules, activity, and posting policies before you commit to a
subreddit. Know what's allowed before you post.
</p>
<form
className="mt-8 rounded-[24px] border border-border bg-card/80 p-6"
Expand Down Expand Up @@ -115,113 +124,138 @@ export default function SubredditAnalyzerPage() {
{error}
</p>
) : null}
{result?.queued ? (
<p className="mt-4 rounded-2xl border border-border bg-background px-4 py-3 text-sm text-muted-foreground">
{result.message ??
"Subreddit is being fetched. Retry in a few seconds."}
</p>
) : null}
</form>
</div>
<div className="space-y-4">
<div className="rounded-[24px] border border-border bg-background/80 p-6">
<p className="text-sm font-semibold">Rule summary</p>
{result?.policy ? (
<p className="text-sm font-semibold">Rules</p>
{hasRules ? (
<ul className="mt-4 space-y-2">
{result!.rules!.map((rule, i) => (
<li
key={i}
className="rounded-2xl border border-border bg-card/80 px-4 py-3 text-sm text-muted-foreground"
>
{rule}
</li>
))}
</ul>
) : hasPolicy ? (
<ul className="mt-4 space-y-2 text-sm text-muted-foreground">
<li>
Link policy:{" "}
Promo allowed:{" "}
<span className="font-semibold text-foreground">
{result.policy.linkPolicy}
{result!.policy!.promoAllowed === null
? "Unknown"
: result!.policy!.promoAllowed
? "Yes"
: "No"}
</span>
</li>
<li>
Promo allowed:{" "}
Link policy:{" "}
<span className="font-semibold text-foreground">
{result.policy.promoAllowed ? "Yes" : "No"}
{result!.policy!.linkPolicy ?? "Unknown"}
</span>
</li>
<li>
Flair required:{" "}
<span className="font-semibold text-foreground">
{result.policy.flairRequired ? "Yes" : "No"}
{result!.policy!.flairRequired ? "Yes" : "No"}
</span>
</li>
<li>
Text only:{" "}
<span className="font-semibold text-foreground">
{result!.policy!.textOnly ? "Yes" : "No"}
</span>
</li>
</ul>
) : (
<p className="mt-4 text-sm text-muted-foreground">
Analyze a subreddit to preview policy and rule signals.
{result
? "No rules data available for this subreddit."
: "Enter a subreddit name to see its rules and posting policies."}
</p>
Comment on lines +131 to 179
)}
</div>

<div className="rounded-[24px] border border-border bg-background/80 p-6">
<p className="text-sm font-semibold">Best-time windows</p>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
{(result?.topTimeWindows?.length
? result.topTimeWindows
: []
).map((slot, index) => (
<div
key={`${slot.dayOfWeek}-${slot.hourUtc}-${index}`}
className="rounded-2xl border border-border bg-card/80 px-4 py-3 text-sm font-semibold"
>
{toDayLabel(slot.dayOfWeek)}{" "}
{String(slot.hourUtc).padStart(2, "0")}
:00 UTC
</div>
))}
{!result?.topTimeWindows?.length ? (
<p className="text-sm text-muted-foreground sm:col-span-2">
No cached windows yet. Run analysis and retry after ingest.
</p>
) : null}
</div>
</div>
<div className="rounded-[24px] border border-border bg-background/80 p-6">
<p className="text-sm font-semibold">Risk signals</p>
<p className="text-sm font-semibold">Community stats</p>
{result?.subreddit ? (
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="rounded-2xl border border-border bg-card/80 px-4 py-3">
<p className="text-xs text-muted-foreground">Subscribers</p>
<p className="text-sm font-semibold">
{result.subreddit.subscribers ?? "Unknown"}
{formatNumber(result.subreddit.subscribers)}
</p>
</div>
<div className="rounded-2xl border border-border bg-card/80 px-4 py-3">
<p className="text-xs text-muted-foreground">
Active users
</p>
<p className="text-sm font-semibold">
{result.subreddit.activeUsers ?? "Unknown"}
{result.subreddit.activeUsers
? formatNumber(result.subreddit.activeUsers)
: "Not available"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
Reddit only shares this for logged-in users.
</p>
</div>
<div className="rounded-2xl border border-border bg-card/80 px-4 py-3">
<p className="text-xs text-muted-foreground">Restriction</p>
<p className="text-xs text-muted-foreground">Type</p>
<p className="text-sm font-semibold">
{result.subreddit.isRestricted ? "Restricted" : "Open"}
{result.subreddit.isQuarantined
? "Quarantined"
: result.subreddit.isRestricted
? "Restricted"
: result.subreddit.nsfw
? "NSFW"
: "Public"}
</p>
</div>
<div className="rounded-2xl border border-border bg-card/80 px-4 py-3">
<p className="text-xs text-muted-foreground">Stale hours</p>
<p className="text-xs text-muted-foreground">Data source</p>
<p className="text-sm font-semibold">
{typeof result.staleHours === "number"
? result.staleHours
: "Unknown"}
{result.source === "database"
? "Cached"
: result.source === "fallback"
? "Estimated (Reddit unavailable)"
: "Live from Reddit"}
</p>
</div>
</div>
) : (
<p className="mt-4 text-sm text-muted-foreground">
Analyze a subreddit to see activity and moderation signals.
Analyze a subreddit to see community stats.
</p>
)}
</div>

{(result?.topTimeWindows?.length ?? 0) > 0 ? (
<div className="rounded-[24px] border border-border bg-background/80 p-6">
<p className="text-sm font-semibold">Best-time windows</p>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
{result!.topTimeWindows!.map((slot, index) => (
<div
key={`${slot.dayOfWeek}-${slot.hourUtc}-${index}`}
className="rounded-2xl border border-border bg-card/80 px-4 py-3 text-sm font-semibold"
>
{toDayLabel(slot.dayOfWeek)}{" "}
{String(slot.hourUtc).padStart(2, "0")}:00 UTC
</div>
))}
</div>
</div>
) : null}
</div>
</div>
<div className="mt-10 rounded-[24px] border border-border bg-card/80 p-6">
<p className="text-sm font-semibold">Turn insights into execution</p>
<p className="text-sm font-semibold">What to do next</p>
<p className="mt-2 text-sm text-muted-foreground">
In the full app, these signals feed recommendations, task planning,
and approval-first scheduling.
Use these insights to pick the right subreddits for your product.
Then generate discussion-first drafts that follow the rules.
</p>
<div className="mt-4 flex flex-wrap gap-3">
<Link
Expand Down
Loading