summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA Farzat <a@farzat.xyz>2025-10-08 18:58:50 +0300
committerA Farzat <a@farzat.xyz>2025-10-08 18:58:50 +0300
commitd1b1c055bb9ed49530e9a96fd34b3ada5899bbb5 (patch)
tree36520f29b88f13151d7582be1f29a568dec193e8
parent1d7afc2f1372e46a102d088242bbccec5ba5af4e (diff)
downloadcsca5028-d1b1c055bb9ed49530e9a96fd34b3ada5899bbb5.tar.gz
csca5028-d1b1c055bb9ed49530e9a96fd34b3ada5899bbb5.zip
Update front-end to display titles and accomodate playlists
-rw-r--r--front-end/src/App.css58
-rw-r--r--front-end/src/App.jsx91
2 files changed, 114 insertions, 35 deletions
diff --git a/front-end/src/App.css b/front-end/src/App.css
index 19cddd2..714d6fb 100644
--- a/front-end/src/App.css
+++ b/front-end/src/App.css
@@ -88,10 +88,68 @@ body {
font-size: 0.9rem;
}
+.subscription-main {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin: 0 0 0.3rem 0;
+}
+
.channel-info strong {
color: #74b9ff;
}
+.subscription-type {
+ color: #74b9ff;
+ font-size: 0.8em;
+ background: rgba(116, 185, 255, 0.2);
+ padding: 0.2rem 0.5rem;
+ border-radius: 10px;
+}
+
+.last-dates {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 1rem;
+ margin: 0;
+ font-size: 0.9em;
+}
+
+.last-updated {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.last-fetched {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.subscription-id {
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.8em;
+ font-weight: normal;
+}
+
+/* Responsive updates */
+@media (max-width: 768px) {
+ .subscription-main {
+ justify-content: center;
+ text-align: center;
+ gap: 0.3rem;
+ }
+
+ .last-dates {
+ justify-content: center;
+ text-align: center;
+ gap: 0.5rem;
+ }
+
+ .channel-info {
+ font-size: 0.8rem;
+ }
+}
+
.subscription-section {
max-width: 600px;
margin: 1.5rem auto 0;
diff --git a/front-end/src/App.jsx b/front-end/src/App.jsx
index 4215650..104b561 100644
--- a/front-end/src/App.jsx
+++ b/front-end/src/App.jsx
@@ -26,23 +26,29 @@ function App() {
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:', '');
+ const formattedChannels = response.data.map(subscription => {
+ // Extract the ID and type from the _id field
+ const isPlaylist = subscription._id.startsWith('yt:playlist:');
+ const id = subscription._id.replace('yt:channel:', '').replace('yt:playlist:', '');
+ const type = isPlaylist ? 'playlist' : '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
+ id: id,
+ _id: subscription._id,
+ type: type,
+ title: subscription.title || id, // Use title if available, fallback to ID
+ last_fetch: subscription.last_fetch,
+ last_video_update: subscription.last_video_update,
+ new_vids: subscription.new_vids,
+ time_between_fetches: subscription.time_between_fetches,
+ videos: subscription.videos
};
});
setChannels(formattedChannels);
}
} catch (err) {
console.error('Error fetching channels:', err);
- setError('Failed to fetch available channels.');
+ setError('Failed to fetch available subscriptions.');
} finally {
setChannelsLoading(false);
}
@@ -54,7 +60,11 @@ function App() {
setError(null);
setExpandedDescriptions({}); // Reset expanded states when fetching new videos
- const apiUrl = `${API_BASE_URL}/vid-from-link/yt:channel:${channelId}`;
+ // Find the full _id for the API call
+ const subscription = channels.find(ch => ch.id === channelId);
+ if (!subscription) return;
+
+ const apiUrl = `${API_BASE_URL}/vid-from-link/${subscription._id}`;
const response = await axios.get(apiUrl);
if (response.data && Array.isArray(response.data)) {
@@ -63,7 +73,7 @@ function App() {
throw new Error('Invalid response format');
}
} catch (err) {
- setError('Failed to fetch videos. Please check the channel and ensure the API is running.');
+ setError('Failed to fetch videos. Please check the subscription and ensure the API is running.');
console.error('Error fetching videos:', err);
} finally {
setLoading(false);
@@ -200,11 +210,11 @@ function App() {
return (
<div className="App">
<header className="App-header">
- <h1>YouTube Channel Videos</h1>
+ <h1>YouTube Subscriptions</h1>
<div className="channel-selector">
<div className="selector-header">
- <label htmlFor="channel-select">Select Channel:</label>
+ <label htmlFor="channel-select">Select Subscription:</label>
<button
onClick={handleRefreshChannels}
className="refresh-button"
@@ -222,15 +232,15 @@ function App() {
disabled={channelsLoading || channels.length === 0}
>
{channelsLoading ? (
- <option value="">Loading channels...</option>
+ <option value="">Loading subscriptions...</option>
) : channels.length === 0 ? (
- <option value="">No channels available</option>
+ <option value="">No subscriptions available</option>
) : (
<>
- <option value="">Choose a channel...</option>
+ <option value="">Choose a subscription...</option>
{channels.map((channel) => (
<option key={channel.id} value={channel.id}>
- {channel.id} ({channel.videos} videos, {channel.new_vids} new)
+ {channel.type === 'playlist' ? '📋' : '📺'} {channel.title} - {channel.id} ({channel.videos} videos, {channel.new_vids} new)
</option>
))}
</>
@@ -239,12 +249,22 @@ function App() {
{selectedChannelId && channels.length > 0 && (
<div className="channel-info">
- <p>
- Selected: <strong>{selectedChannelId}</strong>
- {channels.find(ch => ch.id === selectedChannelId)?.last_video_update && (
- <span> • Last updated: {formatRelativeTime(channels.find(ch => ch.id === selectedChannelId).last_video_update)}</span>
- )}
+ <p className="subscription-main">
+ <strong>{channels.find(ch => ch.id === selectedChannelId)?.title} - {selectedChannelId}</strong>
+ <span className="subscription-type">
+ ({channels.find(ch => ch.id === selectedChannelId)?.type})
+ </span>
</p>
+ {(channels.find(ch => ch.id === selectedChannelId)?.last_video_update || channels.find(ch => ch.id === selectedChannelId)?.last_fetch) && (
+ <p className="last-dates">
+ {channels.find(ch => ch.id === selectedChannelId)?.last_video_update && (
+ <span className="last-updated">Last updated: {formatRelativeTime(channels.find(ch => ch.id === selectedChannelId).last_video_update)}</span>
+ )}
+ {channels.find(ch => ch.id === selectedChannelId)?.last_fetch && (
+ <span className="last-fetched">Last fetched: {formatRelativeTime(channels.find(ch => ch.id === selectedChannelId).last_fetch)}</span>
+ )}
+ </p>
+ )}
</div>
)}
</div>
@@ -265,13 +285,13 @@ function App() {
<h3>Add New Subscription</h3>
<div className="form-group">
- <label htmlFor="subscription-url">YouTube Channel URL:</label>
+ <label htmlFor="subscription-url">YouTube Channel or Playlist URL:</label>
<input
id="subscription-url"
type="url"
value={subscriptionUrl}
onChange={(e) => setSubscriptionUrl(e.target.value)}
- placeholder="https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxx"
+ placeholder="https://www.youtube.com/channel/UCxxx... or https://www.youtube.com/playlist?list=PLxxx..."
className="subscription-input"
disabled={addingSubscription}
/>
@@ -320,7 +340,7 @@ function App() {
</header>
<main className="main-content">
- {channelsLoading && <div className="loading">Loading channels...</div>}
+ {channelsLoading && <div className="loading">Loading subscriptions...</div>}
{error && (
<div className="error">
@@ -333,22 +353,23 @@ function App() {
{!channelsLoading && channels.length === 0 && !error && (
<div className="no-channels">
- <h3>No channels available</h3>
- <p>No YouTube channels found in the subscription list.</p>
+ <h3>No subscriptions available</h3>
+ <p>No YouTube channels or playlists found in the subscription list.</p>
<button onClick={handleRefreshChannels} className="retry-button">
- Refresh Channels
+ Refresh Subscriptions
</button>
</div>
)}
{loading && selectedChannelId && (
- <div className="loading">Loading videos for {selectedChannelId}...</div>
+ <div className="loading">Loading videos for {channels.find(ch => ch.id === selectedChannelId)?.title}...</div>
)}
{!loading && !error && selectedChannelId && (
<div className="videos-container">
<h2>
- Latest Videos from {selectedChannelId}
+ Latest Videos from {channels.find(ch => ch.id === selectedChannelId)?.title}
+ <span className="subscription-id"> - {selectedChannelId}</span>
{videos.length > 0 && ` (${videos.length})`}
</h2>
<div className="videos-grid">
@@ -426,15 +447,15 @@ function App() {
{!loading && !error && selectedChannelId && videos.length === 0 && (
<div className="no-videos">
- <h3>No videos found for this channel</h3>
- <p>The channel might not have any videos or there was an issue loading them.</p>
+ <h3>No videos found for this subscription</h3>
+ <p>The channel or playlist might not have any videos or there was an issue loading them.</p>
</div>
)}
{!channelsLoading && !selectedChannelId && channels.length > 0 && (
<div className="no-selection">
- <h3>Please select a channel to view videos</h3>
- <p>Choose a channel from the dropdown above to see its latest videos.</p>
+ <h3>Please select a subscription to view videos</h3>
+ <p>Choose a channel or playlist from the dropdown above to see its latest videos.</p>
</div>
)}
</main>