2026-04-05 15:16:14 +00:00

206 lines
8.0 KiB
TypeScript

import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import { useRouter } from 'next/router';
import axios from 'axios';
import { mdiMagnify, mdiTelevisionClassic } from '@mdi/js';
import BaseIcon from '../../../components/BaseIcon';
import LoadingSpinner from '../../../components/LoadingSpinner';
import { getPageTitle } from '../../../config';
import { humanizeMediaKind } from '../../../helpers/publicMedia';
import LayoutGuest from '../../../layouts/Guest';
export default function PublicShowsPage() {
const router = useRouter();
const [loading, setLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState('');
const [shows, setShows] = useState<any[]>([]);
const [categories, setCategories] = useState<any[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('');
useEffect(() => {
if (!router.isReady) return;
const categoryFromQuery = typeof router.query.categoryId === 'string' ? router.query.categoryId : '';
const queryFromUrl = typeof router.query.q === 'string' ? router.query.q : '';
setSelectedCategory(categoryFromQuery);
setSearchTerm(queryFromUrl);
}, [router.isReady, router.query.categoryId, router.query.q]);
useEffect(() => {
let isMounted = true;
const loadCategories = async () => {
try {
const response = await axios.get('/public-media/categories', { params: { limit: 24 } });
if (isMounted) {
setCategories(response.data?.rows || []);
}
} catch (error) {
console.error('Failed to load public categories:', error);
}
};
loadCategories();
return () => {
isMounted = false;
};
}, []);
useEffect(() => {
let isMounted = true;
const loadShows = async () => {
setLoading(true);
setErrorMessage('');
try {
const response = await axios.get('/public-media/shows', {
params: {
limit: 24,
q: searchTerm || undefined,
categoryId: selectedCategory || undefined,
},
});
if (isMounted) {
setShows(response.data?.rows || []);
}
} catch (error) {
console.error('Failed to load public shows:', error);
if (isMounted) {
setErrorMessage('We could not load shows right now.');
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
loadShows();
return () => {
isMounted = false;
};
}, [searchTerm, selectedCategory]);
const applyCategory = (categoryId: string) => {
setSelectedCategory(categoryId);
void router.replace(
{
pathname: '/watch/shows',
query: {
...(searchTerm ? { q: searchTerm } : {}),
...(categoryId ? { categoryId } : {}),
},
},
undefined,
{ shallow: true },
);
};
return (
<>
<Head>
<title>{getPageTitle('Shows')}</title>
<meta name='description' content='Browse all published Aliyo Momot shows by category and keyword.' />
</Head>
<main className='min-h-screen bg-[#050816] text-white'>
<section className='border-b border-white/5 bg-[linear-gradient(135deg,_#050816_0%,_#0f172a_55%,_#111827_100%)]'>
<div className='mx-auto max-w-7xl px-6 py-14 lg:px-10'>
<Link href='/watch' className='text-sm text-cyan-200 transition hover:text-white'>
Back to watch hub
</Link>
<div className='mt-6 flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between'>
<div>
<p className='text-sm uppercase tracking-[0.28em] text-cyan-200/70'>Shows library</p>
<h1 className='mt-3 text-4xl font-semibold text-white sm:text-5xl'>Public shows, organized for discovery.</h1>
<p className='mt-4 max-w-3xl text-base leading-7 text-slate-300'>
Filter the published program lineup by keyword or category, then open a show to view its public episode archive.
</p>
</div>
</div>
<div className='mt-8 rounded-[28px] border border-white/10 bg-white/5 p-5'>
<label className='flex items-center gap-3 rounded-[20px] border border-white/10 bg-slate-950/60 px-4 py-3'>
<BaseIcon path={mdiMagnify} size={20} className='text-cyan-200' />
<input
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
placeholder='Search show titles or summaries'
className='w-full bg-transparent text-sm text-white outline-none placeholder:text-slate-500'
/>
</label>
<div className='mt-4 flex flex-wrap gap-3'>
<button
type='button'
onClick={() => applyCategory('')}
className={`rounded-full px-4 py-2 text-sm transition ${selectedCategory ? 'border border-white/10 bg-white/5 text-slate-300 hover:text-white' : 'bg-white text-slate-950'}`}
>
All categories
</button>
{categories.map((category) => (
<button
key={category.id}
type='button'
onClick={() => applyCategory(category.id)}
className={`rounded-full px-4 py-2 text-sm transition ${selectedCategory === category.id ? 'bg-cyan-300 text-slate-950' : 'border border-white/10 bg-white/5 text-slate-300 hover:text-white'}`}
>
{category.name}
</button>
))}
</div>
</div>
</div>
</section>
<section>
<div className='mx-auto max-w-7xl px-6 py-14 lg:px-10'>
{loading ? (
<LoadingSpinner />
) : errorMessage ? (
<div className='rounded-[24px] border border-rose-500/20 bg-rose-500/10 p-6 text-sm text-rose-100'>{errorMessage}</div>
) : shows.length ? (
<div className='grid gap-5 md:grid-cols-2 xl:grid-cols-3'>
{shows.map((show) => (
<Link key={show.id} href={`/watch/shows/${show.id}`} className='group rounded-[28px] border border-white/10 bg-white/5 p-6 transition hover:-translate-y-1 hover:border-cyan-300/35 hover:bg-white/[0.08]'>
<div className='flex items-start justify-between gap-4'>
<div className='rounded-2xl border border-white/10 bg-white/5 p-3'>
<BaseIcon path={mdiTelevisionClassic} size={28} className='text-cyan-200' />
</div>
<div className='rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs uppercase tracking-[0.22em] text-slate-300'>
{humanizeMediaKind(show.show_type)}
</div>
</div>
<h2 className='mt-6 text-2xl font-semibold text-white'>{show.title}</h2>
<p className='mt-3 line-clamp-3 text-sm leading-6 text-slate-300'>{show.summary || 'Published show ready for viewers.'}</p>
<div className='mt-6 flex flex-wrap gap-2 text-xs uppercase tracking-[0.18em] text-slate-400'>
<span>{show.category?.name || 'Uncategorized'}</span>
<span></span>
<span>{show.release_year || 'Current'}</span>
</div>
</Link>
))}
</div>
) : (
<div className='rounded-[24px] border border-white/10 bg-white/5 p-8 text-center text-slate-300'>
No published shows matched your current filters.
</div>
)}
</div>
</section>
</main>
</>
);
}
PublicShowsPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};