mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
- Continue Watching cards now show episode still images in 16:9 aspect with overlaid episode info, play button, and progress bar - Stats section uses per-stat color coding (primary/watching/watchlist/completed) with icon background pills and tabular-nums alignment - No-poster fallback replaced with textured gradient + title text overlay - Updated ContinueWatchingItem interface to include backdropPath, stillPath, overview Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
2.6 KiB
TypeScript
104 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
IconCheck,
|
|
IconLibrary,
|
|
IconMovie,
|
|
IconPlayerPlay,
|
|
} from "@tabler/icons-react";
|
|
import { motion } from "motion/react";
|
|
import { useEffect, useState } from "react";
|
|
|
|
interface Stats {
|
|
moviesThisMonth: number;
|
|
episodesThisWeek: number;
|
|
librarySize: number;
|
|
completed: number;
|
|
}
|
|
|
|
const statDefs = [
|
|
{
|
|
key: "moviesThisMonth" as const,
|
|
label: "Movies This Month",
|
|
icon: IconMovie,
|
|
color: "text-primary",
|
|
bgColor: "bg-primary/10",
|
|
},
|
|
{
|
|
key: "episodesThisWeek" as const,
|
|
label: "Episodes This Week",
|
|
icon: IconPlayerPlay,
|
|
color: "text-status-watching",
|
|
bgColor: "bg-status-watching/10",
|
|
},
|
|
{
|
|
key: "librarySize" as const,
|
|
label: "In Library",
|
|
icon: IconLibrary,
|
|
color: "text-status-watchlist",
|
|
bgColor: "bg-status-watchlist/10",
|
|
},
|
|
{
|
|
key: "completed" as const,
|
|
label: "Completed",
|
|
icon: IconCheck,
|
|
color: "text-status-completed",
|
|
bgColor: "bg-status-completed/10",
|
|
},
|
|
];
|
|
|
|
export function StatsSummary() {
|
|
const [stats, setStats] = useState<Stats | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetch("/api/feed/stats")
|
|
.then((r) => r.json())
|
|
.then((data) => setStats(data))
|
|
.catch(() => {});
|
|
}, []);
|
|
|
|
if (!stats) return null;
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
|
{statDefs.map((def, i) => {
|
|
const Icon = def.icon;
|
|
const value = stats[def.key];
|
|
return (
|
|
<motion.div
|
|
key={def.key}
|
|
initial={{ opacity: 0, y: 12 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 300,
|
|
damping: 24,
|
|
delay: i * 0.08,
|
|
}}
|
|
className="rounded-xl border border-border/30 bg-card/50 p-4"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className={`flex h-6 w-6 items-center justify-center rounded-md ${def.bgColor}`}
|
|
>
|
|
<Icon size={13} className={def.color} />
|
|
</div>
|
|
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
{def.label}
|
|
</span>
|
|
</div>
|
|
<motion.p
|
|
className={`mt-2 font-display text-2xl tabular-nums tracking-tight ${def.color}`}
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ delay: i * 0.08 + 0.2 }}
|
|
>
|
|
{value}
|
|
</motion.p>
|
|
</motion.div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|