Files
jellybloom/src/pages/DiscoverPage.tsx
T

193 lines
6.2 KiB
TypeScript

import { useState } from 'react'
import { useHasTmdbKey } from '../hooks/use-external'
import LazyMount from '../components/ui/LazyMount'
import { usePreferencesStore } from '../stores/preferences-store'
import { regionForUser } from '../lib/format'
import DiscoverFilters, {
DEFAULT_FILTERS,
hasAnyActiveFilters,
type DiscoverFilterState,
} from '../components/discover/DiscoverFilters'
import TonightHero from '../components/discover/TonightHero'
import { MoodChips, MoodRow } from '../components/discover/MoodChips'
import Roulette from '../components/discover/Roulette'
import DecadeStrip from '../components/discover/DecadeStrip'
import CanonicalLists from '../components/discover/CanonicalLists'
import OnThisDay from '../components/discover/OnThisDay'
import LibraryGapFinder from '../components/discover/LibraryGapFinder'
import {
BrowseSection,
genreTiles,
languageTiles,
studioTiles,
networkTiles,
type BrowseKey,
} from '../components/discover/BrowseGrid'
import { NoKey, Hero } from './discover/chrome'
import { FilteredGrid } from './discover/filtered-grid'
import {
TrendingDayRow,
TrendingWeekRow,
PopularMoviesRow,
PopularTvRow,
UpcomingMoviesRow,
TopRatedMoviesRow,
TopRatedTvRow,
CultClassicsRow,
} from './discover/rows'
/**
* Discover page - intent-driven entry to TMDB content the user doesn't
* already have.
*
* Layout:
* 1. Compact page header
* 2. Sticky filter bar (movie/tv + advanced filters)
* 3. Editorial spotlight - one big "pick of the day"
* 4. Mood chips - 10 vibes that resolve to a single content row
* 5. 3-4 curated rows (Trending / Acclaimed / Coming soon / etc.)
* 6. Browse-by sections: genre, language, studio, network - compact
* tile grids with inline expansion when a tile is clicked
*
* Active filters take over the page with the existing FilteredGrid - that
* mode is unchanged so the filter workflow stays familiar.
*/
export default function DiscoverPage() {
const hasTmdb = useHasTmdbKey()
const prefs = usePreferencesStore()
const region = prefs.region || regionForUser()
const [filters, setFilters] = useState<DiscoverFilterState>(() => ({
...DEFAULT_FILTERS,
watchRegion: region,
}))
const [moodId, setMoodId] = useState<string | null>(null)
const [decade, setDecade] = useState<string | null>(null)
const [expanded, setExpanded] = useState<BrowseKey | null>(null)
if (!hasTmdb) return <NoKey />
const filterMode = hasAnyActiveFilters(filters)
const kind = filters.kind
// Reset the mood / expansion when toggling movie<->tv since both are
// kind-scoped and the previously-active mood may not exist on the
// other side.
function onFiltersChange(next: DiscoverFilterState) {
if (next.kind !== filters.kind) {
setMoodId(null)
setDecade(null)
setExpanded(null)
}
setFilters(next)
}
return (
<div className="pb-12 overflow-x-hidden" style={{ isolation: 'isolate' }}>
<Hero />
<div
className="sticky top-0 py-3 -mt-1 mb-3 border-b border-border/60"
style={{ zIndex: 25, backgroundColor: 'var(--color-void)' }}
>
<DiscoverFilters filters={filters} onChange={onFiltersChange} region={region} />
</div>
{filterMode ? (
<FilteredGrid filters={filters} />
) : (
<>
<LazyMount><TonightHero kind={kind} /></LazyMount>
<div className="px-7 mb-2 flex items-center justify-end">
<Roulette kind={kind} moodId={moodId} />
</div>
<MoodChips activeId={moodId} onChange={setMoodId} kind={kind} />
{moodId && (
<div className="mb-4">
<MoodRow moodId={moodId} kind={kind} />
</div>
)}
<DecadeStrip kind={kind} active={decade} onChange={setDecade} />
<div className="relative z-10">
{kind === 'movie' ? (
<>
<LazyMount><TrendingDayRow /></LazyMount>
<LazyMount><PopularMoviesRow /></LazyMount>
<LazyMount><UpcomingMoviesRow region={region} /></LazyMount>
<LazyMount><TopRatedMoviesRow /></LazyMount>
<LazyMount><CultClassicsRow /></LazyMount>
</>
) : (
<>
<LazyMount><TrendingWeekRow /></LazyMount>
<LazyMount><PopularTvRow /></LazyMount>
<LazyMount><TopRatedTvRow /></LazyMount>
</>
)}
</div>
{kind === 'movie' && <LazyMount><OnThisDay /></LazyMount>}
{kind === 'movie' && <LazyMount><LibraryGapFinder /></LazyMount>}
{kind === 'movie' && <LazyMount><CanonicalLists /></LazyMount>}
<div className="mt-4 mb-2 px-7">
<div className="flex items-center gap-3 text-text-4">
<span className="flex-1 h-px bg-border" />
<span className="text-[10.5px] uppercase tracking-[0.2em] font-semibold">Browse by</span>
<span className="flex-1 h-px bg-border" />
</div>
</div>
<BrowseSection
eyebrow="By genre"
title="Genres"
subtitle="Top of each genre, missing from your shelves"
tiles={genreTiles()}
expanded={expanded}
onSelect={setExpanded}
/>
{kind === 'movie' && (
<BrowseSection
eyebrow="By language"
title="Around the world"
subtitle="Top-rated cinema by original language"
tiles={languageTiles()}
expanded={expanded}
onSelect={setExpanded}
/>
)}
{kind === 'movie' && (
<BrowseSection
eyebrow="By studio"
title="From the studios"
subtitle="Films grouped by who made them"
tiles={studioTiles()}
expanded={expanded}
onSelect={setExpanded}
/>
)}
{kind === 'tv' && (
<BrowseSection
eyebrow="By network"
title="On the networks"
subtitle="Shows grouped by where they air"
tiles={networkTiles()}
expanded={expanded}
onSelect={setExpanded}
/>
)}
</>
)}
</div>
)
}