'use client'; import Link from 'next/link'; import { useState, useEffect, useCallback } from 'react'; import { getChannelVideosClient, getChannelInfoClient } from '../../clientActions'; import { VideoData } from '../../constants'; import LoadingSpinner from '../../components/LoadingSpinner'; const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8080/api'; interface Subscription { channel_id: string; channel_name: string; channel_avatar: string; } const DEFAULT_THUMBNAIL = 'data:image/svg+xml,No thumbnail'; interface ChannelVideos { subscription: Subscription; videos: VideoData[]; channelInfo: any; } // Fetch subscriptions from backend API async function fetchSubscriptions(): Promise { try { const res = await fetch(`${API_BASE}/subscriptions`, { cache: 'no-store' }); if (!res.ok) return []; const data = await res.json(); return Array.isArray(data) ? data : []; } catch (e) { console.error('Failed to fetch subscriptions:', e); return []; } } const INITIAL_ROWS = 2; const VIDEOS_PER_ROW = 5; const MAX_ROWS = 5; function formatViews(views: number): string { if (views >= 1000000) return (views / 1000000).toFixed(1) + 'M'; if (views >= 1000) return (views / 1000).toFixed(1) + 'K'; return views.toString(); } function ChannelSection({ channelVideos, defaultExpanded = false }: { channelVideos: ChannelVideos; defaultExpanded?: boolean }) { const { subscription, videos } = channelVideos; const [expanded, setExpanded] = useState(defaultExpanded); const handleImageError = useCallback((e: React.SyntheticEvent) => { const img = e.target as HTMLImageElement; if (img.src !== DEFAULT_THUMBNAIL) { img.src = DEFAULT_THUMBNAIL; } }, []); if (videos.length === 0) return null; const initialCount = INITIAL_ROWS * VIDEOS_PER_ROW; const maxCount = MAX_ROWS * VIDEOS_PER_ROW; const displayedVideos = expanded ? videos.slice(0, maxCount) : videos.slice(0, initialCount); const hasMore = videos.length > initialCount; return (
{subscription.channel_avatar ? ( ) : ( subscription.channel_name ? subscription.channel_name[0].toUpperCase() : '?' )}
{subscription.channel_name || subscription.channel_id}
{displayedVideos.map((video) => { const relativeTime = video.publishedAt || video.upload_date || 'recently'; const destination = `/watch?v=${video.id}`; const thumbnailSrc = video.thumbnail || DEFAULT_THUMBNAIL; return (
{video.title} {video.duration && (
{video.duration}
)}

{video.title}

{video.viewCount || formatViews(video.view_count || 0)} views • {relativeTime}

); })}
{hasMore && (
)}
); } export default function SubscriptionsPage() { const [channelsVideos, setChannelsVideos] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchData() { try { const subs = await fetchSubscriptions(); const channelVideos: ChannelVideos[] = []; // Fetch videos for each subscription in parallel const promises = subs.map(async (sub) => { try { const channelId = sub.channel_id; const videos = await getChannelVideosClient(channelId, MAX_ROWS * VIDEOS_PER_ROW); const channelInfo = await getChannelInfoClient(channelId); if (videos.length > 0) { return { subscription: sub, videos: videos, channelInfo: channelInfo || null, }; } return null; } catch (err) { console.error(`Failed to fetch videos for ${sub.channel_id}:`, err); return null; } }); const results = await Promise.all(promises); const validResults = results.filter((r): r is ChannelVideos => r !== null); setChannelsVideos(validResults); } catch (err) { console.error('Failed to fetch subscriptions:', err); } finally { setLoading(false); } } fetchData(); }, []); if (loading) { return (
); } if (channelsVideos.length === 0) { return (

No subscriptions yet

Subscribe to channels to see their latest videos here

Discover videos
); } return (

Sub

{channelsVideos.map((channelData) => ( ))}
); }