feat: feedback round 3 — content-type toggles, channel filter, card polish

- Content type is now three independent toggles (Normal / Shorts / Live·Upcoming);
  the feed is the union of enabled types (backend show_normal + include_shorts +
  include_live)
- Per-channel filter: a button on each card scopes the feed to that channel, shown
  as a removable chip in the sidebar
- Hide now refetches the feed once the change is persisted, so a hidden video shows
  up in the Hidden view immediately (no refresh needed); optimistic updates respect
  the current view
- Grid cards are now distinct panels (card background, border, shadow) that lift on
  hover; clickable channel name in list view too
This commit is contained in:
npeter83 2026-06-11 03:47:51 +02:00
parent e07a37622d
commit ecbecbb9f4
6 changed files with 115 additions and 30 deletions

View file

@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { X } from "lucide-react";
import { api, type FeedFilters, type Tag } from "../lib/api";
const SORTS = [
@ -64,13 +65,27 @@ export default function Sidebar({
const active =
filters.tags.length > 0 ||
!filters.includeNormal ||
filters.includeShorts ||
filters.includeLive ||
filters.show !== "unwatched" ||
filters.sort !== "newest";
filters.sort !== "newest" ||
!!filters.channelId;
return (
<aside className="w-64 shrink-0 border-r border-border bg-surface/40 overflow-y-auto p-4 space-y-5 hidden md:block">
{filters.channelId && (
<Section title="Channel">
<button
onClick={() => setFilters({ ...filters, channelId: undefined, channelName: undefined })}
className="w-full flex items-center justify-between gap-2 text-sm px-3 py-2 rounded-lg bg-accent text-accent-fg shadow-sm hover:opacity-90 transition"
>
<span className="truncate">{filters.channelName ?? "This channel"}</span>
<X className="w-4 h-4 shrink-0" />
</button>
</Section>
)}
<Section title="Show">
<div className="grid grid-cols-2 gap-1.5">
{SHOWS.map((s) => (
@ -103,14 +118,19 @@ export default function Sidebar({
</select>
</Section>
<Section title="Content">
<Section title="Content type">
<Toggle
label="Include Shorts"
label="Normal"
checked={filters.includeNormal}
onChange={(v) => setFilters({ ...filters, includeNormal: v })}
/>
<Toggle
label="Shorts"
checked={filters.includeShorts}
onChange={(v) => setFilters({ ...filters, includeShorts: v })}
/>
<Toggle
label="Include live / upcoming"
label="Live / Upcoming"
checked={filters.includeLive}
onChange={(v) => setFilters({ ...filters, includeLive: v })}
/>
@ -167,6 +187,7 @@ export default function Sidebar({
tagMode: "or",
q: filters.q,
sort: "newest",
includeNormal: true,
includeShorts: false,
includeLive: false,
show: "unwatched",