100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
import { useState } from 'react'
|
|
import { Filter } from '../../../lib/icons'
|
|
import { useHasTmdbKey } from '../../../hooks/use-external'
|
|
import { CANON_LISTS } from '../../../lib/canon-lists'
|
|
import { STUDIOS, NETWORKS } from '../../../lib/studios-and-networks'
|
|
import BrandRow from '../../../components/ui/BrandRow'
|
|
import CanonListRow from '../../../components/ui/CanonListRow'
|
|
import LetterboxdListRow from '../../../components/ui/LetterboxdListRow'
|
|
import LetterboxdAddModal from '../../../components/ui/LetterboxdAddModal'
|
|
import { useLetterboxdLists } from '../../../stores/letterboxd-lists-store'
|
|
|
|
/**
|
|
* "From the studios" - one row per major film studio.
|
|
*/
|
|
export function StudioRows() {
|
|
const hasTmdb = useHasTmdbKey()
|
|
if (!hasTmdb) return null
|
|
return (
|
|
<section className="mb-4 mt-4">
|
|
<BrandSectionHeader eyebrow="Studios" title="From the studios" subtitle="Films grouped by the company that made them" />
|
|
{STUDIOS.map(s => (
|
|
<BrandRow key={s.id} brandId={s.id} label={s.label} subtitle={s.blurb} kind="movie" />
|
|
))}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* "On the networks" - one row per major TV network.
|
|
*/
|
|
export function NetworkRows() {
|
|
const hasTmdb = useHasTmdbKey()
|
|
if (!hasTmdb) return null
|
|
return (
|
|
<section className="mb-4 mt-4">
|
|
<BrandSectionHeader eyebrow="Networks" title="On the networks" subtitle="Shows grouped by where they air" />
|
|
{NETWORKS.map(n => (
|
|
<BrandRow key={n.id} brandId={n.id} label={n.label} subtitle={n.blurb} kind="tv" />
|
|
))}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
function BrandSectionHeader({ eyebrow, title, subtitle }: { eyebrow: string; title: string; subtitle: string }) {
|
|
return (
|
|
<div className="px-7 mb-5 pt-2">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="w-1 h-3.5 rounded-full bg-accent" />
|
|
<span className="text-[10.5px] font-semibold text-text-3 uppercase tracking-[0.18em]">{eyebrow}</span>
|
|
</div>
|
|
<h2 className="font-display text-[20px] font-bold tracking-tight text-text-1 leading-tight">{title}</h2>
|
|
<p className="text-[12px] text-text-3 mt-0.5">{subtitle}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Discover canon - renders bundled canon lists (AFI, Sight & Sound, IMDb top).
|
|
*/
|
|
export function DiscoverCanonSection() {
|
|
const hasTmdb = useHasTmdbKey()
|
|
if (!hasTmdb) return null
|
|
return (
|
|
<section className="mb-4 mt-4">
|
|
<BrandSectionHeader eyebrow="Canon" title="Discover canon" subtitle="Bundled lists from AFI, Sight & Sound, and IMDb" />
|
|
{CANON_LISTS.map(list => (
|
|
<CanonListRow key={list.id} list={list} />
|
|
))}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Letterboxd lists. Each saved list URL renders as its own row lazy-mounted
|
|
* on scroll. The trailing affordance opens a modal to paste a new URL.
|
|
*/
|
|
export function LetterboxdListsSection() {
|
|
const hasTmdb = useHasTmdbKey()
|
|
const lists = useLetterboxdLists(s => s.lists)
|
|
const [addOpen, setAddOpen] = useState(false)
|
|
if (!hasTmdb) return null
|
|
return (
|
|
<>
|
|
{lists.map(saved => (
|
|
<LetterboxdListRow key={saved.url} saved={saved} />
|
|
))}
|
|
<div className="px-7 mb-10">
|
|
<button
|
|
onClick={() => setAddOpen(true)}
|
|
className="inline-flex items-center gap-2 h-9 px-3.5 rounded-full bg-elevated/50 ring-1 ring-border hover:ring-accent/40 hover:text-accent text-[12.5px] text-text-2 tracking-tight transition focus-ring"
|
|
>
|
|
<Filter size={13} />
|
|
{lists.length === 0 ? 'Add a Letterboxd list' : 'Add another Letterboxd list'}
|
|
</button>
|
|
</div>
|
|
<LetterboxdAddModal open={addOpen} onClose={() => setAddOpen(false)} />
|
|
</>
|
|
)
|
|
}
|