"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 { Loader2 } from "lucide-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 { useSharePost } from "./share-post-provider"; // 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 { openShareSheet } = useSharePost(); const isMobile = useMediaQuery("(max-width: 767px)"); const [collectionsOpen, setCollectionsOpen] = useState(false); // Tracks the platform-aware share routing (short URL + image fetch // on mobile native-share; sheet mount on desktop) so the Send icon // can render a spinner while the user waits. const [sharePending, setSharePending] = 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 }); } } // Send button: hand off to the provider, which decides between the // mobile native-share path (await short URL + image proxy in // parallel, then navigator.share) and the desktop/no-native-share // path (mount the custom SharePostSheet). Provider's openShareSheet // is always-resolves, so a single try/finally toggles the spinner. async function onSendClick() { if (sharePending) return; setSharePending(true); try { await openShareSheet(post); } finally { setSharePending(false); } } 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 ? ( ) : ( ) } /> ) : ( withAuth(onReshare)} icon={} /> )}
withAuth(() => setCollectionsOpen(true))} icon={} />
setCollectionsOpen(false)} isMobile={isMobile} existingPostId={post.id} /> {/* SharePostSheet is mounted globally by SharePostProvider, so no per-card instance lives here. The provider's openShareSheet swaps the active post under the hood. */} ); } function ActionButton({ label, onClick, icon, disabled = false, }: { label: string; onClick: () => void; icon: React.ReactNode; /** Greys out the button and blocks clicks. Used by the Send button * while the short URL is being resolved on the mobile native-share * path so users can't fire navigator.share before the URL is * ready. */ disabled?: boolean; }) { return ( ); }