optimizations
This commit is contained in:
127
resources/js/Pages/Home/HomeCollections.jsx
Normal file
127
resources/js/Pages/Home/HomeCollections.jsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from 'react'
|
||||
import CollectionCard from '../../components/profile/collections/CollectionCard'
|
||||
|
||||
function normalizeItems(items) {
|
||||
if (!Array.isArray(items)) return []
|
||||
|
||||
return items.filter((item) => item && typeof item === 'object')
|
||||
}
|
||||
|
||||
function SectionHeader({ title, subtitle, href, ctaLabel = 'See all' }) {
|
||||
return (
|
||||
<div className="mb-5 flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-white">{title}</h3>
|
||||
{subtitle ? <p className="mt-1 text-xs text-nova-400">{subtitle}</p> : null}
|
||||
</div>
|
||||
{href ? (
|
||||
<a href={href} className="shrink-0 text-sm text-nova-300 transition hover:text-white">
|
||||
{ctaLabel} →
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CollectionStrip({ items }) {
|
||||
if (!items.length) return null
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{items.map((collection) => (
|
||||
<CollectionCard key={collection.id} collection={collection} isOwner={false} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CollectionSection({ title, subtitle, href, items, limit = 3, ctaLabel }) {
|
||||
const normalized = normalizeItems(items).slice(0, limit)
|
||||
if (!normalized.length) return null
|
||||
|
||||
return (
|
||||
<section className="mt-10">
|
||||
<SectionHeader title={title} subtitle={subtitle} href={href} ctaLabel={ctaLabel} />
|
||||
<CollectionStrip items={normalized} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default function HomeCollections({
|
||||
featured,
|
||||
recent,
|
||||
trending,
|
||||
editorial,
|
||||
community,
|
||||
isLoggedIn = false,
|
||||
}) {
|
||||
const featuredItems = normalizeItems(featured)
|
||||
const recentItems = normalizeItems(recent)
|
||||
const trendingItems = normalizeItems(trending)
|
||||
const editorialItems = normalizeItems(editorial)
|
||||
const communityItems = normalizeItems(community)
|
||||
|
||||
if (!featuredItems.length && !recentItems.length && !trendingItems.length && !editorialItems.length && !communityItems.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-6 flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">Curated Collections</h2>
|
||||
<p className="mt-1 max-w-2xl text-sm text-nova-300">
|
||||
Hand-built galleries, smart collections, and community showcases worth opening next.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 text-xs font-semibold uppercase tracking-[0.16em] text-nova-400">
|
||||
{isLoggedIn && recentItems.length ? <span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1">Recent</span> : null}
|
||||
{featuredItems.length ? <span className="rounded-full border border-amber-300/20 bg-amber-300/10 px-3 py-1 text-amber-100">Featured</span> : null}
|
||||
{communityItems.length ? <span className="rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-sky-100">Community</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionSection
|
||||
title="Featured Collections"
|
||||
subtitle="Standout galleries with strong sequencing, presentation, or curator voice."
|
||||
href="/collections/featured"
|
||||
items={featuredItems}
|
||||
limit={3}
|
||||
/>
|
||||
|
||||
{isLoggedIn ? (
|
||||
<CollectionSection
|
||||
title="Recently Active"
|
||||
subtitle="Fresh collection activity from around the site, including new updates and resurfacing galleries."
|
||||
href="/collections/trending"
|
||||
items={recentItems}
|
||||
limit={3}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<CollectionSection
|
||||
title="Trending Collections"
|
||||
subtitle="Collections getting the strongest mix of follows, saves, and engagement right now."
|
||||
href="/collections/trending"
|
||||
items={trendingItems}
|
||||
limit={3}
|
||||
/>
|
||||
|
||||
<CollectionSection
|
||||
title="Editorial Picks"
|
||||
subtitle="Staff and premium editorial showcases with stronger themes and presentation rules."
|
||||
href="/collections/editorial"
|
||||
items={editorialItems}
|
||||
limit={3}
|
||||
/>
|
||||
|
||||
<CollectionSection
|
||||
title="Community Highlights"
|
||||
subtitle="Collaborative and submission-friendly collections that spotlight multiple creators together."
|
||||
href="/collections/community"
|
||||
items={communityItems}
|
||||
limit={3}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import React, { lazy, Suspense } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
// Above-fold — eager
|
||||
import HomeHero from './HomeHero'
|
||||
|
||||
// Below-fold — lazy-loaded to keep initial bundle small
|
||||
const HomeWelcomeRow = lazy(() => import('./HomeWelcomeRow'))
|
||||
const HomeFromFollowing = lazy(() => import('./HomeFromFollowing'))
|
||||
@@ -13,6 +10,7 @@ const HomeSuggestedCreators = lazy(() => import('./HomeSuggestedCreators'))
|
||||
const HomeTrending = lazy(() => import('./HomeTrending'))
|
||||
const HomeRising = lazy(() => import('./HomeRising'))
|
||||
const HomeFresh = lazy(() => import('./HomeFresh'))
|
||||
const HomeCollections = lazy(() => import('./HomeCollections'))
|
||||
const HomeCategories = lazy(() => import('./HomeCategories'))
|
||||
const HomeTags = lazy(() => import('./HomeTags'))
|
||||
const HomeCreators = lazy(() => import('./HomeCreators'))
|
||||
@@ -26,12 +24,10 @@ function SectionFallback() {
|
||||
}
|
||||
|
||||
function GuestHomePage(props) {
|
||||
const { hero, rising, trending, fresh, tags, creators, news } = props
|
||||
const { rising, trending, fresh, tags, creators, news, collections_featured, collections_trending, collections_editorial, collections_community } = props
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 1. Hero */}
|
||||
<HomeHero artwork={hero} isLoggedIn={false} />
|
||||
<Suspense fallback={<SectionFallback />}>
|
||||
<HomeRising items={rising} />
|
||||
</Suspense>
|
||||
@@ -44,6 +40,15 @@ function GuestHomePage(props) {
|
||||
<HomeFresh items={fresh} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback={<SectionFallback />}>
|
||||
<HomeCollections
|
||||
featured={collections_featured}
|
||||
trending={collections_trending}
|
||||
editorial={collections_editorial}
|
||||
community={collections_community}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* 4. Explore Categories */}
|
||||
<Suspense fallback={<SectionFallback />}>
|
||||
<HomeCategories />
|
||||
@@ -75,12 +80,16 @@ function GuestHomePage(props) {
|
||||
function AuthHomePage(props) {
|
||||
const {
|
||||
user_data,
|
||||
hero,
|
||||
for_you,
|
||||
from_following,
|
||||
rising,
|
||||
trending,
|
||||
fresh,
|
||||
by_tags,
|
||||
collections_featured,
|
||||
collections_recent,
|
||||
collections_trending,
|
||||
collections_editorial,
|
||||
collections_community,
|
||||
by_categories,
|
||||
suggested_creators,
|
||||
tags,
|
||||
@@ -91,9 +100,6 @@ function AuthHomePage(props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 1. Hero — flush to top */}
|
||||
<HomeHero artwork={hero} isLoggedIn />
|
||||
|
||||
{/* P0. Welcome/status row — below hero so featured image sits at 0px */}
|
||||
<Suspense fallback={null}>
|
||||
<HomeWelcomeRow user_data={user_data} />
|
||||
@@ -104,9 +110,9 @@ function AuthHomePage(props) {
|
||||
<HomeFromFollowing items={from_following} />
|
||||
</Suspense>
|
||||
|
||||
{/* P3. Trending For You (by_tags = Meilisearch tag overlap sorted by trending) */}
|
||||
{/* P3. Personalized For You preview */}
|
||||
<Suspense fallback={<SectionFallback />}>
|
||||
<HomeTrendingForYou items={by_tags} preferences={preferences} />
|
||||
<HomeTrendingForYou items={for_you} preferences={preferences} />
|
||||
</Suspense>
|
||||
|
||||
{/* Rising Now */}
|
||||
@@ -129,6 +135,17 @@ function AuthHomePage(props) {
|
||||
<HomeFresh items={fresh} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback={<SectionFallback />}>
|
||||
<HomeCollections
|
||||
featured={collections_featured}
|
||||
recent={collections_recent}
|
||||
trending={collections_trending}
|
||||
editorial={collections_editorial}
|
||||
community={collections_community}
|
||||
isLoggedIn
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* 4. Explore Categories */}
|
||||
<Suspense fallback={<SectionFallback />}>
|
||||
<HomeCategories />
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import React from 'react'
|
||||
import ArtworkGalleryGrid from '../../components/artwork/ArtworkGalleryGrid'
|
||||
|
||||
/**
|
||||
* Personalized trending: artworks matching user's top tags, sorted by trending score.
|
||||
* Label and browse link adapt to the user's first top tag.
|
||||
*/
|
||||
export default function HomeTrendingForYou({ items, preferences }) {
|
||||
if (!Array.isArray(items) || items.length === 0) return null
|
||||
|
||||
const topTag = preferences?.top_tags?.[0]
|
||||
const heading = topTag ? `🎯 Trending in #${topTag}` : '🎯 Trending For You'
|
||||
const link = topTag ? `/browse?tags=${encodeURIComponent(topTag)}&sort=trending` : '/discover/trending'
|
||||
const heading = 'Picked For You'
|
||||
const subheading = topTag
|
||||
? `Fresh recommendations informed by your recent interest in #${topTag}.`
|
||||
: 'A live preview of your personalized discovery feed.'
|
||||
const link = '/discover/for-you'
|
||||
|
||||
return (
|
||||
<section className="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-5 flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold text-white">{heading}</h2>
|
||||
<a href={link} className="text-sm text-nova-300 hover:text-white transition">
|
||||
See all →
|
||||
<div className="mb-5 flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<p className="text-[0.7rem] font-semibold uppercase tracking-[0.28em] text-sky-200/70">Personalized feed</p>
|
||||
<h2 className="mt-2 text-xl font-bold text-white">{heading}</h2>
|
||||
<p className="mt-1 max-w-2xl text-sm text-slate-300">{subheading}</p>
|
||||
</div>
|
||||
<a href={link} className="text-sm text-nova-300 transition hover:text-white">
|
||||
Open full feed →
|
||||
</a>
|
||||
</div>
|
||||
<ArtworkGalleryGrid items={items.slice(0, 8)} compact />
|
||||
|
||||
Reference in New Issue
Block a user