2026-09-16 21:40:32 +00:00
|
|
|
|
import { Link, useLocation } from 'react-router-dom'
|
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 type { FeedVideoDto } from '../api/client'
|
|
|
|
|
|
import { formatDuration, formatRelativeTime } from '../utils/format'
|
2026-09-16 18:56:17 +00:00
|
|
|
|
import DownloadButton from './DownloadButton'
|
2026-09-16 21:40:32 +00:00
|
|
|
|
import Icon from './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
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
video: FeedVideoDto
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function VideoCard({ video }: Props) {
|
|
|
|
|
|
const duration = formatDuration(video.duration_seconds)
|
|
|
|
|
|
const href = `/video/${video.youtube_video_id}`
|
2026-09-16 21:40:32 +00:00
|
|
|
|
const location = useLocation()
|
|
|
|
|
|
const from = location.pathname + location.search
|
|
|
|
|
|
const rememberScroll = () => sessionStorage.setItem(`feed-scroll:${from}`, String(window.scrollY))
|
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
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<li className="video-card">
|
2026-09-16 21:40:32 +00:00
|
|
|
|
<Link to={href} state={{ from }} onClick={rememberScroll} className="video-thumbnail" aria-label={`Смотреть: ${video.title}`}>
|
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
|
|
|
|
{video.thumbnail_url ? (
|
2026-09-16 21:40:32 +00:00
|
|
|
|
<img src={video.thumbnail_url} alt="" loading="lazy" />
|
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
|
|
|
|
) : (
|
|
|
|
|
|
<div className="video-thumbnail-placeholder" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
{duration && <span className="duration-badge">{duration}</span>}
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="video-info">
|
2026-09-16 21:40:32 +00:00
|
|
|
|
<Link to={href} state={{ from }} onClick={rememberScroll} className="video-title" title={video.title}>
|
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
|
|
|
|
{video.title}
|
|
|
|
|
|
</Link>
|
2026-09-16 21:40:32 +00:00
|
|
|
|
<div className="video-meta"><span className="video-channel">{video.channel.title}</span><span>{formatRelativeTime(video.published_at)}</span></div>
|
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
|
|
|
|
<div className="video-actions">
|
2026-09-16 21:40:32 +00:00
|
|
|
|
<Link to={href} state={{ from }} onClick={rememberScroll} className="button-link"><Icon name="play" size={16} /> Смотреть</Link>
|
2026-09-16 18:56:17 +00:00
|
|
|
|
<DownloadButton video={video} />
|
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
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default VideoCard
|