From 625c22b833f06120c4b7e12d6814fcbae090d286 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Thu, 21 May 2026 20:03:23 +0000 Subject: [PATCH] feat(dashboard): Meet Kevin videos feed page --- dashboard/src/pages/meetKevin/Videos.tsx | 195 +++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 dashboard/src/pages/meetKevin/Videos.tsx diff --git a/dashboard/src/pages/meetKevin/Videos.tsx b/dashboard/src/pages/meetKevin/Videos.tsx new file mode 100644 index 0000000..e11a508 --- /dev/null +++ b/dashboard/src/pages/meetKevin/Videos.tsx @@ -0,0 +1,195 @@ +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import meetKevinApi from '../../api/meetKevin'; +import type { VideoStatus } from '../../types/meetKevin'; + +const STATUS_OPTIONS: { value: VideoStatus | ''; label: string }[] = [ + { value: '', label: 'All' }, + { value: 'analyzed', label: 'Analyzed' }, + { value: 'captioned', label: 'Captioned' }, + { value: 'discovered', label: 'Discovered' }, + { value: 'failed', label: 'Failed' }, + { value: 'skipped', label: 'Skipped' }, +]; + +function statusColor(status: VideoStatus): string { + if (status === 'analyzed') return 'text-green-400'; + if (status === 'failed') return 'text-red-400'; + return 'text-yellow-300'; +} + +export default function MeetKevinVideos() { + const [status, setStatus] = useState(''); + const [q, setQ] = useState(''); + const [page, setPage] = useState(1); + const per_page = 20; + + const { data, isLoading } = useQuery({ + queryKey: ['meet-kevin', 'videos', status, q, page], + queryFn: () => + meetKevinApi.listVideos({ + status: status || undefined, + q: q || undefined, + page, + per_page, + }), + }); + + const totalPages = data ? Math.ceil(data.total / per_page) : 0; + + return ( +
+
+

Meet Kevin — Videos

+ {data?.total ?? 0} videos +
+ + {/* Filters */} +
+
+
+ + +
+ +
+ + { + setQ(e.target.value); + setPage(1); + }} + placeholder="e.g. Fed, rate cut…" + className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-blue-500" + /> +
+ + {(status || q) && ( + + )} +
+
+ + {/* Cards */} + {isLoading ? ( +
+ +
+ ) : data && data.videos.length > 0 ? ( +
+ {data.videos.map((video) => ( + + {/* Thumbnail */} + {video.thumbnail_url ? ( + + ) : ( +
+ No image +
+ )} + + {/* Details */} +
+

+ {video.title} +

+ +
+ + {new Date(video.published_at).toLocaleDateString()} + + + {video.status} + +
+ + {video.failure_reason && ( +

+ {video.failure_reason} +

+ )} + + {video.top_tickers.length > 0 && ( +
+ {video.top_tickers.map((ticker) => ( + + {ticker} + + ))} +
+ )} +
+ + ))} +
+ ) : ( +
+ No videos found +
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ + + Page {page} of {totalPages} + + +
+ )} +
+ ); +}