"use client"; /** * PostActionsFooter — the icon-button row at the bottom of a post card. * * Left cluster (non-owner): Like · Comment · Reshare * Left cluster (owner): Like · Comment · Send * Right cluster: Save to Collection (folder-plus) * * Reshare and Send are *mutually exclusive* — once the post is on your * account there's nothing left to reshare onto it, so the Reshare slot * is replaced by Send (external share via SharePostSheet). Ownership is * decided by `user.id === post.user_id`, which the BFF already includes * on every post fetch — no extra round-trip needed. * * Like / Comment / Reshare are placeholders today (toast on click). * Save to Collection mounts the existing CollectionsDrawer in * `existingPostId` mode so the post attaches without a duplicate. * * Auth gating is handled per-action via `useAuthGate` — anonymous users * see the in-place auth modal before the underlying action runs. */ import { useState } from "react"; import { HiOutlineArrowPath, HiOutlineChatBubbleOvalLeft, HiOutlineFolderPlus, HiOutlineHeart, HiOutlinePaperAirplane, } from "react-icons/hi2"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { posts as postsApi, SocialError, type Post } from "@/lib/social"; import { useAuth } from "@/components/auth/auth-provider"; import { useAuthGate } from "@/components/auth/auth-gate"; import { useMediaQuery } from "@/components/selection-toolbar/shared"; import { CollectionsDrawer } from "@/components/selection-toolbar/collections-drawer"; import { SharePostSheet } from "./share-post-sheet"; // All icons come from the Heroicons v2 outline family (react-icons // `hi2` namespace), so they share one design language — uniform stroke // weight, matching corner radius, matching optical sizing. const ICON_CLASS = "size-[1.375rem]"; export function PostActionsFooter({ post }: { post: Post }) { const { user } = useAuth(); const { requireAuth } = useAuthGate(); const isMobile = useMediaQuery("(max-width: 767px)"); const [collectionsOpen, setCollectionsOpen] = useState(false); const [shareOpen, setShareOpen] = useState(false); // Optimistic local mirror of post.viewer_has_reshared so the swap // happens the instant the user taps Reshare. Reconciles to the // server's value via rollback if the API call fails. const [reshared, setReshared] = useState( !!post.viewer_has_reshared, ); const isOwner = !!user && user.id === post.user_id; // Owners and resharers both see Send — the post is "on their account" // either way, so the action they need is external share, not reshare. const showSend = isOwner || reshared; async function withAuth(fn: () => void | Promise) { if (!(await requireAuth())) return; await fn(); } async function onReshare() { // Optimistic flip first so the UI feels instant; revert on error. setReshared(true); try { await postsApi.reshare(post.id); toast.success("Reshared to your account", { duration: 2000 }); } catch (err) { setReshared(false); const msg = err instanceof SocialError ? err.message : "Could not reshare"; toast.error(msg, { duration: 3000 }); } } return ( <>
withAuth(() => { toast("Likes are coming soon", { duration: 2000 }); }) } icon={} /> withAuth(() => { toast("Comments are coming soon", { duration: 2000 }); }) } icon={} /> {/* Reshare for posts not yet on your account, Send for posts that are — authored OR previously reshared. Mutually exclusive: the row stays three icons regardless of viewer state. */} {showSend ? ( setShareOpen(true)} icon={} /> ) : ( withAuth(onReshare)} icon={} /> )}
withAuth(() => setCollectionsOpen(true))} icon={} />
setCollectionsOpen(false)} isMobile={isMobile} existingPostId={post.id} /> {showSend ? ( setShareOpen(false)} post={post} isMobile={isMobile} /> ) : null} ); } function ActionButton({ label, onClick, icon, }: { label: string; onClick: () => void; icon: React.ReactNode; }) { return ( ); }