feat: add policy pages

This commit is contained in:
gosangam
2026-06-02 01:57:08 +05:30
parent 0352cb2003
commit 91d5d272d2
168 changed files with 1326 additions and 81 deletions
+28
View File
@@ -0,0 +1,28 @@
/**
* /u/[handle]/posts — public Instagram-style scroll feed.
*
* Server shell so we can export `generateMetadata` for OG previews on
* direct shares of this URL. We re-use the same profile-OG metadata as
* /u/[handle] — the post anchor (#post-<id>) that may be appended to
* the URL is a client-side fragment and never reaches server-rendered
* metadata, so we can't surface post-level OG here.
*
* (Post-level OG cards still work via the SharePostSheet → short link
* → /share/[code] route, which DOES see the hash and dispatches to
* `ogForPost` in share-og.ts.)
*/
import type { Metadata } from "next";
import { metadataForHandle } from "../profile-og";
import { ScrollFeed } from "./scroll-feed";
type Props = { params: Promise<{ handle: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { handle } = await params;
return metadataForHandle(handle);
}
export default function PublicPostsFeedPage({ params }: Props) {
return <ScrollFeed params={params} />;
}
@@ -0,0 +1,78 @@
"use client";
/**
* ScrollFeed — client body for /u/[handle]/posts (public scroll feed).
*
* Split out from page.tsx so the page itself can be a server component
* exporting `generateMetadata` for the OG card. The layout owns the
* ProfileHeader + tabs + outer container; this component only renders
* the inner scroll column (the Instagram-style feed of full posts).
*
* Hash-anchor behaviour: when arriving with `#post-<id>`, we scroll to
* that post once the feed has loaded. Used by the SharePostSheet flow
* — share short URL resolves to /u/<handle>/posts#post-<id>, recipient
* lands here, and we scroll to the post that was actually shared.
*/
import { use, useEffect, useRef, useState } from "react";
import { notFound } from "next/navigation";
import { profile, SocialError, type Post } from "@/lib/social";
import { PostScrollCard } from "@/components/social/post-scroll-card";
export function ScrollFeed({
params,
}: {
params: Promise<{ handle: string }>;
}) {
const { handle } = use(params);
const [feed, setFeed] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [missing, setMissing] = useState(false);
const scrolledToHashRef = useRef(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
profile
.feed(handle, 60)
.then((rows) => {
if (cancelled) return;
setFeed(rows ?? []);
})
.catch((err) => {
if (err instanceof SocialError && err.status === 404) {
setMissing(true);
}
})
.finally(() => !cancelled && setLoading(false));
return () => {
cancelled = true;
};
}, [handle]);
useEffect(() => {
if (loading || feed.length === 0 || scrolledToHashRef.current) return;
if (typeof window === "undefined") return;
const hash = window.location.hash;
if (!hash) return;
scrolledToHashRef.current = true;
requestAnimationFrame(() => {
const el = document.getElementById(hash.slice(1));
if (el) el.scrollIntoView({ block: "start", behavior: "auto" });
});
}, [loading, feed.length]);
if (missing) notFound();
return (
<div className="max-w-xl w-full mx-auto space-y-6">
{loading ? (
<p className="text-sm text-muted-foreground">Loading…</p>
) : feed.length === 0 ? (
<p className="text-sm text-muted-foreground">No posts yet.</p>
) : (
feed.map((p) => <PostScrollCard key={p.id} post={p} />)
)}
</div>
);
}