// src/App.js import React, { useState, useEffect } from 'react'; import axios from 'axios'; import './App.css'; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:4000'; function App() { const [videos, setVideos] = useState([]); const [channels, setChannels] = useState([]); const [loading, setLoading] = useState(true); const [channelsLoading, setChannelsLoading] = useState(true); const [error, setError] = useState(null); const [selectedChannelId, setSelectedChannelId] = useState(''); const [expandedDescriptions, setExpandedDescriptions] = useState({}); const [showAddSubscription, setShowAddSubscription] = useState(false); const [subscriptionUrl, setSubscriptionUrl] = useState(''); const [timeBetweenFetches, setTimeBetweenFetches] = useState(300); // Default 5 minutes const [addingSubscription, setAddingSubscription] = useState(false); const [subscriptionError, setSubscriptionError] = useState(null); const [subscriptionSuccess, setSubscriptionSuccess] = useState(null); const fetchChannels = async () => { try { setChannelsLoading(true); const response = await axios.get(`${API_BASE_URL}/subs-info`); if (response.data && Array.isArray(response.data)) { const formattedChannels = response.data.map(channel => { // Extract the channel ID from the _id field const channelId = channel._id.replace('yt:channel:', ''); return { id: channelId, _id: channel._id, last_video_update: channel.last_video_update, new_vids: channel.new_vids, time_between_fetches: channel.time_between_fetches, videos: channel.videos }; }); setChannels(formattedChannels); } } catch (err) { console.error('Error fetching channels:', err); setError('Failed to fetch available channels.'); } finally { setChannelsLoading(false); } }; const fetchVideos = async (channelId) => { try { setLoading(true); setError(null); setExpandedDescriptions({}); // Reset expanded states when fetching new videos const apiUrl = `${API_BASE_URL}/vid-from-link/yt:channel:${channelId}`; const response = await axios.get(apiUrl); if (response.data && Array.isArray(response.data)) { setVideos(response.data); } else { throw new Error('Invalid response format'); } } catch (err) { setError('Failed to fetch videos. Please check the channel and ensure the API is running.'); console.error('Error fetching videos:', err); } finally { setLoading(false); } }; useEffect(() => { fetchChannels(); }, []); useEffect(() => { if (selectedChannelId) { fetchVideos(selectedChannelId); } }, [selectedChannelId]); const handleChannelChange = (e) => { setSelectedChannelId(e.target.value); }; const handleRefreshChannels = () => { fetchChannels(); }; const toggleDescription = (videoId) => { setExpandedDescriptions(prev => ({ ...prev, [videoId]: !prev[videoId] })); }; const formatDate = (dateString) => { try { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch { return dateString; } }; const formatRelativeTime = (dateString) => { try { const date = new Date(dateString); const now = new Date(); const diffInHours = Math.floor((now - date) / (1000 * 60 * 60)); if (diffInHours < 1) { return 'Just now'; } else if (diffInHours < 24) { return `${diffInHours}h ago`; } else { const diffInDays = Math.floor(diffInHours / 24); return `${diffInDays}d ago`; } } catch { return dateString; } }; const formatDuration = (seconds) => { if (seconds < 0) { return '?:??'; } const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; } else { return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } }; const handleAddSubscription = async (e) => { e.preventDefault(); if (!subscriptionUrl.trim()) { setSubscriptionError('Please enter a YouTube channel/playlist URL'); return; } try { setAddingSubscription(true); setSubscriptionError(null); setSubscriptionSuccess(null); const formData = new FormData(); formData.append('url', subscriptionUrl.trim()); formData.append('time_between_fetches', timeBetweenFetches.toString()); const response = await axios.post(`${API_BASE_URL}/add-sub/`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); if (response.data) { setSubscriptionSuccess('Subscription added successfully!'); setSubscriptionUrl(''); setTimeBetweenFetches(300); setShowAddSubscription(false); // Refresh the subs list to include the new subscription fetchChannels(); } else { throw new Error(response.data.error || 'Failed to add subscription'); } } catch (err) { console.error('Error adding subscription:', err); setSubscriptionError( err.response?.data?.error || err.message || 'Failed to add subscription. Please check the URL and try again.' ); } finally { setAddingSubscription(false); } }; const resetSubscriptionForm = () => { setSubscriptionUrl(''); setTimeBetweenFetches(300); setSubscriptionError(null); setSubscriptionSuccess(null); }; return (

YouTube Channel Videos

{selectedChannelId && channels.length > 0 && (

Selected: {selectedChannelId} {channels.find(ch => ch.id === selectedChannelId)?.last_video_update && ( • Last updated: {formatRelativeTime(channels.find(ch => ch.id === selectedChannelId).last_video_update)} )}

)}
{showAddSubscription && (

Add New Subscription

setSubscriptionUrl(e.target.value)} placeholder="https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxx" className="subscription-input" disabled={addingSubscription} />
setTimeBetweenFetches(parseInt(e.target.value) || 300)} min="60" max="86400" className="subscription-input" disabled={addingSubscription} /> How often to check for new videos (default: 300s = 5 minutes)
{subscriptionError && (
{subscriptionError}
)} {subscriptionSuccess && (
{subscriptionSuccess}
)}
)}
{channelsLoading &&
Loading channels...
} {error && (
{error}
)} {!channelsLoading && channels.length === 0 && !error && (

No channels available

No YouTube channels found in the subscription list.

)} {loading && selectedChannelId && (
Loading videos for {selectedChannelId}...
)} {!loading && !error && selectedChannelId && (

Latest Videos from {selectedChannelId} {videos.length > 0 && ` (${videos.length})`}

{videos.sort((a, b) => new Date(b.published) - new Date(a.published)).map((video) => (
{video.title} { e.target.src = 'https://via.placeholder.com/300x180/333/fff?text=No+Thumbnail'; }} />
{formatDuration(video.duration)}

{video.title}

By: {video.author}

Published: {formatDate(video.published)}

{video.updated && (

Updated: {formatDate(video.updated)}

)} {video.summary && (

{video.summary}

{video.summary.length > 100 && ( )}
)}
))}
)} {!loading && !error && selectedChannelId && videos.length === 0 && (

No videos found for this channel

The channel might not have any videos or there was an issue loading them.

)} {!channelsLoading && !selectedChannelId && channels.length > 0 && (

Please select a channel to view videos

Choose a channel from the dropdown above to see its latest videos.

)}
); } export default App;