import React, { useState, useEffect } from 'react' export default function ArtworkActions({ artwork, canonicalUrl, mobilePriority = false }) { const [liked, setLiked] = useState(Boolean(artwork?.viewer?.is_liked)) const [favorited, setFavorited] = useState(Boolean(artwork?.viewer?.is_favorited)) const [reporting, setReporting] = useState(false) const [downloading, setDownloading] = useState(false) // Fallback URL used only if the API call fails entirely const fallbackUrl = artwork?.thumbs?.xl?.url || artwork?.thumbs?.lg?.url || artwork?.file?.url || '#' const shareUrl = canonicalUrl || artwork?.canonical_url || (typeof window !== 'undefined' ? window.location.href : '#') const csrfToken = typeof document !== 'undefined' ? document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') : null // Track the view once per browser session (sessionStorage prevents re-firing). useEffect(() => { if (!artwork?.id) return const key = `sb_viewed_${artwork.id}` if (typeof sessionStorage !== 'undefined' && sessionStorage.getItem(key)) return if (typeof sessionStorage !== 'undefined') sessionStorage.setItem(key, '1') fetch(`/api/art/${artwork.id}/view`, { method: 'POST', headers: { 'X-CSRF-TOKEN': csrfToken || '', 'Content-Type': 'application/json' }, credentials: 'same-origin', }).catch(() => {}) }, [artwork?.id]) // eslint-disable-line react-hooks/exhaustive-deps /** * Async download handler: * 1. POST /api/art/{id}/download → records the event, returns { url, filename } * 2. Programmatically clicks a hidden to trigger the save dialog * 3. Falls back to the pre-resolved fallbackUrl if the API is unreachable */ const handleDownload = async (e) => { e.preventDefault() if (downloading || !artwork?.id) return setDownloading(true) try { const res = await fetch(`/api/art/${artwork.id}/download`, { method: 'POST', headers: { 'X-CSRF-TOKEN': csrfToken || '', 'Content-Type': 'application/json' }, credentials: 'same-origin', }) const data = res.ok ? await res.json() : null const url = data?.url || fallbackUrl const filename = data?.filename || '' // Trigger browser save-dialog with the correct filename const a = document.createElement('a') a.href = url a.download = filename a.rel = 'noopener noreferrer' document.body.appendChild(a) a.click() document.body.removeChild(a) } catch { // API unreachable — open the best available URL directly window.open(fallbackUrl, '_blank', 'noopener,noreferrer') } finally { setDownloading(false) } } const postInteraction = async (url, body) => { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken || '', }, credentials: 'same-origin', body: JSON.stringify(body), }) if (!response.ok) throw new Error('Request failed') return response.json() } const onToggleLike = async () => { const nextState = !liked setLiked(nextState) try { await postInteraction(`/api/artworks/${artwork.id}/like`, { state: nextState }) } catch { setLiked(!nextState) } } const onToggleFavorite = async () => { const nextState = !favorited setFavorited(nextState) try { await postInteraction(`/api/artworks/${artwork.id}/favorite`, { state: nextState }) } catch { setFavorited(!nextState) } } const onShare = async () => { try { if (navigator.share) { await navigator.share({ title: artwork?.title || 'Artwork', url: shareUrl, }) return } await navigator.clipboard.writeText(shareUrl) } catch { // noop } } const onReport = async () => { if (reporting) return setReporting(true) try { await postInteraction(`/api/artworks/${artwork.id}/report`, { reason: 'Reported from artwork page', }) } catch { // noop } finally { setReporting(false) } } return (

Actions

{/* Download — full-width primary CTA */} {/* Secondary actions — icon row */}
{/* Like */} {/* Favorite */} {/* Share */} {/* Report */}
{mobilePriority && (
)}
) }