import React, { useEffect, useState } from 'react' import CollectionCard from '../collections/CollectionCard' import CollectionEmptyState from '../collections/CollectionEmptyState' function getCsrfToken() { if (typeof document === 'undefined') return '' return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' } async function deleteCollection(url) { const response = await fetch(url, { method: 'DELETE', credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken(), 'X-Requested-With': 'XMLHttpRequest', }, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || 'Unable to delete collection.') } return payload } 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 || 'Unable to update collection presentation.') } return payload } const FILTERS = ['all', 'featured', 'smart', 'manual'] export default function TabCollections({ collections, isOwner, createUrl, reorderUrl, featuredUrl, featureLimit = 3 }) { const [items, setItems] = useState(Array.isArray(collections) ? collections : []) const [busyId, setBusyId] = useState(null) const [filter, setFilter] = useState('all') useEffect(() => { setItems(Array.isArray(collections) ? collections : []) }, [collections]) async function handleDelete(collection) { if (!collection?.delete_url) return if (!window.confirm(`Delete "${collection.title}"? Artworks will remain untouched.`)) return setBusyId(collection.id) try { await deleteCollection(collection.delete_url) setItems((current) => current.filter((item) => item.id !== collection.id)) } catch (error) { window.alert(error.message) } finally { setBusyId(null) } } async function handleToggleFeature(collection) { const url = collection?.is_featured ? collection?.unfeature_url : collection?.feature_url const method = collection?.is_featured ? 'DELETE' : 'POST' if (!url) return setBusyId(collection.id) try { const payload = await requestJson(url, { method }) setItems((current) => current.map((item) => ( item.id === collection.id ? { ...item, is_featured: payload?.collection?.is_featured ?? !item.is_featured, featured_at: payload?.collection?.featured_at ?? item.featured_at, updated_at: payload?.collection?.updated_at ?? item.updated_at, } : item ))) } catch (error) { window.alert(error.message) } finally { setBusyId(null) } } async function handleMove(collection, direction) { const index = items.findIndex((item) => item.id === collection.id) const nextIndex = index + direction if (index < 0 || nextIndex < 0 || nextIndex >= items.length || !reorderUrl) return const next = [...items] const temp = next[index] next[index] = next[nextIndex] next[nextIndex] = temp setItems(next) try { const payload = await requestJson(reorderUrl, { method: 'POST', body: { collection_ids: next.map((item) => item.id) }, }) if (Array.isArray(payload?.collections)) { setItems(payload.collections) } } catch (error) { window.alert(error.message) setItems(Array.isArray(collections) ? collections : []) } } const featuredItems = items.filter((collection) => collection.is_featured) const smartItems = items.filter((collection) => collection.mode === 'smart') const filteredItems = items.filter((collection) => { if (filter === 'featured') return collection.is_featured if (filter === 'smart') return collection.mode === 'smart' if (filter === 'manual') return collection.mode !== 'smart' return true }) return (

Collections

Curated showcases from the gallery

Collections now support featured presentation, smart rule-based curation, and richer profile storytelling.

{featuredUrl ? Browse Featured : null} {isOwner && createUrl ? Create Collection : null}
{FILTERS.map((value) => ( ))}
{items.length > 0 && featuredItems.length > 0 && filter === 'all' ? (

Featured Collections

Premium profile showcases

{isOwner ?

{featuredItems.length}/{featureLimit} featured

: null}
{featuredItems.map((collection, index) => ( handleMove(collection, -1) : null} onMoveDown={isOwner ? () => handleMove(collection, 1) : null} canMoveUp={index > 0} canMoveDown={index < featuredItems.length - 1} busy={busyId === collection.id} /> ))}
) : null} {isOwner && items.length > 0 && featuredItems.length === 0 ?
Feature your best collections to pin them at the top of your profile.
: null} {isOwner && items.length > 0 && smartItems.length === 0 ?
Create a smart collection from your tags or categories to keep a showcase updated automatically.
: null} {items.length === 0 ? ( ) : (
{filteredItems.map((collection, index) => ( handleMove(collection, -1) : null} onMoveDown={isOwner ? () => handleMove(collection, 1) : null} canMoveUp={index > 0} canMoveDown={index < filteredItems.length - 1} busy={busyId === collection.id} /> ))}
)}
) }