"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-`, we scroll to * that post once the feed has loaded. Used by the SharePostSheet flow * — share short URL resolves to /u//posts#post-, 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/post-scroll-card"; export function ScrollFeed({ params, }: { params: Promise<{ handle: string }>; }) { const { handle } = use(params); const [feed, setFeed] = useState([]); const [loading, setLoading] = useState(true); const [missing, setMissing] = useState(false); const scrolledToHashRef = useRef(false); useEffect(() => { let cancelled = false; 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 (
{loading ? (

Loading…

) : feed.length === 0 ? (

No posts yet.

) : ( feed.map((p) => ) )}
); }