myYouTube/frontend/src/pages/ChannelVideos.tsx
vrubelroman 0ed20bb838 Implement Phases 1-5: skeleton, OAuth, categories, video sync/feed, playback
- FastAPI + PostgreSQL + Alembic + React/Vite skeleton, Docker Compose, healthcheck
- Google OAuth (single allowed account), encrypted refresh token storage
- Subscriptions sync with pagination, uploads playlist batch fetch
- Categories CRUD, many-to-many channel assignment, category filtering
- Video sync (playlistItems + videos.list batching), cached feed with cursor
  pagination, background scheduler (APScheduler)
- Video detail page with YouTube embed player
- SPA fallback routing, optimistic UI updates, client-side query caching

40 backend tests covering OAuth allow-list, sync idempotency, cascade deletes,
cursor pagination, and category filtering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 18:44:30 +00:00

56 lines
1.9 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 VideoCard from '../components/VideoCard'
function ChannelVideos() {
const { channelId } = useParams<{ channelId: string }>()
const id = Number(channelId)
const channelQuery = useQuery({ queryKey: ['channel', id], queryFn: () => getChannel(id) })
const feedQuery = useInfiniteQuery({
queryKey: ['feed', 'channel', id],
queryFn: ({ pageParam }) => getFeed({ channelId: id, cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined,
})
const items = feedQuery.data?.pages.flatMap((page) => page.items) ?? []
return (
<div className="feed-screen">
<header>
<div>
<Link to="/channels" className="back-link">
← Каналы
</Link>
<h1>{channelQuery.data?.title ?? 'Канал'}</h1>
</div>
</header>
{feedQuery.isLoading && <p>Загрузка...</p>}
{feedQuery.isError && <p className="error">Не удалось загрузить видео</p>}
<ul className="video-grid">
{items.map((video) => (
<VideoCard key={video.youtube_video_id} video={video} />
))}
</ul>
{!feedQuery.isLoading && items.length === 0 && <p>У этого канала пока нет синхронизированных видео.</p>}
{feedQuery.hasNextPage && (
<button
className="load-more"
onClick={() => feedQuery.fetchNextPage()}
disabled={feedQuery.isFetchingNextPage}
>
{feedQuery.isFetchingNextPage ? 'Загрузка...' : 'Показать ещё'}
</button>
)}
</div>
)
}
export default ChannelVideos