feat(homepage): Nova homepage layout — guest/auth split, mascot category tiles, 5-col artwork grids

- HomeController: is_logged_in now lives inside props JSON (not separate view var)
- HomepageService: allForUser() adds user_data, fresh, suggested_creators; auth
  payload includes is_logged_in:true; guest payload merged with is_logged_in:false
- getTrending/getFreshUploads/getFollowingFeed: limit 12→10 (2 clean rows of 5)
- New getUserData() — unread messages + notifications counts via DB
- New getSuggestedCreators() — top followed-count creators not yet followed, cached 5 min

React — new components:
- HomeWelcomeRow: greeting bar with avatar, unread message/notification badges, upload CTA
- HomeFromFollowing: art grid from followed creators with empty-state
- HomeTrendingForYou: personalized grid adapts heading/link to user's top tag
- HomeBecauseYouLike: secondary personalized section keyed on top tag
- HomeSuggestedCreators: 4-col creator cards with avatar, stats, View Profile link
- HomeCTA: gradient upload banner; guest sees Create Account second CTA
- HomeCategories: 5-tile static category grid with mascot images

React — updated:
- HomePage: split into GuestHomePage + AuthHomePage; routes on is_logged_in prop
- HomeHero: isLoggedIn prop; upload href gates on auth; CTA → /discover/trending
- HomeTrending: see-all → /discover/trending; grid 4→5 cols; slice to multiple of 5
- HomeFresh: see-all → /discover/fresh; grid 4→5 cols; slice to multiple of 5
- HomeFromFollowing/TrendingForYou/BecauseYouLike: 5-col grid, slice to multiple of 5
- HomeCategories: mascots per category (wallpapers/photography/skins/other), smaller tiles
This commit is contained in:
2026-02-27 10:48:35 +01:00
parent f0cca76eb3
commit 4f9b43bbba
13 changed files with 771 additions and 47 deletions

View File

@@ -1,14 +1,22 @@
import React, { lazy, Suspense } from 'react'
import { createRoot } from 'react-dom/client'
// Sub-section components — lazy-loaded so only the hero blocks the initial bundle
// Above-fold — eager
import HomeHero from './HomeHero'
const HomeTrending = lazy(() => import('./HomeTrending'))
const HomeFresh = lazy(() => import('./HomeFresh'))
const HomeTags = lazy(() => import('./HomeTags'))
const HomeCreators = lazy(() => import('./HomeCreators'))
const HomeNews = lazy(() => import('./HomeNews'))
// Below-fold — lazy-loaded to keep initial bundle small
const HomeWelcomeRow = lazy(() => import('./HomeWelcomeRow'))
const HomeFromFollowing = lazy(() => import('./HomeFromFollowing'))
const HomeTrendingForYou = lazy(() => import('./HomeTrendingForYou'))
const HomeBecauseYouLike = lazy(() => import('./HomeBecauseYouLike'))
const HomeSuggestedCreators = lazy(() => import('./HomeSuggestedCreators'))
const HomeTrending = lazy(() => import('./HomeTrending'))
const HomeFresh = lazy(() => import('./HomeFresh'))
const HomeCategories = lazy(() => import('./HomeCategories'))
const HomeTags = lazy(() => import('./HomeTags'))
const HomeCreators = lazy(() => import('./HomeCreators'))
const HomeNews = lazy(() => import('./HomeNews'))
const HomeCTA = lazy(() => import('./HomeCTA'))
function SectionFallback() {
return (
@@ -16,32 +24,141 @@ function SectionFallback() {
)
}
function HomePage({ hero, trending, fresh, tags, creators, news }) {
return (
<div className="pb-24">
{/* Hero — above-fold, eager */}
<HomeHero artwork={hero} />
function GuestHomePage(props) {
const { hero, trending, fresh, tags, creators, news } = props
{/* Below-fold sections — lazy */}
return (
<>
{/* 1. Hero */}
<HomeHero artwork={hero} isLoggedIn={false} />
<Suspense fallback={<SectionFallback />}>
<HomeTrending items={trending} />
</Suspense>
{/* 3. Fresh Uploads */}
<Suspense fallback={<SectionFallback />}>
<HomeFresh items={fresh} />
</Suspense>
{/* 4. Explore Categories */}
<Suspense fallback={<SectionFallback />}>
<HomeCategories />
</Suspense>
{/* 5. Popular Tags */}
<Suspense fallback={<SectionFallback />}>
<HomeTags tags={tags} />
</Suspense>
{/* 6. Top Creators */}
<Suspense fallback={<SectionFallback />}>
<HomeCreators creators={creators} />
</Suspense>
{/* 7. News */}
<Suspense fallback={<SectionFallback />}>
<HomeNews items={news} />
</Suspense>
{/* 8. CTA Upload */}
<Suspense fallback={<SectionFallback />}>
<HomeCTA isLoggedIn={false} />
</Suspense>
</>
)
}
function AuthHomePage(props) {
const {
user_data,
hero,
from_following,
trending,
fresh,
by_tags,
by_categories,
suggested_creators,
tags,
creators,
news,
preferences,
} = props
return (
<>
{/* P0. Welcome/status row */}
<Suspense fallback={null}>
<HomeWelcomeRow user_data={user_data} />
</Suspense>
{/* 1. Hero */}
<HomeHero artwork={hero} isLoggedIn />
{/* P2. From Creators You Follow */}
<Suspense fallback={<SectionFallback />}>
<HomeFromFollowing items={from_following} />
</Suspense>
{/* P3. Trending For You (by_tags = Meilisearch tag overlap sorted by trending) */}
<Suspense fallback={<SectionFallback />}>
<HomeTrendingForYou items={by_tags} preferences={preferences} />
</Suspense>
{/* 2. Global Trending Now */}
<Suspense fallback={<SectionFallback />}>
<HomeTrending items={trending} />
</Suspense>
{/* P4. Because You Like {top tag} — uses by_categories for variety */}
<Suspense fallback={<SectionFallback />}>
<HomeBecauseYouLike items={by_categories} preferences={preferences} />
</Suspense>
{/* 3. Fresh Uploads */}
<Suspense fallback={<SectionFallback />}>
<HomeFresh items={fresh} />
</Suspense>
{/* 4. Explore Categories */}
<Suspense fallback={<SectionFallback />}>
<HomeCategories />
</Suspense>
{/* P5. Suggested Creators */}
<Suspense fallback={<SectionFallback />}>
<HomeSuggestedCreators creators={suggested_creators} />
</Suspense>
{/* 5. Popular Tags */}
<Suspense fallback={<SectionFallback />}>
<HomeTags tags={tags} />
</Suspense>
{/* 6. Top Creators */}
<Suspense fallback={<SectionFallback />}>
<HomeCreators creators={creators} />
</Suspense>
{/* 7. News */}
<Suspense fallback={<SectionFallback />}>
<HomeNews items={news} />
</Suspense>
{/* 8. CTA Upload */}
<Suspense fallback={<SectionFallback />}>
<HomeCTA isLoggedIn />
</Suspense>
</>
)
}
function HomePage(props) {
return (
<div className="pb-24">
{props.is_logged_in
? <AuthHomePage {...props} />
: <GuestHomePage {...props} />
}
</div>
)
}