auto-commit for b082c1c8-f6e3-4cfd-82ee-0396c70d1a00

This commit is contained in:
emergent-agent-e1
2026-07-23 22:18:08 +00:00
parent 34a052dabf
commit 22d8479b39
16 changed files with 1364 additions and 94 deletions
+80
View File
@@ -0,0 +1,80 @@
import { useEffect, useState, useMemo } from "react";
import { useParams, useNavigate } from "react-router-dom";
import api from "../lib/api";
import { Play, ArrowLeft } from "lucide-react";
export default function ShowDetail() {
const { id } = useParams();
const nav = useNavigate();
const [show, setShow] = useState(null);
const [episodes, setEpisodes] = useState([]);
const [season, setSeason] = useState(1);
useEffect(() => {
(async () => {
const [s, e] = await Promise.all([api.get(`/shows/${id}`), api.get(`/shows/${id}/episodes`)]);
setShow(s.data); setEpisodes(e.data);
const firstS = e.data[0]?.season_number || 1;
setSeason(firstS);
})();
}, [id]);
const seasons = useMemo(() => [...new Set(episodes.map((e) => e.season_number))].sort((a, b) => a - b), [episodes]);
const inSeason = useMemo(() => episodes.filter((e) => e.season_number === season), [episodes, season]);
if (!show) return <div className="min-h-screen bg-[#050505] flex items-center justify-center text-[#8A8A8A]">Loading</div>;
return (
<div className="min-h-screen bg-[#050505]" data-testid={`show-detail-${show.id}`}>
<div className="relative h-[60vh] min-h-[400px] overflow-hidden">
<img src={show.backdrop_url || show.poster_url} alt="" className="absolute inset-0 w-full h-full object-cover" />
<div className="absolute inset-0 hero-fade" />
<button onClick={() => nav(-1)} className="absolute top-24 left-6 md:left-12 flex items-center gap-2 text-white/80 hover:text-white bg-black/40 hover:bg-black/60 px-4 py-2 backdrop-blur transition-colors" data-testid="show-back">
<ArrowLeft size={16} strokeWidth={1.5} /> <span className="text-xs uppercase tracking-[0.2em]">Back</span>
</button>
<div className="absolute bottom-12 left-6 md:left-12 max-w-2xl">
<span className="text-xs uppercase tracking-[0.3em] text-[#D9381E]">Series</span>
<h1 className="font-display text-5xl md:text-6xl font-black tracking-tighter text-white mt-2" data-testid="show-title">{show.title}</h1>
<div className="flex items-center gap-3 mt-3 text-xs uppercase tracking-[0.2em] text-[#8A8A8A]">
<span>{show.year}</span><span>·</span><span>{show.rating}</span><span>·</span>
<span>{show.season_count} seasons · {show.episode_count} episodes</span>
</div>
<p className="text-[#C8C8C8] mt-5 max-w-xl leading-relaxed">{show.description}</p>
</div>
</div>
<div className="px-6 md:px-12 max-w-[1500px] mx-auto py-12">
<div className="flex items-center gap-3 flex-wrap mb-6">
<span className="text-[10px] uppercase tracking-[0.3em] text-[#8A8A8A]">Seasons</span>
{seasons.map((n) => (
<button key={n} onClick={() => setSeason(n)}
className={`text-sm px-4 py-2 border transition-colors ${season === n ? "border-[#D9381E] text-white bg-[#D9381E]/10" : "border-[#222] text-[#8A8A8A] hover:text-white"}`}
data-testid={`season-${n}`}>
Season {n}
</button>
))}
</div>
<div className="space-y-3">
{inSeason.map((ep) => (
<button key={ep.id} onClick={() => nav(`/watch/episode/${ep.id}`)}
className="w-full flex items-center gap-4 p-4 border border-[#222] hover:border-[#D9381E] hover:bg-[#0F0F0F] transition-colors text-left group"
data-testid={`episode-${ep.id}`}>
<div className="w-12 h-12 flex items-center justify-center bg-[#0F0F0F] group-hover:bg-[#D9381E] transition-colors shrink-0">
<Play size={16} strokeWidth={1.5} fill="currentColor" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-baseline gap-3">
<span className="text-[10px] uppercase tracking-[0.3em] text-[#8A8A8A]">E{ep.episode_number}</span>
<h3 className="font-display text-lg text-white truncate">{ep.title || `Episode ${ep.episode_number}`}</h3>
{ep.duration_minutes > 0 && <span className="text-xs text-[#8A8A8A] ml-auto">{ep.duration_minutes}m</span>}
</div>
{ep.description && <p className="text-sm text-[#8A8A8A] mt-1 line-clamp-2">{ep.description}</p>}
</div>
</button>
))}
</div>
</div>
</div>
);
}