import React, { useState, useCallback, useEffect } from 'react' import { createRoot } from 'react-dom/client' import axios from 'axios' import ArtworkHero from '../components/artwork/ArtworkHero' import ArtworkMeta from '../components/artwork/ArtworkMeta' import ArtworkAwards from '../components/artwork/ArtworkAwards' import ArtworkTags from '../components/artwork/ArtworkTags' import ArtworkDescription from '../components/artwork/ArtworkDescription' import ArtworkComments from '../components/artwork/ArtworkComments' import ArtworkActionBar from '../components/artwork/ArtworkActionBar' import ArtworkDetailsPanel from '../components/artwork/ArtworkDetailsPanel' import CreatorSpotlight from '../components/artwork/CreatorSpotlight' import ArtworkRecommendationsRails from '../components/artwork/ArtworkRecommendationsRails' import ArtworkNavigator from '../components/viewer/ArtworkNavigator' import ArtworkViewer from '../components/viewer/ArtworkViewer' import ReactionBar from '../components/comments/ReactionBar' function ArtworkPage({ artwork: initialArtwork, related: initialRelated, presentMd: initialMd, presentLg: initialLg, presentXl: initialXl, presentSq: initialSq, canonicalUrl: initialCanonical, isAuthenticated = false, comments: initialComments = [] }) { const [viewerOpen, setViewerOpen] = useState(false) const openViewer = useCallback(() => setViewerOpen(true), []) const closeViewer = useCallback(() => setViewerOpen(false), []) // Navigable state — updated on client-side navigation const [artwork, setArtwork] = useState(initialArtwork) const [liveStats, setLiveStats] = useState(initialArtwork?.stats || {}) const handleStatsChange = useCallback((delta) => { setLiveStats(prev => { const next = { ...prev } Object.entries(delta).forEach(([key, val]) => { next[key] = Math.max(0, (Number(next[key]) || 0) + val) }) return next }) }, []) const [presentMd, setPresentMd] = useState(initialMd) const [presentLg, setPresentLg] = useState(initialLg) const [presentXl, setPresentXl] = useState(initialXl) const [presentSq, setPresentSq] = useState(initialSq) const [related, setRelated] = useState(initialRelated) const [comments, setComments] = useState(initialComments) const [canonicalUrl, setCanonicalUrl] = useState(initialCanonical) // Nav arrow state — populated by ArtworkNavigator once neighbors resolve const [navState, setNavState] = useState({ hasPrev: false, hasNext: false, navigatePrev: null, navigateNext: null }) // Artwork-level reactions const [reactionTotals, setReactionTotals] = useState(null) useEffect(() => { if (!artwork?.id) return axios .get(`/api/artworks/${artwork.id}/reactions`) .then(({ data }) => setReactionTotals(data.totals ?? {})) .catch(() => setReactionTotals({})) }, [artwork?.id]) /** * Called by ArtworkNavigator after a successful no-reload navigation. * data = ArtworkResource JSON from /api/artworks/{id}/page */ const handleNavigate = useCallback((data) => { setArtwork(data) setLiveStats(data.stats || {}) setPresentMd(data.thumbs?.md ?? null) setPresentLg(data.thumbs?.lg ?? null) setPresentXl(data.thumbs?.xl ?? null) setPresentSq(data.thumbs?.sq ?? null) setRelated([]) // cleared on navigation; user can scroll down for related setComments([]) // cleared; per-page server data setCanonicalUrl(data.canonical_url ?? window.location.href) setViewerOpen(false) // close viewer when navigating away }, []) if (!artwork) return null const initialAwards = artwork?.awards ?? null return ( <> {/* ── Hero ────────────────────────────────────────────────────── */} {/* ── Centered action bar with stat counts ────────────────────── */} {/* ── Two-column content ──────────────────────────────────────── */} {/* LEFT COLUMN — main content */} {/* Title + author + breadcrumbs */} {/* Description */} {/* Artwork reactions */} {reactionTotals !== null && ( )} {/* Tags & categories */} {/* Comments */} {/* RIGHT COLUMN — sidebar */} {/* ── Full-width recommendation rails ─────────────────────────── */} {/* Artwork navigator — prev/next arrows, keyboard, swipe, no page reload */} {/* Fullscreen viewer modal */} > ) } // Auto-mount if the Blade view provided data attributes const el = document.getElementById('artwork-page') if (el) { const parse = (key, fallback = null) => { try { return JSON.parse(el.dataset[key] || 'null') ?? fallback } catch { return fallback } } const root = createRoot(el) root.render( , ) } export default ArtworkPage