import React from 'react' import { usePage } from '@inertiajs/react' import ArtworkGallery from '../../components/artwork/ArtworkGallery' import CollectionCard from '../../components/profile/collections/CollectionCard' import CollectionVisibilityBadge from '../../components/profile/collections/CollectionVisibilityBadge' import NovaSelect from '../../components/ui/NovaSelect' import SeoHead from '../../components/seo/SeoHead' import CommentForm from '../../components/social/CommentForm' import CommentList from '../../components/social/CommentList' import useWebShare from '../../hooks/useWebShare' function getCsrfToken() { if (typeof document === 'undefined') return '' return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' } async function requestJson(url, { method = 'POST', body } = {}) { const response = await fetch(url, { method, credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken(), 'X-Requested-With': 'XMLHttpRequest', }, body: body ? JSON.stringify(body) : undefined, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || 'Request failed.') } return payload } function TypeBadge({ collection }) { const label = collection?.type === 'editorial' ? 'Editorial' : collection?.type === 'community' ? 'Community' : 'Personal' return {label} } const COLLABORATOR_ROLE_COLORS = { owner: 'border-amber-300/20 bg-amber-400/10 text-amber-200', moderator: 'border-sky-300/20 bg-sky-400/10 text-sky-200', contributor: 'border-emerald-300/20 bg-emerald-400/10 text-emerald-200', curator: 'border-violet-300/20 bg-violet-400/10 text-violet-200', } function CollaboratorCard({ member }) { const roleColor = COLLABORATOR_ROLE_COLORS[String(member?.role || '').toLowerCase()] ?? 'border-white/10 bg-white/[0.05] text-slate-300' return ( {member?.user?.avatar_url ? ( {member?.user?.name ) : (
)}
{member?.user?.name || member?.user?.username}
{member?.user?.username ?
@{member.user.username}
: null}
{member?.role}{member?.status === 'pending' ? ' · invited' : ''}
) } function SubmissionCard({ submission, onApprove, onReject, onWithdraw, onReport }) { return (
{submission?.artwork?.thumb ? {submission.artwork.title} : null}
{submission?.artwork?.title || 'Artwork submission'}
{submission?.status}

Submitted by @{submission?.user?.username}

{submission?.message ?

{submission.message}

: null}
{submission?.can_review ? : null} {submission?.can_review ? : null} {submission?.can_withdraw ? : null} {submission?.can_report ? : null}
) } const METAROW_TONES = { 'fa-images': { icon: 'text-sky-300', bg: 'bg-sky-400/10 border-sky-300/20', bar: 'from-sky-400/60' }, 'fa-heart': { icon: 'text-rose-300', bg: 'bg-rose-400/10 border-rose-300/20', bar: 'from-rose-400/60' }, 'fa-bell': { icon: 'text-emerald-300', bg: 'bg-emerald-400/10 border-emerald-300/20', bar: 'from-emerald-400/60' }, 'fa-eye': { icon: 'text-violet-300', bg: 'bg-violet-400/10 border-violet-300/20', bar: 'from-violet-400/60' }, 'fa-bookmark': { icon: 'text-amber-300', bg: 'bg-amber-400/10 border-amber-300/20', bar: 'from-amber-400/60' }, 'fa-panorama': { icon: 'text-slate-300', bg: 'bg-white/[0.05] border-white/10', bar: 'from-slate-400/40' }, 'fa-gauge-high': { icon: 'text-teal-300', bg: 'bg-teal-400/10 border-teal-300/20', bar: 'from-teal-400/60' }, 'fa-ranking-star': { icon: 'text-amber-300', bg: 'bg-amber-400/10 border-amber-300/20', bar: 'from-amber-400/60' }, 'fa-bullhorn': { icon: 'text-orange-300', bg: 'bg-orange-400/10 border-orange-300/20', bar: 'from-orange-400/60' }, } function MetaRow({ icon, label, value, compact = false }) { const title = `${label}: ${value}` const tone = METAROW_TONES[icon] ?? { icon: 'text-slate-300', bg: 'bg-white/[0.05] border-white/10', bar: 'from-slate-400/40' } if (compact) { return (
{value}
{label}
) } return (
{label}
{value}
) } const HERO_ACTION_TONES = { neutral: { idle: 'border-white/10 bg-white/[0.05] text-white hover:border-white/20 hover:bg-white/[0.09]', active: 'border-white/15 bg-[linear-gradient(135deg,rgba(255,255,255,0.12),rgba(255,255,255,0.04))] text-white shadow-[0_18px_40px_rgba(2,6,23,0.18)]', icon: 'border-white/10 bg-white/[0.08] text-slate-200', iconActive: 'border-white/15 bg-white/[0.12] text-white', }, rose: { idle: 'border-white/10 bg-white/[0.05] text-white hover:border-rose-400/30 hover:bg-rose-400/[0.12] hover:text-rose-100', active: 'border-rose-400/30 bg-[linear-gradient(135deg,rgba(244,63,94,0.18),rgba(255,255,255,0.06))] text-rose-50 shadow-[0_18px_40px_rgba(244,63,94,0.14)]', icon: 'border-rose-300/15 bg-rose-400/[0.08] text-rose-200', iconActive: 'border-rose-300/30 bg-rose-400/[0.16] text-rose-50', }, emerald: { idle: 'border-white/10 bg-white/[0.05] text-white hover:border-emerald-400/30 hover:bg-emerald-400/[0.12] hover:text-emerald-100', active: 'border-emerald-400/30 bg-[linear-gradient(135deg,rgba(52,211,153,0.18),rgba(255,255,255,0.06))] text-emerald-50 shadow-[0_18px_40px_rgba(52,211,153,0.14)]', icon: 'border-emerald-300/15 bg-emerald-400/[0.08] text-emerald-200', iconActive: 'border-emerald-300/30 bg-emerald-400/[0.16] text-emerald-50', }, violet: { idle: 'border-white/10 bg-white/[0.05] text-white hover:border-violet-400/30 hover:bg-violet-400/[0.12] hover:text-violet-100', active: 'border-violet-400/30 bg-[linear-gradient(135deg,rgba(167,139,250,0.18),rgba(255,255,255,0.06))] text-violet-50 shadow-[0_18px_40px_rgba(167,139,250,0.14)]', icon: 'border-violet-300/15 bg-violet-400/[0.08] text-violet-200', iconActive: 'border-violet-300/30 bg-violet-400/[0.16] text-violet-50', }, sky: { idle: 'border-white/10 bg-white/[0.05] text-white hover:border-sky-400/30 hover:bg-sky-400/[0.12] hover:text-sky-100', active: 'border-sky-400/30 bg-[linear-gradient(135deg,rgba(56,189,248,0.18),rgba(255,255,255,0.06))] text-sky-50 shadow-[0_18px_40px_rgba(56,189,248,0.14)]', icon: 'border-sky-300/15 bg-sky-400/[0.08] text-sky-200', iconActive: 'border-sky-300/30 bg-sky-400/[0.16] text-sky-50', }, amber: { idle: 'border-white/10 bg-white/[0.05] text-white hover:border-amber-400/30 hover:bg-amber-400/[0.12] hover:text-amber-100', active: 'border-amber-400/30 bg-[linear-gradient(135deg,rgba(251,191,36,0.18),rgba(255,255,255,0.06))] text-amber-50 shadow-[0_18px_40px_rgba(251,191,36,0.14)]', icon: 'border-amber-300/15 bg-amber-400/[0.08] text-amber-200', iconActive: 'border-amber-300/30 bg-amber-400/[0.16] text-amber-50', }, } function CollectionHeroAction({ href = null, onClick = null, icon, label, tone = 'neutral', active = false, disabled = false, compact = false }) { const toneClasses = HERO_ACTION_TONES[tone] ?? HERO_ACTION_TONES.neutral const Component = href ? 'a' : 'button' const componentProps = href ? { href } : { type: 'button', onClick, disabled } return ( {label} ) } const HERO_METRIC_TONES = { sky: { icon: 'text-sky-200', chip: 'border-sky-300/20 bg-sky-400/[0.12]', glow: 'from-sky-400/35', orb: 'bg-sky-400/20', }, rose: { icon: 'text-rose-200', chip: 'border-rose-300/20 bg-rose-400/[0.12]', glow: 'from-rose-400/35', orb: 'bg-rose-400/20', }, emerald: { icon: 'text-emerald-200', chip: 'border-emerald-300/20 bg-emerald-400/[0.12]', glow: 'from-emerald-400/35', orb: 'bg-emerald-400/20', }, violet: { icon: 'text-violet-200', chip: 'border-violet-300/20 bg-violet-400/[0.12]', glow: 'from-violet-400/35', orb: 'bg-violet-400/20', }, amber: { icon: 'text-amber-200', chip: 'border-amber-300/20 bg-amber-400/[0.12]', glow: 'from-amber-400/35', orb: 'bg-amber-400/20', }, slate: { icon: 'text-slate-200', chip: 'border-white/10 bg-white/[0.08]', glow: 'from-white/20', orb: 'bg-white/[0.12]', }, } function HeroMetricCard({ icon, label, value, helper = null, tone = 'slate' }) { const style = HERO_METRIC_TONES[tone] ?? HERO_METRIC_TONES.slate return (
{value}
{label}
{helper ?
{helper}
: null}
) } function HeroSignalCard({ icon, label, value, description = null, tone = 'slate' }) { const style = HERO_METRIC_TONES[tone] ?? HERO_METRIC_TONES.slate return (
{label}
{value}
{description ?

{description}

: null}
) } function getSpotlightClasses(style) { switch (style) { case 'editorial': return 'border-amber-300/20 bg-[linear-gradient(135deg,rgba(120,53,15,0.45),rgba(2,6,23,0.82))] text-amber-50' case 'seasonal': return 'border-emerald-300/20 bg-[linear-gradient(135deg,rgba(6,78,59,0.5),rgba(2,6,23,0.82))] text-emerald-50' case 'challenge': return 'border-fuchsia-300/20 bg-[linear-gradient(135deg,rgba(112,26,117,0.48),rgba(2,6,23,0.82))] text-fuchsia-50' case 'community': return 'border-sky-300/20 bg-[linear-gradient(135deg,rgba(3,105,161,0.45),rgba(2,6,23,0.82))] text-sky-50' default: return 'border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.9),rgba(30,41,59,0.72))] text-white' } } function EmptyCollectionState({ isOwner, smart = false }) { return (

This collection is still taking shape

{isOwner ? (smart ? 'Adjust the smart rules to broaden the match or publish more artworks that fit this set.' : 'Add artworks to start building the visual rhythm, cover image, and sequence for this showcase.') : (smart ? 'This smart collection does not have visible matches right now.' : 'There are no visible artworks in this collection right now.')}

) } function OwnerCard({ owner, collectionType }) { const label = owner?.is_system ? 'Editorial Owner' : collectionType === 'editorial' ? 'Editorial Curator' : 'Curator' const body = (
{owner?.avatar_url ? ( {owner?.name ) : (
)}
{label}
{owner?.name || owner?.username || 'Skinbase Curator'}
{owner?.username ?
@{owner.username}
: null}
{owner?.profile_url ? : null}
) if (owner?.profile_url) { return (
) } return (
{body}
) } function PageSection({ eyebrow, title, count, icon, children }) { return (
{icon && (
)}

{eyebrow}

{title}

{count !== undefined ? {count} : null}
{children}
) } function EntityLinkCard({ item }) { return (
{item?.image_url ? {item?.title} :
}

{item?.title}

{item?.meta ? {item.meta} : null}
{item?.subtitle ?

{item.subtitle}

: null} {item?.relationship_type ?

{item.relationship_type}

: null}
{item?.description ?

{item.description}

: null}
) } function CollectionCover({ collection }) { const coverImage = collection?.cover_image const coverMaturity = collection?.cover_image_maturity || null const shouldBlur = Boolean(coverMaturity?.should_blur) const isMature = Boolean(coverMaturity?.is_mature_effective) if (!coverImage) { return
} return (
{collection?.title} {isMature ?
Mature cover
: null} {shouldBlur ?
Blurred by your settings
: null}
) } function humanizeToken(value) { return String(value || '') .replaceAll('_', ' ') .replaceAll('-', ' ') .replace(/\b\w/g, (match) => match.toUpperCase()) } function groupEntityLinks(items) { return (Array.isArray(items) ? items : []).reduce((groups, item) => { const key = item?.linked_type || 'other' if (!groups[key]) groups[key] = [] groups[key].push(item) return groups }, {}) } function recommendationReasons(currentCollection, candidate) { const reasons = [] if (candidate?.event_key && currentCollection?.event_key && candidate.event_key === currentCollection.event_key) { reasons.push('Same event context') } if (candidate?.campaign_key && currentCollection?.campaign_key && candidate.campaign_key === currentCollection.campaign_key) { reasons.push('Same campaign') } if (candidate?.theme_token && currentCollection?.theme_token && candidate.theme_token === currentCollection.theme_token) { reasons.push('Shared theme') } if (candidate?.type && currentCollection?.type && candidate.type === currentCollection.type) { reasons.push(`${humanizeToken(candidate.type)} collection`) } if (candidate?.owner?.id && currentCollection?.owner?.id && candidate.owner.id === currentCollection.owner.id) { reasons.push('Same curator') } if (candidate?.trust_tier && currentCollection?.trust_tier && candidate.trust_tier === currentCollection.trust_tier) { reasons.push(`${humanizeToken(candidate.trust_tier)} trust tier`) } return reasons.slice(0, 3) } const CONTEXT_SIGNAL_TYPES = { Campaign: { icon: 'fa-solid fa-bullhorn', accent: 'border-orange-300/20 from-orange-400/50', badge: 'border-orange-300/20 bg-orange-400/10 text-orange-200', kicker: 'text-orange-300/80' }, Event: { icon: 'fa-solid fa-calendar-star', accent: 'border-sky-300/20 from-sky-400/50', badge: 'border-sky-300/20 bg-sky-400/10 text-sky-200', kicker: 'text-sky-300/80' }, Program: { icon: 'fa-solid fa-layer-group', accent: 'border-violet-300/20 from-violet-400/50', badge: 'border-violet-300/20 bg-violet-400/10 text-violet-200', kicker: 'text-violet-300/80' }, Theme: { icon: 'fa-solid fa-palette', accent: 'border-teal-300/20 from-teal-400/50', badge: 'border-teal-300/20 bg-teal-400/10 text-teal-200', kicker: 'text-teal-300/80' }, 'Quality Tier': { icon: 'fa-solid fa-gauge-high', accent: 'border-amber-300/20 from-amber-400/50', badge: 'border-amber-300/20 bg-amber-400/10 text-amber-200', kicker: 'text-amber-300/80' }, } function ContextSignalCard({ item }) { const typeStyle = CONTEXT_SIGNAL_TYPES[item.meta] ?? { icon: 'fa-solid fa-circle-info', accent: 'border-white/10 from-slate-400/30', badge: 'border-white/10 bg-white/[0.05] text-slate-300', kicker: 'text-sky-200/80' } const wrapperClassName = `relative overflow-hidden flex h-full flex-col gap-3 rounded-[24px] border ${typeStyle.accent.split(' ')[0]} bg-white/[0.04] p-5 transition hover:bg-white/[0.07]` const body = ( <>
{item.meta}
{item.kicker ? {item.kicker} : null}

{item.title}

{item.subtitle ?

{item.subtitle}

: null}
{item.description ?

{item.description}

: null} ) if (item.url) { return {body} } return
{body}
} export default function CollectionShow() { const { props } = usePage() const { collection: initialCollection, artworks, owner, isOwner, manageUrl, editUrl, analyticsUrl, historyUrl, profileCollectionsUrl, featuredCollectionsUrl, engagement, seo, members: initialMembers, comments: initialComments, submissions: initialSubmissions, entityLinks, relatedCollections, commentsEndpoint, submitEndpoint, submissionArtworkOptions, seriesContext, canSubmit, canComment, reportEndpoint, } = props const [collection, setCollection] = React.useState(initialCollection) const [comments, setComments] = React.useState(initialComments || []) const [submissions, setSubmissions] = React.useState(initialSubmissions || []) const [selectedArtworkId, setSelectedArtworkId] = React.useState(submissionArtworkOptions?.[0]?.id || '') const [state, setState] = React.useState({ liked: Boolean(engagement?.liked), following: Boolean(engagement?.following), saved: Boolean(engagement?.saved), notice: '', busy: false, }) const artworkItems = artworks?.data ?? [] const creatorIds = Array.from(new Set(artworkItems.map((artwork) => artwork?.author?.id).filter(Boolean))) const featuringCreatorsCount = creatorIds.length const showArtworkAuthors = collection?.type !== 'personal' || featuringCreatorsCount > 1 const enabledModules = Array.isArray(collection?.layout_modules) ? collection.layout_modules.filter((module) => module?.enabled !== false) : [] const enabledModuleKeys = new Set(enabledModules.map((module) => module?.key).filter(Boolean)) const showIntroBlock = enabledModuleKeys.size === 0 || enabledModuleKeys.has('intro_block') const metaOwnerName = owner?.name || owner?.username || collection?.owner?.name || 'Skinbase Curator' const metaTitle = seo?.title || `${collection?.title} — Skinbase Nova` const metaDescription = seo?.description || collection?.summary || collection?.description || '' const collectionSchema = seo?.canonical ? { '@context': 'https://schema.org', '@type': 'CollectionPage', name: collection?.title, description: metaDescription, url: seo.canonical, image: seo?.og_image || collection?.cover_image || undefined, isPartOf: { '@type': 'WebSite', name: 'Skinbase Nova', url: typeof window !== 'undefined' ? window.location.origin : undefined, }, author: owner ? { '@type': 'Person', name: metaOwnerName, url: owner.profile_url, } : undefined, keywords: [collection?.type, collection?.mode, collection?.badge_label].filter(Boolean).join(', ') || undefined, mainEntity: { '@type': 'ItemList', numberOfItems: collection?.artworks_count || artworkItems.length || 0, itemListElement: artworkItems.slice(0, 12).map((artwork, index) => ({ '@type': 'ListItem', position: index + 1, url: artwork.url, name: artwork.title, })), }, } : null const [members] = React.useState(initialMembers || []) const spotlightClasses = getSpotlightClasses(collection?.spotlight_style) const groupedEntityLinks = groupEntityLinks(entityLinks) const storyLinks = groupedEntityLinks.story || [] const taxonomyLinks = [...(groupedEntityLinks.category || []), ...(groupedEntityLinks.tag || [])] const contributorLinks = [...(groupedEntityLinks.creator || []), ...(groupedEntityLinks.artwork || [])] const linkedContextSignals = [ ...(groupedEntityLinks.campaign || []).map((item) => ({ meta: item.meta || 'Campaign', kicker: item.relationship_type || 'Linked campaign', title: item.title, subtitle: item.subtitle, description: item.description, url: item.url, })), ...(groupedEntityLinks.event || []).map((item) => ({ meta: item.meta || 'Event', kicker: item.relationship_type || 'Linked event', title: item.title, subtitle: item.subtitle, description: item.description, url: item.url, })), ] const contextSignals = [ collection?.campaign_key ? { meta: 'Campaign', kicker: 'Discover surface', title: collection.campaign_label || humanizeToken(collection.campaign_key), subtitle: collection.campaign_key, description: 'This collection is programmed into a campaign surface and can be explored alongside other campaign-ready sets.', url: `/collections/campaigns/${encodeURIComponent(collection.campaign_key)}`, } : null, collection?.program_key ? { meta: 'Program', kicker: 'Partner context', title: humanizeToken(collection.program_key), subtitle: collection.partner_label || collection.partner_key || 'Collection program', description: 'This collection is attached to a program or partner-ready surface, which affects how it is grouped and surfaced.', url: `/collections/program/${encodeURIComponent(collection.program_key)}`, } : null, (collection?.event_label || collection?.event_key) ? { meta: 'Event', kicker: 'Seasonal context', title: collection.event_label || humanizeToken(collection.event_key), subtitle: collection.season_key ? `Season ${humanizeToken(collection.season_key)}` : 'Event-linked collection', description: 'This collection is tied to an event or seasonal programming window, so related recommendations favor matching event context.', url: null, } : null, collection?.theme_token ? { meta: 'Theme', kicker: 'Visual language', title: humanizeToken(collection.theme_token), subtitle: collection.presentation_style ? `Presentation ${humanizeToken(collection.presentation_style)}` : null, description: 'Theme and presentation signals help similar collections cluster together in discovery and recommendation surfaces.', url: `/collections/search?theme=${encodeURIComponent(collection.theme_token)}`, } : null, collection?.trust_tier ? { meta: 'Quality Tier', kicker: 'Placement signal', title: humanizeToken(collection.trust_tier), subtitle: collection?.quality_score != null ? `Quality score ${Number(collection.quality_score).toFixed(1)}` : null, description: 'Trust tier and quality score shape how aggressively this collection can be used in premium or partner-facing placements.', url: `/collections/search?quality_tier=${encodeURIComponent(collection.trust_tier)}`, } : null, ...linkedContextSignals, ].filter(Boolean).filter((item, index, items) => { const key = `${item.meta}:${item.title}:${item.subtitle || ''}` return items.findIndex((candidate) => `${candidate.meta}:${candidate.title}:${candidate.subtitle || ''}` === key) === index }) const heroMetrics = [ { icon: 'fa-images', label: 'Artworks', value: (collection?.artworks_count ?? 0).toLocaleString(), helper: showArtworkAuthors && featuringCreatorsCount > 1 ? `${featuringCreatorsCount} creators featured` : (collection?.mode === 'smart' ? 'Matched works' : 'Published pieces'), tone: 'sky', }, { icon: 'fa-heart', label: 'Likes', value: (collection?.likes_count ?? 0).toLocaleString(), helper: 'Community response', tone: 'rose', }, { icon: 'fa-bell', label: 'Followers', value: (collection?.followers_count ?? 0).toLocaleString(), helper: 'Watching updates', tone: 'emerald', }, { icon: 'fa-eye', label: 'Views', value: (collection?.views_count ?? 0).toLocaleString(), helper: 'Detail visits', tone: 'violet', }, { icon: 'fa-bookmark', label: 'Saves', value: (collection?.saves_count ?? 0).toLocaleString(), helper: 'Pinned for later', tone: 'amber', }, ] const heroSignals = [ collection?.quality_score != null ? { icon: 'fa-gauge-high', label: 'Quality', value: Number(collection.quality_score).toFixed(1), description: collection?.trust_tier ? `${humanizeToken(collection.trust_tier)} placement tier` : 'Placement quality signal', tone: 'emerald', } : null, collection?.ranking_score != null ? { icon: 'fa-ranking-star', label: 'Ranking', value: Number(collection.ranking_score).toFixed(1), description: 'Current discovery momentum score', tone: 'amber', } : null, collection?.presentation_style && collection.presentation_style !== 'standard' ? { icon: 'fa-panorama', label: 'Presentation', value: humanizeToken(collection.presentation_style), description: 'Visual treatment for this collection surface', tone: 'sky', } : null, collection?.campaign_key ? { icon: 'fa-bullhorn', label: 'Campaign', value: collection.campaign_label || humanizeToken(collection.campaign_key), description: 'Programmed into a campaign surface', tone: 'rose', } : null, ].filter(Boolean) const { share } = useWebShare({ onFallback: async ({ url }) => { if (navigator?.clipboard?.writeText) { await navigator.clipboard.writeText(url) setState((current) => ({ ...current, notice: 'Collection link copied.' })) } }, }) async function handleLike() { if (!engagement?.can_interact) { if (engagement?.login_url) window.location.assign(engagement.login_url) return } setState((current) => ({ ...current, busy: true, notice: '' })) try { const payload = await requestJson(state.liked ? engagement.unlike_url : engagement.like_url, { method: state.liked ? 'DELETE' : 'POST', }) setState((current) => ({ ...current, liked: Boolean(payload?.liked), busy: false })) setCollection((current) => ({ ...current, likes_count: payload?.likes_count ?? current.likes_count })) } catch (error) { setState((current) => ({ ...current, busy: false, notice: error.message })) } } async function handleFollow() { if (!engagement?.can_interact) { if (engagement?.login_url) window.location.assign(engagement.login_url) return } setState((current) => ({ ...current, busy: true, notice: '' })) try { const payload = await requestJson(state.following ? engagement.unfollow_url : engagement.follow_url, { method: state.following ? 'DELETE' : 'POST', }) setState((current) => ({ ...current, following: Boolean(payload?.following), busy: false })) setCollection((current) => ({ ...current, followers_count: payload?.followers_count ?? current.followers_count })) } catch (error) { setState((current) => ({ ...current, busy: false, notice: error.message })) } } async function handleShare() { try { const payload = await requestJson(engagement?.share_url, { method: 'POST' }) setCollection((current) => ({ ...current, shares_count: payload?.shares_count ?? current.shares_count })) await share({ title: collection?.title, text: collection?.summary || collection?.description || `Explore ${collection?.title} on Skinbase Nova.`, url: collection?.public_url, }) } catch (error) { setState((current) => ({ ...current, notice: error.message })) } } async function handleSave() { if (!engagement?.save_url) { if (engagement?.login_url) window.location.assign(engagement.login_url) return } setState((current) => ({ ...current, busy: true, notice: '' })) try { const payload = await requestJson(state.saved ? engagement.unsave_url : engagement.save_url, { method: state.saved ? 'DELETE' : 'POST', body: state.saved ? undefined : { context: 'collection_detail', context_meta: { collection_type: collection?.type || null, collection_mode: collection?.mode || null, }, }, }) setState((current) => ({ ...current, saved: Boolean(payload?.saved), busy: false })) setCollection((current) => ({ ...current, saves_count: payload?.saves_count ?? current.saves_count })) } catch (error) { setState((current) => ({ ...current, busy: false, notice: error.message })) } } async function handleCommentSubmit(body) { const payload = await requestJson(commentsEndpoint, { method: 'POST', body: { body }, }) setComments(payload?.comments || []) setCollection((current) => ({ ...current, comments_count: payload?.comments_count ?? current.comments_count })) } async function handleDeleteComment(commentId) { const payload = await requestJson(`${commentsEndpoint}/${commentId}`, { method: 'DELETE' }) setComments(payload?.comments || []) setCollection((current) => ({ ...current, comments_count: payload?.comments_count ?? current.comments_count })) } async function handleSubmitArtwork() { if (!submitEndpoint || !selectedArtworkId) return try { const payload = await requestJson(submitEndpoint, { method: 'POST', body: { artwork_id: selectedArtworkId }, }) setSubmissions(payload?.submissions || []) setState((current) => ({ ...current, notice: 'Artwork submitted for review.' })) } catch (error) { setState((current) => ({ ...current, notice: error.message })) } } async function handleSubmissionAction(submission, action) { const url = action === 'approve' ? `/collections/submissions/${submission.id}/approve` : action === 'reject' ? `/collections/submissions/${submission.id}/reject` : `/collections/submissions/${submission.id}` const payload = await requestJson(url, { method: action === 'withdraw' ? 'DELETE' : 'POST', }) setSubmissions(payload?.submissions || []) if (action === 'approve') { setCollection((current) => ({ ...current, artworks_count: (current?.artworks_count ?? 0) + 1 })) } } async function handleReport(targetType, targetId) { if (!reportEndpoint) { if (engagement?.login_url) window.location.assign(engagement.login_url) return } const reason = window.prompt('Why are you reporting this? (required)') if (!reason || !reason.trim()) return try { await requestJson(reportEndpoint, { method: 'POST', body: { target_type: targetType, target_id: targetId, reason: reason.trim(), }, }) setState((current) => ({ ...current, notice: 'Report submitted. Thank you.' })) } catch (error) { setState((current) => ({ ...current, notice: error.message })) } } function renderModule(module) { if (!module?.key) return null if (module.key === 'intro_block') { return null } if (module.key === 'featured_artworks') { if (!artworkItems.length) return null return (

Start with the standout pieces from this collection before diving into the full sequence.

) } if (module.key === 'editorial_note') { if (collection?.type !== 'editorial') return null return (
{collection?.description || 'A staff-curated collection prepared for premium discovery placement.'}
{(collection?.event_label || collection?.badge_label) ? (
{collection?.event_label ? {collection.event_label} : null} {collection?.badge_label ? {collection.badge_label} : null}
) : null}
) } if (module.key === 'artwork_grid') { return (
{artworkItems.length ? : } {(artworks?.links?.prev || artworks?.links?.next) ? ( ) : null}
) } if (module.key === 'discussion') { if (!collection?.allow_comments) return null return ( {canComment ?
: null}
handleReport('collection_comment', comment.id)} emptyMessage="No comments yet." />
) } if (module.key === 'related_collections') { if (!Array.isArray(relatedCollections) || !relatedCollections.length) return null return (
{relatedCollections.map((item) => (
{recommendationReasons(collection, item).map((reason) => ( {reason} ))}
))}
) } if (module.key === 'collaborators') { return (
{members.length ? members.filter((member) => member?.status === 'active').map((member) => ) :

This collection is curated by a single owner right now.

}
) } if (module.key === 'submissions') { if (!collection?.allow_submissions) return null return ( {canSubmit && submissionArtworkOptions?.length ? (
setSelectedArtworkId(val)} placeholder="Select artwork" options={submissionArtworkOptions.map((a) => ({ value: String(a.id), label: a.title }))} />
) : (

Sign in with at least one artwork on your account to submit here.

)}
{submissions.length ? submissions.map((submission) => ( handleSubmissionAction(item, 'approve')} onReject={(item) => handleSubmissionAction(item, 'reject')} onWithdraw={(item) => handleSubmissionAction(item, 'withdraw')} onReport={(item) => handleReport('collection_submission', item.id)} /> )) :

No submissions yet.

}
) } return null } const renderedFullModules = enabledModules.filter((module) => module.slot === 'full').map(renderModule).filter(Boolean) const renderedMainModules = enabledModules.filter((module) => module.slot === 'main').map(renderModule).filter(Boolean) const renderedSidebarModules = enabledModules.filter((module) => module.slot === 'sidebar').map(renderModule).filter(Boolean) return ( <>