feat: add policy pages
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Public collection at /c/[id]. Anyone can view if `is_public=true`.
|
||||
* Private collections return 403 from the BFF, which we treat as "not
|
||||
* found" to avoid leaking existence.
|
||||
*/
|
||||
|
||||
import { use, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Globe, Lock, FolderPlus } from "lucide-react";
|
||||
import {
|
||||
collections,
|
||||
SocialError,
|
||||
type Collection,
|
||||
type Post,
|
||||
} from "@/lib/social";
|
||||
import { PostViews } from "@/components/social/post-views";
|
||||
|
||||
export default function PublicCollectionPage(props: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = use(props.params);
|
||||
const [c, setC] = useState<Collection | null>(null);
|
||||
const [posts, setPosts] = useState<Post[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [missing, setMissing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
collections
|
||||
.get(id)
|
||||
.then(({ collection, posts }) => {
|
||||
if (cancelled) return;
|
||||
setC(collection);
|
||||
setPosts(posts ?? []);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (
|
||||
err instanceof SocialError &&
|
||||
(err.status === 404 || err.status === 403)
|
||||
) {
|
||||
setMissing(true);
|
||||
}
|
||||
})
|
||||
.finally(() => !cancelled && setLoading(false));
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
if (missing) notFound();
|
||||
if (loading || !c)
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 py-12">
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
</main>
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6 py-10 space-y-8">
|
||||
<header className="rounded-2xl bg-card ring-1 ring-foreground/10 p-6 sm:p-8">
|
||||
<div className="flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||||
{c.is_public ? <Globe className="size-3.5" /> : <Lock className="size-3.5" />}
|
||||
{c.is_public ? "Public collection" : "Private collection"}
|
||||
</div>
|
||||
<h1 className="mt-3 font-[family-name:var(--font-title)] font-semibold text-3xl sm:text-4xl text-foreground leading-tight">
|
||||
{c.title}
|
||||
</h1>
|
||||
{c.description ? (
|
||||
<p className="mt-3 text-base text-muted-foreground leading-relaxed">
|
||||
{c.description}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-4 text-xs text-muted-foreground tabular-nums">
|
||||
{c.item_count} post{c.item_count === 1 ? "" : "s"}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="space-y-3">
|
||||
{posts.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-border p-8 text-center">
|
||||
<FolderPlus className="size-6 text-muted-foreground mx-auto" />
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
This collection has no items yet.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<PostViews posts={posts} />
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user