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
import { useInfiniteQuery , useQuery } from '@tanstack/react-query'
import { Link , useParams } from 'react-router-dom'
import { getChannel , getFeed } from '../api/client'
2026-09-16 21:40:32 +00:00
import Icon from '../components/Icon'
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
import VideoCard from '../components/VideoCard'
2026-09-17 21:07:33 +00:00
import { formatSubscriberCount } from '../utils/format'
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
function ChannelVideos() {
const { channelId } = useParams < { channelId : string } > ( )
const id = Number ( channelId )
2026-09-16 21:40:32 +00:00
const valid = Number . isInteger ( id ) && id > 0
const channelQuery = useQuery ( { queryKey : [ 'channel' , id ] , queryFn : ( ) = > getChannel ( id ) , enabled : valid } )
2026-09-17 21:07:33 +00:00
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 } )
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
const items = feedQuery . data ? . pages . flatMap ( ( page ) = > page . items ) ? ? [ ]
2026-09-17 21:07:33 +00:00
const subscriberLabel = channelQuery . data ? formatSubscriberCount ( channelQuery . data . subscriber_count ) : null
2026-09-17 17:22:22 +00:00
return < section className = "page" >
2026-09-16 21:40:32 +00:00
< Link to = "/channels" className = "back-link" > < Icon name = "arrow" size = { 17 } / > К а н а л ы < / Link >
2026-09-17 21:07:33 +00:00
< div className = "page-heading" > < div > < p className = "eyebrow" > В и д е о к а н а л а < / p > < h1 > { channelQuery . data ? . title ? ? 'Канал' } < / h1 > < p className = "page-subtitle" > { subscriberLabel ? ? 'Последние ролики из твоих подписок.' } < / p > < / div > < / div >
2026-09-16 21:40:32 +00:00
{ ( channelQuery . isError || feedQuery . isError || ! valid ) && < div className = "empty-state" role = "alert" > < h2 > Н е у д а л о с ь о т к р ы т ь к а н а л < / h2 > < Link to = "/channels" className = "button-primary" > К с п и с к у к а н а л о в < / Link > < / div > }
2026-09-17 17:22:22 +00:00
{ 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 > }
2026-09-16 21:40:32 +00:00
{ ! 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 >
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
}
export default ChannelVideos