Files
2026-06-21 18:35:48 +05:30

100 lines
3.3 KiB
TypeScript

"use client";
/**
* /u/me/posts — the owner's Instagram-style scroll feed.
*
* Reached by tapping a thumbnail from the /u/me Posts grid. The clicked
* post's id arrives as a `#post-<id>` hash; usePostScrollFeed fetches that
* post directly so it lands at the top instantly (even for old posts), then
* keyset-pages older posts beneath it as you scroll.
*
* Owner controls (Delete + Update background) are mounted per-card.
*/
import { useState } from "react";
import { Loader2 } from "lucide-react";
import { useAuth } from "@/components/auth/auth-provider";
import { PostScrollCard } from "@/components/social/post/post-scroll-card";
import { usePostScrollFeed } from "@/components/social/post/use-post-scroll-feed";
import { ConfirmModal } from "@/components/ui/confirm-modal";
import { posts, SocialError, type Post } from "@/lib/social";
export default function MyPostsFeedPage() {
const { user } = useAuth();
// Defer the feed (which calls the authed /posts/mine) until we have a user.
if (!user) return null;
return <OwnerScrollFeed />;
}
function OwnerScrollFeed() {
const { feed, setFeed, loading, hasMore, error, setError, sentinelRef } =
usePostScrollFeed((before, limit) =>
posts.mine({ limit, before: before ?? undefined }),
);
const [pendingDelete, setPendingDelete] = useState<string | null>(null);
async function performDelete() {
const id = pendingDelete;
if (!id) return;
const prev = feed;
setFeed((rows) => rows.filter((p) => p.id !== id));
try {
await posts.delete(id);
} catch (err) {
setFeed(prev);
const msg =
err instanceof SocialError ? err.message : "Could not delete.";
setError(msg);
// Re-throw so ConfirmModal surfaces the failure and stays open.
throw new Error(msg);
}
}
function onPostUpdated(updated: Post) {
setFeed((rows) => rows.map((p) => (p.id === updated.id ? updated : p)));
}
// Hiding a post moves it off the timeline (into the Hidden tab), so drop
// it from this feed immediately rather than waiting for a refresh.
function onHiddenChange(id: string, hidden: boolean) {
if (hidden) setFeed((rows) => rows.filter((p) => p.id !== id));
}
return (
<div className="max-w-xl w-full mx-auto space-y-6">
{error ? (
<p className="text-sm text-destructive">{error}</p>
) : loading && feed.length === 0 ? (
<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}
onDelete={(id) => setPendingDelete(id)}
onUpdated={onPostUpdated}
onHiddenChange={onHiddenChange}
/>
))
)}
{hasMore && feed.length > 0 ? (
<div ref={sentinelRef} className="flex justify-center py-6">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
</div>
) : null}
<ConfirmModal
open={!!pendingDelete}
onClose={() => setPendingDelete(null)}
title="Delete this post?"
message="The post will be removed permanently from your feed and from any collections it's in. This can't be undone."
confirmLabel="Delete post"
onConfirm={performDelete}
/>
</div>
);
}