import React, { useCallback, useOptimistic, useState } from 'react' import axios from 'axios' /** * Reaction bar for an artwork or comment. * * Props: * entityType 'artwork' | 'comment' * entityId number * initialTotals Record * isLoggedIn boolean — if false, clicking shows a prompt */ export default function ReactionBar({ entityType, entityId, initialTotals = {}, isLoggedIn = false }) { const [totals, setTotals] = useState(initialTotals) const [loading, setLoading] = useState(null) // slug being toggled const endpoint = entityType === 'artwork' ? `/api/artworks/${entityId}/reactions` : `/api/comments/${entityId}/reactions` const toggle = useCallback( async (slug) => { if (!isLoggedIn) { window.location.href = '/login' return } if (loading) return // prevent double-click setLoading(slug) // Optimistic update setTotals((prev) => { const entry = prev[slug] ?? { count: 0, mine: false } return { ...prev, [slug]: { ...entry, count: entry.mine ? entry.count - 1 : entry.count + 1, mine: !entry.mine, }, } }) try { const { data } = await axios.post(endpoint, { reaction: slug }) setTotals(data.totals) } catch { // Rollback setTotals((prev) => { const entry = prev[slug] ?? { count: 0, mine: false } return { ...prev, [slug]: { ...entry, count: entry.mine ? entry.count - 1 : entry.count + 1, mine: !entry.mine, }, } }) } finally { setLoading(null) } }, [endpoint, isLoggedIn, loading], ) const entries = Object.entries(totals) if (entries.length === 0) return null return (
{entries.map(([slug, info]) => { const { emoji, label, count, mine } = info const isProcessing = loading === slug return ( ) })}
) }