feat: add reusable gallery carousel and ranking feed infrastructure

This commit is contained in:
2026-02-28 07:56:25 +01:00
parent 67ef79766c
commit 6536d4ae78
36 changed files with 3177 additions and 373 deletions

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
export default function ArtworkActions({ artwork, canonicalUrl, mobilePriority = false }) {
export default function ArtworkActions({ artwork, canonicalUrl, mobilePriority = false, onStatsChange }) {
const [liked, setLiked] = useState(Boolean(artwork?.viewer?.is_liked))
const [favorited, setFavorited] = useState(Boolean(artwork?.viewer?.is_favorited))
const [reporting, setReporting] = useState(false)
@@ -17,11 +17,16 @@ export default function ArtworkActions({ artwork, canonicalUrl, mobilePriority =
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',
}).then(res => {
// Only mark as seen after a confirmed success — if the POST fails the
// next page load will retry rather than silently skipping forever.
if (res.ok && typeof sessionStorage !== 'undefined') {
sessionStorage.setItem(key, '1')
}
}).catch(() => {})
}, [artwork?.id]) // eslint-disable-line react-hooks/exhaustive-deps
@@ -81,6 +86,7 @@ export default function ArtworkActions({ artwork, canonicalUrl, mobilePriority =
setLiked(nextState)
try {
await postInteraction(`/api/artworks/${artwork.id}/like`, { state: nextState })
onStatsChange?.({ likes: nextState ? 1 : -1 })
} catch {
setLiked(!nextState)
}
@@ -91,6 +97,7 @@ export default function ArtworkActions({ artwork, canonicalUrl, mobilePriority =
setFavorited(nextState)
try {
await postInteraction(`/api/artworks/${artwork.id}/favorite`, { state: nextState })
onStatsChange?.({ favorites: nextState ? 1 : -1 })
} catch {
setFavorited(!nextState)
}