myYouTube/frontend/src/pages/ChannelVideos.tsx
vrubelroman e10df8dcbd Channel features: subscribers, new-videos badges, activity-driven sync
- Store subscriber count from YouTube statistics and show it on channel page
- Sync 50 videos per channel with playlistItems pagination support
- Show per-channel and per-category new-videos counters (2-day window)
- Replace hourly videos sync with activity trigger (2h idle) and
  incremental backfill (hard cap 200 per channel)
- Clicking the sidebar new-videos count filters the category feed to
  recent videos only (new_only)
- Update agent-team docs: deploy after green checks
2026-09-17 21:07:33 +00:00

26 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import { Link, useParams } from 'react-router-dom'
import { getChannel, getFeed } from '../api/client'
import Icon from '../components/Icon'
import VideoCard from '../components/VideoCard'
import { formatSubscriberCount } from '../utils/format'
function ChannelVideos() {
const { channelId } = useParams<{ channelId: string }>()
const id = Number(channelId)
const valid = Number.isInteger(id) && id > 0
const channelQuery = useQuery({ queryKey: ['channel', id], queryFn: () => getChannel(id), enabled: valid })
const feedQuery = useInfiniteQuery({ queryKey: ['feed', 'channel', id], queryFn: ({ pageParam }) => getFeed({ channelId: id, limit: 20, cursor: pageParam }), initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined, enabled: valid })
const items = feedQuery.data?.pages.flatMap((page) => page.items) ?? []
const subscriberLabel = channelQuery.data ? formatSubscriberCount(channelQuery.data.subscriber_count) : null
return <section className="page">
<Link to="/channels" className="back-link"><Icon name="arrow" size={17} /> Каналы</Link>
<div className="page-heading"><div><p className="eyebrow">Видео канала</p><h1>{channelQuery.data?.title ?? 'Канал'}</h1><p className="page-subtitle">{subscriberLabel ?? 'Последние ролики из твоих подписок.'}</p></div></div>
{(channelQuery.isError || feedQuery.isError || !valid) && <div className="empty-state" role="alert"><h2>Не удалось открыть канал</h2><Link to="/channels" className="button-primary">К списку каналов</Link></div>}
{feedQuery.isLoading && <div className="video-grid">{Array.from({ length: 6 }, (_, index) => <div className="video-card" key={index}><div className="skeleton-thumbnail" /><div className="video-info"><div className="skeleton-line wide" /><div className="skeleton-line" /></div></div>)}</div>}
{!feedQuery.isLoading && !feedQuery.isError && items.length > 0 && <ul className="video-grid">{items.map((video) => <VideoCard key={video.youtube_video_id} video={video} />)}</ul>}
{!feedQuery.isLoading && !feedQuery.isError && items.length === 0 && <div className="empty-state"><h2>Пока нет видео</h2><p>Обнови ленту, чтобы получить новые ролики канала.</p></div>}
{feedQuery.hasNextPage && <button className="button-secondary load-more" onClick={() => feedQuery.fetchNextPage()} disabled={feedQuery.isFetchingNextPage}>{feedQuery.isFetchingNextPage ? 'Загрузка…' : 'Показать ещё'}</button>}
</section>
}
export default ChannelVideos