"use client"; import { useState } from "react"; import useSWR, { mutate } from "swr"; import { create, windowScheduler } from "@yornaath/batshit"; import { RecommendTinyEmpty, RecommendTinyFilled } from "./Icons/RecommendTiny"; import { recommendAction, unrecommendAction, } from "app/lish/[did]/[publication]/[rkey]/Interactions/recommendAction"; import { callRPC } from "app/api/rpc/client"; import { useSmoker, useToaster } from "./Toast"; import { OAuthErrorMessage, isOAuthSessionError } from "./OAuthError"; import { ButtonSecondary } from "./Buttons"; import { Separator } from "./Layout"; // Create a batcher for recommendation checks // Batches requests made within 10ms window const recommendationBatcher = create({ fetcher: async (documentUris: string[]) => { const response = await callRPC("get_user_recommendations", { documentUris, }); return response.result; }, resolver: (results, documentUri) => results[documentUri] ?? false, scheduler: windowScheduler(10), }); const getRecommendationKey = (documentUri: string) => `recommendation:${documentUri}`; function useUserRecommendation(documentUri: string) { const { data: hasRecommended, isLoading } = useSWR( getRecommendationKey(documentUri), () => recommendationBatcher.fetch(documentUri), ); return { hasRecommended: hasRecommended ?? false, isLoading, }; } function mutateRecommendation(documentUri: string, hasRecommended: boolean) { mutate(getRecommendationKey(documentUri), hasRecommended, { revalidate: false, }); } /** * RecommendButton that fetches the user's recommendation status asynchronously. * Uses SWR with batched requests for efficient fetching when many buttons are rendered. */ export function RecommendButton(props: { documentUri: string; recommendsCount: number; className?: string; expanded?: boolean; }) { const { hasRecommended, isLoading } = useUserRecommendation( props.documentUri, ); const [count, setCount] = useState(props.recommendsCount); const [isPending, setIsPending] = useState(false); const [optimisticRecommended, setOptimisticRecommended] = useState< boolean | null >(null); const toaster = useToaster(); const smoker = useSmoker(); // Use optimistic state if set, otherwise use fetched state const displayRecommended = optimisticRecommended !== null ? optimisticRecommended : hasRecommended; const handleClick = async (e: React.MouseEvent) => { if (isPending || isLoading) return; const currentlyRecommended = displayRecommended; setIsPending(true); setOptimisticRecommended(!currentlyRecommended); setCount((c) => (currentlyRecommended ? c - 1 : c + 1)); if (!currentlyRecommended) { smoker({ position: { x: e.clientX, y: e.clientY - 16, }, text: