490 lines
27 KiB
JavaScript
490 lines
27 KiB
JavaScript
import React from 'react'
|
|
import { Head, usePage } from '@inertiajs/react'
|
|
import CollectionCard from '../../components/profile/collections/CollectionCard'
|
|
|
|
const SEARCH_SELECT_OPTIONS = {
|
|
type: [
|
|
{ value: 'personal', label: 'Personal' },
|
|
{ value: 'community', label: 'Community' },
|
|
{ value: 'editorial', label: 'Editorial' },
|
|
],
|
|
mode: [
|
|
{ value: 'manual', label: 'Manual' },
|
|
{ value: 'smart', label: 'Smart' },
|
|
],
|
|
lifecycle_state: [
|
|
{ value: 'published', label: 'Published' },
|
|
{ value: 'featured', label: 'Featured' },
|
|
{ value: 'archived', label: 'Archived' },
|
|
],
|
|
health_state: [
|
|
{ value: 'healthy', label: 'Healthy' },
|
|
{ value: 'needs_metadata', label: 'Needs metadata' },
|
|
{ value: 'stale', label: 'Stale' },
|
|
{ value: 'low_content', label: 'Low content' },
|
|
{ value: 'broken_items', label: 'Broken items' },
|
|
{ value: 'weak_cover', label: 'Weak cover' },
|
|
{ value: 'low_engagement', label: 'Low engagement' },
|
|
{ value: 'duplicate_risk', label: 'Duplicate risk' },
|
|
{ value: 'merge_candidate', label: 'Merge candidate' },
|
|
],
|
|
sort: [
|
|
{ value: 'trending', label: 'Trending' },
|
|
{ value: 'recent', label: 'Recent' },
|
|
{ value: 'quality', label: 'Quality' },
|
|
{ value: 'evergreen', label: 'Evergreen' },
|
|
],
|
|
}
|
|
|
|
function humanizeToken(value) {
|
|
return String(value || '')
|
|
.replaceAll('_', ' ')
|
|
.replaceAll('-', ' ')
|
|
.replace(/\b\w/g, (match) => match.toUpperCase())
|
|
}
|
|
|
|
function searchChipLabel(key, value) {
|
|
if (!value) return null
|
|
|
|
const option = SEARCH_SELECT_OPTIONS[key]?.find((item) => item.value === value)
|
|
const displayValue = option?.label || humanizeToken(value)
|
|
|
|
return key === 'q'
|
|
? `Query: ${value}`
|
|
: key === 'campaign_key'
|
|
? `Campaign: ${displayValue}`
|
|
: key === 'program_key'
|
|
? `Program: ${displayValue}`
|
|
: key === 'quality_tier'
|
|
? `Quality Tier: ${displayValue}`
|
|
: key === 'sort'
|
|
? `Sort: ${displayValue}`
|
|
: `${humanizeToken(key)}: ${displayValue}`
|
|
}
|
|
|
|
function buildSearchHref(filters, omitKey = null) {
|
|
const params = new URLSearchParams()
|
|
|
|
Object.entries(filters || {}).forEach(([key, value]) => {
|
|
if (key === omitKey) return
|
|
if (value === null || value === undefined || value === '') return
|
|
params.set(key, value)
|
|
})
|
|
|
|
const query = params.toString()
|
|
return query ? `/collections/search?${query}` : '/collections/search'
|
|
}
|
|
|
|
function activeSearchChips(filters) {
|
|
return Object.entries(filters || {})
|
|
.filter(([, value]) => value !== null && value !== undefined && value !== '')
|
|
.map(([key, value]) => ({
|
|
key,
|
|
label: searchChipLabel(key, value),
|
|
href: buildSearchHref(filters, key),
|
|
}))
|
|
.filter((chip) => chip.label)
|
|
}
|
|
|
|
function primarySaveContext({ search, campaign, program, title, eyebrow }) {
|
|
if (search) {
|
|
return {
|
|
context: 'collection_search',
|
|
meta: {
|
|
query: search?.filters?.q || null,
|
|
surface_label: 'collection search',
|
|
},
|
|
}
|
|
}
|
|
|
|
if (campaign) {
|
|
return {
|
|
context: 'campaign_landing',
|
|
meta: {
|
|
campaign_key: campaign.key,
|
|
campaign_label: campaign.label,
|
|
surface_label: campaign.label || 'campaign landing',
|
|
},
|
|
}
|
|
}
|
|
|
|
if (program) {
|
|
return {
|
|
context: 'program_landing',
|
|
meta: {
|
|
program_key: program.key,
|
|
program_label: program.label,
|
|
surface_label: program.label || 'program landing',
|
|
},
|
|
}
|
|
}
|
|
|
|
if (eyebrow === 'Trending') return { context: 'trending_landing', meta: { surface_label: 'trending collections' } }
|
|
if (eyebrow === 'Editorial') return { context: 'editorial_landing', meta: { surface_label: 'editorial collections' } }
|
|
if (eyebrow === 'Community') return { context: 'community_landing', meta: { surface_label: 'community collections' } }
|
|
if (eyebrow === 'Seasonal') return { context: 'seasonal_landing', meta: { surface_label: 'seasonal collections' } }
|
|
if (title === 'Recommended collections' || title === 'Collections worth exploring') return { context: 'recommended_landing', meta: { surface_label: 'recommended collections' } }
|
|
|
|
return {
|
|
context: 'featured_landing',
|
|
meta: { surface_label: 'featured collections' },
|
|
}
|
|
}
|
|
|
|
function HeroStat({ icon, label, value }) {
|
|
return (
|
|
<div className="rounded-[22px] border border-white/10 bg-white/[0.05] px-4 py-4">
|
|
<div className="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">
|
|
<i className={`fa-solid ${icon} text-[10px]`} />
|
|
{label}
|
|
</div>
|
|
<div className="mt-2 text-2xl font-semibold tracking-[-0.03em] text-white">{value}</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function EmptyState() {
|
|
return (
|
|
<div className="rounded-[32px] border border-dashed border-white/12 bg-white/[0.03] px-6 py-16 text-center">
|
|
<div className="mx-auto flex h-20 w-20 items-center justify-center rounded-[24px] border border-white/12 bg-white/[0.05] text-slate-400">
|
|
<i className="fa-solid fa-compass text-3xl" />
|
|
</div>
|
|
<h2 className="mt-5 text-2xl font-semibold text-white">No featured collections yet</h2>
|
|
<p className="mx-auto mt-3 max-w-xl text-sm leading-relaxed text-slate-300">
|
|
Featured placement is reserved for public collections with a strong visual point of view. Check back once creators start pinning their best showcases.
|
|
</p>
|
|
<div className="mt-6 flex justify-center">
|
|
<a
|
|
href="/"
|
|
className="inline-flex items-center gap-2 rounded-2xl border border-white/12 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]"
|
|
>
|
|
<i className="fa-solid fa-house fa-fw" />
|
|
Back to home
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SearchPanel({ search }) {
|
|
if (!search) return null
|
|
|
|
const filters = search.filters || {}
|
|
const options = search.options || {}
|
|
const chips = activeSearchChips(filters)
|
|
|
|
return (
|
|
<section className="mt-8 rounded-[32px] border border-white/10 bg-white/[0.04] p-6 backdrop-blur-sm md:p-7">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Filters</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-white">Search collections</h2>
|
|
</div>
|
|
<span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-xs font-semibold text-slate-300">{search?.meta?.total ?? 0} results</span>
|
|
</div>
|
|
<form method="GET" action="/collections/search" className="mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
<input name="q" defaultValue={filters.q || ''} placeholder="Search title or summary" className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35 xl:col-span-2" />
|
|
<select name="type" defaultValue={filters.type || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">All types</option>
|
|
<option value="personal">Personal</option>
|
|
<option value="community">Community</option>
|
|
<option value="editorial">Editorial</option>
|
|
</select>
|
|
<select name="sort" defaultValue={filters.sort || 'trending'} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="trending">Trending</option>
|
|
<option value="recent">Recent</option>
|
|
<option value="quality">Quality</option>
|
|
<option value="evergreen">Evergreen</option>
|
|
</select>
|
|
<select name="category" defaultValue={filters.category || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any category</option>
|
|
{(options.category || []).map((item) => (
|
|
<option key={`category-${item.value}`} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
<select name="mode" defaultValue={filters.mode || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any curation mode</option>
|
|
<option value="manual">Manual</option>
|
|
<option value="smart">Smart</option>
|
|
</select>
|
|
<select name="style" defaultValue={filters.style || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any style signal</option>
|
|
{(options.style || []).map((item) => (
|
|
<option key={`style-${item.value}`} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
<select name="lifecycle_state" defaultValue={filters.lifecycle_state || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any lifecycle</option>
|
|
<option value="published">Published</option>
|
|
<option value="featured">Featured</option>
|
|
<option value="archived">Archived</option>
|
|
</select>
|
|
<select name="theme" defaultValue={filters.theme || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any theme</option>
|
|
{(options.theme || []).map((item) => (
|
|
<option key={`theme-${item.value}`} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
<select name="health_state" defaultValue={filters.health_state || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any quality state</option>
|
|
<option value="healthy">Healthy</option>
|
|
<option value="needs_metadata">Needs metadata</option>
|
|
<option value="stale">Stale</option>
|
|
<option value="low_content">Low content</option>
|
|
<option value="broken_items">Broken items</option>
|
|
<option value="weak_cover">Weak cover</option>
|
|
<option value="low_engagement">Low engagement</option>
|
|
<option value="duplicate_risk">Duplicate risk</option>
|
|
<option value="merge_candidate">Merge candidate</option>
|
|
</select>
|
|
<select name="color" defaultValue={filters.color || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any color palette</option>
|
|
{(options.color || []).map((item) => (
|
|
<option key={`color-${item.value}`} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
<input name="campaign_key" defaultValue={filters.campaign_key || ''} placeholder="Campaign key" className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35" />
|
|
<input name="program_key" defaultValue={filters.program_key || ''} placeholder="Program key" className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35" />
|
|
<select name="quality_tier" defaultValue={filters.quality_tier || ''} className="rounded-2xl border border-white/10 bg-[#0d1726] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/35">
|
|
<option value="">Any quality tier</option>
|
|
{(options.quality_tier || []).map((item) => (
|
|
<option key={`quality-tier-${item.value}`} value={item.value}>{item.label}</option>
|
|
))}
|
|
</select>
|
|
<div className="md:col-span-2 xl:col-span-4 flex flex-wrap gap-3">
|
|
<button type="submit" className="inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15"><i className="fa-solid fa-magnifying-glass fa-fw" />Apply filters</button>
|
|
<a href="/collections/search" className="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.07]"><i className="fa-solid fa-rotate-left fa-fw" />Reset</a>
|
|
</div>
|
|
</form>
|
|
{chips.length ? (
|
|
<div className="mt-5 flex flex-wrap gap-3">
|
|
{chips.map((chip) => (
|
|
<a key={chip.key} href={chip.href} className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-3 py-1.5 text-xs font-semibold text-slate-200 transition hover:bg-white/[0.08]">
|
|
<span>{chip.label}</span>
|
|
<i className="fa-solid fa-xmark text-[10px] text-slate-400" />
|
|
</a>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
{(search?.links?.prev || search?.links?.next) ? (
|
|
<div className="mt-5 flex flex-wrap gap-3 text-sm">
|
|
{search.links.prev ? <a href={search.links.prev} className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-white transition hover:bg-white/[0.07]"><i className="fa-solid fa-arrow-left fa-fw text-[10px]" />Previous</a> : null}
|
|
{search.links.next ? <a href={search.links.next} className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-white transition hover:bg-white/[0.07]">Next<i className="fa-solid fa-arrow-right fa-fw text-[10px]" /></a> : null}
|
|
</div>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export default function CollectionFeaturedIndex() {
|
|
const { props } = usePage()
|
|
const seo = props.seo || {}
|
|
const eyebrow = props.eyebrow || 'Discovery'
|
|
const title = props.title || 'Featured collections'
|
|
const description = props.description || 'A rotating set of standout galleries from across Skinbase Nova. Some are meticulously hand-sequenced. Others are smart collections that stay fresh as the creator publishes new work.'
|
|
const collections = Array.isArray(props.collections) ? props.collections : []
|
|
const communityCollections = Array.isArray(props.communityCollections) ? props.communityCollections : []
|
|
const editorialCollections = Array.isArray(props.editorialCollections) ? props.editorialCollections : []
|
|
const recentCollections = Array.isArray(props.recentCollections) ? props.recentCollections : []
|
|
const trendingCollections = Array.isArray(props.trendingCollections) ? props.trendingCollections : []
|
|
const seasonalCollections = Array.isArray(props.seasonalCollections) ? props.seasonalCollections : []
|
|
const campaign = props.campaign || null
|
|
const program = props.program || null
|
|
const search = props.search || null
|
|
const smartCount = collections.filter((collection) => collection?.mode === 'smart').length
|
|
const totalArtworks = collections.reduce((sum, collection) => sum + (collection?.artworks_count || 0), 0)
|
|
const mainSave = primarySaveContext({ search, campaign, program, title, eyebrow })
|
|
const listSchema = seo?.canonical ? {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'CollectionPage',
|
|
name: title,
|
|
description,
|
|
url: seo.canonical,
|
|
mainEntity: {
|
|
'@type': 'ItemList',
|
|
numberOfItems: collections.length,
|
|
itemListElement: collections.slice(0, 18).map((collection, index) => ({
|
|
'@type': 'ListItem',
|
|
position: index + 1,
|
|
url: collection.url,
|
|
name: collection.title,
|
|
})),
|
|
},
|
|
} : null
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>{seo?.title || `${title} — Skinbase Nova`}</title>
|
|
<meta name="description" content={seo?.description || description} />
|
|
{seo?.canonical ? <link rel="canonical" href={seo.canonical} /> : null}
|
|
<meta name="robots" content={seo?.robots || 'index,follow'} />
|
|
<meta property="og:title" content={seo?.title || `${title} — Skinbase Nova`} />
|
|
<meta property="og:description" content={seo?.description || description} />
|
|
{seo?.canonical ? <meta property="og:url" content={seo.canonical} /> : null}
|
|
<meta property="og:type" content="website" />
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content={seo?.title || `${title} — Skinbase Nova`} />
|
|
<meta name="twitter:description" content={seo?.description || description} />
|
|
{listSchema ? <script type="application/ld+json">{JSON.stringify(listSchema)}</script> : null}
|
|
</Head>
|
|
|
|
<div className="relative min-h-screen overflow-hidden pb-16">
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[38rem] opacity-95"
|
|
style={{
|
|
background: 'radial-gradient(circle at 12% 14%, rgba(56,189,248,0.18), transparent 28%), radial-gradient(circle at 88% 16%, rgba(249,115,22,0.18), transparent 26%), linear-gradient(180deg, #07101d 0%, #0a1220 42%, #08111f 100%)',
|
|
}}
|
|
/>
|
|
<div aria-hidden="true" className="pointer-events-none absolute inset-0 -z-10 opacity-[0.05]" style={{ backgroundImage: 'url(/gfx/noise.png)', backgroundSize: '180px' }} />
|
|
|
|
<div className="mx-auto max-w-7xl px-4 pt-8 md:px-6">
|
|
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-300">
|
|
<a href="/" className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white">
|
|
<i className="fa-solid fa-arrow-left fa-fw text-[11px]" />
|
|
Back to home
|
|
</a>
|
|
<a href="/collections/featured" className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white">Featured</a>
|
|
<a href="/collections/trending" className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white">Trending</a>
|
|
<a href="/collections/community" className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white">Community</a>
|
|
<a href="/collections/editorial" className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white">Editorial</a>
|
|
<a href="/collections/seasonal" className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 transition hover:bg-white/[0.07] hover:text-white">Seasonal</a>
|
|
</div>
|
|
|
|
<section className="mt-6 overflow-hidden rounded-[34px] border border-white/10 bg-white/[0.04] p-6 shadow-[0_30px_90px_rgba(2,6,23,0.28)] backdrop-blur-sm md:p-8">
|
|
<div className="grid gap-8 xl:grid-cols-[minmax(0,1.18fr)_400px] xl:items-end">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">{eyebrow}</p>
|
|
<h1 className="mt-3 text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl">{title}</h1>
|
|
{campaign?.badge_label ? (
|
|
<div className="mt-4 inline-flex items-center rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100">
|
|
{campaign.badge_label}
|
|
</div>
|
|
) : program?.promotion_tier ? (
|
|
<div className="mt-4 inline-flex items-center rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-sky-100">
|
|
Promotion tier: {program.promotion_tier}
|
|
</div>
|
|
) : null}
|
|
<p className="mt-4 max-w-3xl text-sm leading-relaxed text-slate-300 md:text-[15px]">
|
|
{description}
|
|
</p>
|
|
{campaign ? (
|
|
<div className="mt-5 flex flex-wrap gap-3 text-xs text-slate-300">
|
|
<span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Campaign key: {campaign.key}</span>
|
|
{campaign.event_label ? <span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Event: {campaign.event_label}</span> : null}
|
|
{campaign.season_key ? <span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Season: {campaign.season_key}</span> : null}
|
|
{Array.isArray(campaign.active_surface_keys) && campaign.active_surface_keys.length ? <span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Surfaces: {campaign.active_surface_keys.join(', ')}</span> : null}
|
|
</div>
|
|
) : program ? (
|
|
<div className="mt-5 flex flex-wrap gap-3 text-xs text-slate-300">
|
|
<span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Program key: {program.key}</span>
|
|
<span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Collections: {program.collections_count ?? collections.length}</span>
|
|
{program.trust_tier ? <span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Trust: {program.trust_tier}</span> : null}
|
|
{Array.isArray(program.partner_labels) && program.partner_labels.length ? <span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Partners: {program.partner_labels.join(', ')}</span> : null}
|
|
{Array.isArray(program.sponsorship_labels) && program.sponsorship_labels.length ? <span className="rounded-full border border-white/10 bg-white/[0.05] px-3 py-2">Sponsors: {program.sponsorship_labels.join(', ')}</span> : null}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
|
|
<HeroStat icon="fa-layer-group" label="Collections" value={collections.length.toLocaleString()} />
|
|
<HeroStat icon="fa-wand-magic-sparkles" label="Smart" value={smartCount.toLocaleString()} />
|
|
<HeroStat icon="fa-images" label="Artworks" value={totalArtworks.toLocaleString()} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="mt-8">
|
|
<SearchPanel search={search} />
|
|
</section>
|
|
|
|
<section className="mt-8">
|
|
{collections.length ? (
|
|
<div className="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{collections.map((collection) => (
|
|
<CollectionCard key={collection.id} collection={collection} isOwner={false} saveContext={mainSave.context} saveContextMeta={mainSave.meta} />
|
|
))}
|
|
</div>
|
|
) : (
|
|
<EmptyState />
|
|
)}
|
|
</section>
|
|
|
|
{communityCollections.length ? (
|
|
<section className="mt-10">
|
|
<div className="flex items-end justify-between gap-4">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Community</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-white">Collaborative picks</h2>
|
|
</div>
|
|
</div>
|
|
<div className="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{communityCollections.map((collection) => (
|
|
<CollectionCard key={collection.id} collection={collection} isOwner={false} saveContext="community_row" saveContextMeta={{ surface_label: 'community collections' }} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
) : null}
|
|
|
|
{trendingCollections.length ? (
|
|
<section className="mt-10">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Trending</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-white">Momentum right now</h2>
|
|
</div>
|
|
<div className="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{trendingCollections.map((collection) => (
|
|
<CollectionCard key={collection.id} collection={collection} isOwner={false} saveContext="trending_row" saveContextMeta={{ surface_label: 'trending collections' }} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
) : null}
|
|
|
|
{editorialCollections.length ? (
|
|
<section className="mt-10">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Editorial</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-white">Staff and campaign collections</h2>
|
|
</div>
|
|
<div className="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{editorialCollections.map((collection) => (
|
|
<CollectionCard key={collection.id} collection={collection} isOwner={false} saveContext="editorial_row" saveContextMeta={{ surface_label: 'editorial collections' }} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
) : null}
|
|
|
|
{seasonalCollections.length ? (
|
|
<section className="mt-10">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Seasonal</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-white">Campaign and event spotlights</h2>
|
|
</div>
|
|
<div className="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{seasonalCollections.map((collection) => (
|
|
<CollectionCard key={collection.id} collection={collection} isOwner={false} saveContext="seasonal_row" saveContextMeta={{ surface_label: 'seasonal collections' }} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
) : null}
|
|
|
|
{recentCollections.length ? (
|
|
<section className="mt-10 pb-8">
|
|
<div>
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-200/80">Recent</p>
|
|
<h2 className="mt-2 text-2xl font-semibold text-white">Freshly published collections</h2>
|
|
</div>
|
|
<div className="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{recentCollections.map((collection) => (
|
|
<CollectionCard key={collection.id} collection={collection} isOwner={false} saveContext="recent_row" saveContextMeta={{ surface_label: 'recent collections' }} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|