import React, { useMemo, useState } from 'react' import NovaConfirmDialog from '../ui/NovaConfirmDialog' const AVATAR_FALLBACK = 'https://files.skinbase.org/default/missing_sq.webp' function formatCount(value) { const n = Number(value || 0) if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M` if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, '')}k` return `${n}` } function toCard(item) { return { id: item?.id || item?.slug || item?.url, title: item?.title, author: item?.author, url: item?.url, thumb: item?.thumb, thumbSrcSet: item?.thumb_srcset, } } export default function CreatorSpotlight({ artwork, presentSq, related = [] }) { const [following, setFollowing] = useState(Boolean(artwork?.viewer?.is_following_author)) const [followersCount, setFollowersCount] = useState(Number(artwork?.user?.followers_count || 0)) const [confirmOpen, setConfirmOpen] = useState(false) const [pendingFollowState, setPendingFollowState] = useState(null) const user = artwork?.user || {} const authorName = user.name || user.username || 'Artist' const profileUrl = user.profile_url || (user.username ? `/@${user.username}` : '#') const avatar = user.avatar_url || presentSq?.url || AVATAR_FALLBACK const csrfToken = typeof document !== 'undefined' ? document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') : null const creatorItems = useMemo(() => { const filtered = (Array.isArray(related) ? related : []).filter((item) => { const sameAuthor = String(item?.author || '').trim().toLowerCase() === String(authorName || '').trim().toLowerCase() const notCurrent = item?.url && item.url !== artwork?.canonical_url return sameAuthor && notCurrent }) const source = filtered.length > 0 ? filtered : (Array.isArray(related) ? related : []) return source.slice(0, 12).map(toCard) }, [related, authorName, artwork?.canonical_url]) const persistFollowState = async (nextState) => { setFollowing(nextState) try { const response = await fetch(`/api/users/${user.id}/follow`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken || '', }, credentials: 'same-origin', body: JSON.stringify({ state: nextState }), }) if (!response.ok) throw new Error('Follow failed') const payload = await response.json() if (typeof payload?.followers_count === 'number') { setFollowersCount(payload.followers_count) } setFollowing(Boolean(payload?.is_following)) } catch { setFollowing(!nextState) } } const onToggleFollow = async () => { const nextState = !following if (!nextState) { setPendingFollowState(nextState) setConfirmOpen(true) return } await persistFollowState(nextState) } const onConfirmUnfollow = async () => { if (pendingFollowState === null) return setConfirmOpen(false) await persistFollowState(pendingFollowState) setPendingFollowState(null) } const onCloseConfirm = () => { setConfirmOpen(false) setPendingFollowState(null) } return ( <>
{/* Avatar + info — stacked for sidebar */}
{authorName} { event.currentTarget.src = AVATAR_FALLBACK }} /> {authorName} {user.username &&

@{user.username}

}

{followersCount.toLocaleString()} Followers

{/* Profile + Follow buttons */}
Profile
{/* More from creator rail */} {creatorItems.length > 0 && (

More from {authorName}

{creatorItems.slice(0, 3).map((item, idx) => (
{item.title
{item.likes ? formatCount(item.likes) : ''}
))}
)}
) }