@@ -9,15 +9,90 @@ const YouTubeFeed = () => {
99 useEffect ( ( ) => {
1010 const fetchVideos = async ( ) => {
1111 try {
12- // Using playlist ID for "Latest" or specific curated list
12+ // First 3 videos from workshop playlist
1313 const PLAYLIST_ID = 'PL5uLNcv9SibDZ6AkVDXXqxKtYM0JJ0wqt' ;
14- const FEED_URL = `https://www.youtube.com/feeds/videos.xml?playlist_id=${ PLAYLIST_ID } ` ;
14+ const PLAYLIST_FEED_URL = `https://www.youtube.com/feeds/videos.xml?playlist_id=${ PLAYLIST_ID } ` ;
1515
16- const response = await fetch ( `https://api.rss2json.com/v1/api.json?rss_url=${ encodeURIComponent ( FEED_URL ) } ` ) ;
17- const data = await response . json ( ) ;
16+ // Fetch workshop playlist first
17+ const playlistResponse = await fetch ( `https://api.rss2json.com/v1/api.json?rss_url=${ encodeURIComponent ( PLAYLIST_FEED_URL ) } ` ) ;
18+ const playlistData = await playlistResponse . json ( ) ;
1819
19- if ( data . items ) {
20- setVideos ( data . items . slice ( 0 , 6 ) ) ; // Top 6 videos
20+ const allVideos = [ ] ;
21+
22+ // Add first 3 from workshop playlist
23+ if ( playlistData . items && playlistData . items . length > 0 ) {
24+ allVideos . push ( ...playlistData . items . slice ( 0 , 3 ) ) ;
25+ }
26+
27+ // Try to fetch channel's latest videos
28+ // Try multiple RSS feed formats for the channel
29+ // Channel ID format: UU + channel ID for uploads playlist
30+ const channelFeedUrls = [
31+ `https://www.youtube.com/feeds/videos.xml?channel_id=UCi-1nnN0eC9nRleXdZA6ncg` , // Channel ID format
32+ `https://www.youtube.com/feeds/videos.xml?user=kubesimplify` , // User format (legacy)
33+ `https://www.youtube.com/feeds/videos.xml?playlist_id=UUi-1nnN0eC9nRleXdZA6ncg` , // Uploads playlist (UU + channel ID)
34+ ] ;
35+
36+ let channelVideos = [ ] ;
37+ for ( const feedUrl of channelFeedUrls ) {
38+ try {
39+ const channelResponse = await fetch ( `https://api.rss2json.com/v1/api.json?rss_url=${ encodeURIComponent ( feedUrl ) } ` ) ;
40+ const channelData = await channelResponse . json ( ) ;
41+
42+ if ( channelData . items && channelData . items . length > 0 ) {
43+ // Exclude duplicates from playlist
44+ const playlistVideoIds = new Set ( allVideos . map ( v => v . guid || v . link ) ) ;
45+ channelVideos = channelData . items
46+ . filter ( v => {
47+ const videoId = v . guid || v . link ;
48+ return ! playlistVideoIds . has ( videoId ) ;
49+ } )
50+ . slice ( 0 , 3 ) ;
51+ break ; // Success, stop trying other URLs
52+ }
53+ } catch ( err ) {
54+ console . log ( `Failed to fetch from ${ feedUrl } , trying next...` ) ;
55+ continue ;
56+ }
57+ }
58+
59+ // If channel feed didn't work, try getting channel uploads playlist
60+ // Channel uploads playlist ID format: UU + channel ID (but we don't have channel ID)
61+ // Alternative: Use the channel's custom URL format
62+ if ( channelVideos . length === 0 ) {
63+ try {
64+ // Try using the channel handle in uploads format
65+ // This is a workaround - we'll use the channel's uploads if we can find it
66+ const uploadsFeedUrl = `https://www.youtube.com/feeds/videos.xml?user=kubesimplify` ;
67+ const uploadsResponse = await fetch ( `https://api.rss2json.com/v1/api.json?rss_url=${ encodeURIComponent ( uploadsFeedUrl ) } ` ) ;
68+ const uploadsData = await uploadsResponse . json ( ) ;
69+
70+ if ( uploadsData . items && uploadsData . items . length > 0 ) {
71+ const playlistVideoIds = new Set ( allVideos . map ( v => v . guid || v . link ) ) ;
72+ channelVideos = uploadsData . items
73+ . filter ( v => {
74+ const videoId = v . guid || v . link ;
75+ return ! playlistVideoIds . has ( videoId ) ;
76+ } )
77+ . slice ( 0 , 3 ) ;
78+ }
79+ } catch ( err ) {
80+ console . log ( 'Could not fetch channel uploads, using only playlist videos' ) ;
81+ }
82+ }
83+
84+ // Add channel videos if we got them
85+ if ( channelVideos . length > 0 ) {
86+ allVideos . push ( ...channelVideos ) ;
87+ } else {
88+ // If we couldn't get channel videos, just use more from playlist
89+ if ( playlistData . items && playlistData . items . length > 3 ) {
90+ allVideos . push ( ...playlistData . items . slice ( 3 , 6 ) ) ;
91+ }
92+ }
93+
94+ if ( allVideos . length > 0 ) {
95+ setVideos ( allVideos ) ;
2196 } else {
2297 setError ( true ) ;
2398 }
@@ -34,11 +109,11 @@ const YouTubeFeed = () => {
34109
35110 if ( loading ) {
36111 return (
37- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 " >
112+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-5 " >
38113 { [ ...Array ( 3 ) ] . map ( ( _ , i ) => (
39- < div key = { i } className = "bg-dark-card rounded-3xl overflow-hidden border border-white/5" >
114+ < div key = { i } className = "bg-dark-card rounded-2xl overflow-hidden border border-white/5" >
40115 < Skeleton className = "h-48 w-full" />
41- < div className = "p-6" >
116+ < div className = "p-5 md:p- 6" >
42117 < Skeleton className = "h-6 w-3/4 mb-3 bg-white/10" />
43118 < Skeleton className = "h-4 w-1/2 bg-white/10" />
44119 </ div >
@@ -50,7 +125,7 @@ const YouTubeFeed = () => {
50125
51126 if ( error ) {
52127 return (
53- < div className = "text-center py-10 bg-white/5 rounded-3xl border border-white/10" >
128+ < div className = "text-center py-10 bg-white/5 rounded-2xl border border-white/10" >
54129 < p className = "text-gray-400 mb-4" > Unable to load videos at the moment.</ p >
55130 < a href = "https://www.youtube.com/@kubesimplify" target = "_blank" rel = "noopener noreferrer" className = "text-primary hover:underline" >
56131 Visit our YouTube Channel
@@ -60,11 +135,11 @@ const YouTubeFeed = () => {
60135 }
61136
62137 return (
63- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 " >
138+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-5 " >
64139 { videos . map ( ( video ) => (
65- < div key = { video . guid } className = "group relative bg-[#0a0a0a] rounded-3xl overflow-hidden border border-white/5 hover:border-primary/50 transition-all duration-500 hover:shadow-[0_0_30px_rgba(0,242,255,0.15)] hover:-translate-y-2 flex flex-col h-full" >
66- < a href = { video . link } target = "_blank" rel = "noopener noreferrer" className = "block flex-1 flex flex-col" >
67- < div className = "relative overflow-hidden aspect-video" >
140+ < div key = { video . guid } className = "group relative bg-[#0a0a0a] rounded-2xl border border-white/5 hover:border-primary/50 transition-all duration-500 hover:shadow-[0_0_30px_rgba(0,242,255,0.15)] hover:-translate-y-2 flex flex-col h-full" >
141+ < a href = { video . link } target = "_blank" rel = "noopener noreferrer" className = "block flex-1 flex flex-col h-full " >
142+ < div className = "relative w-full aspect-video flex-shrink-0 rounded-t-2xl overflow-hidden " >
68143 < img
69144 src = { video . thumbnail }
70145 alt = { video . title }
@@ -77,20 +152,21 @@ const YouTubeFeed = () => {
77152 </ svg >
78153 </ div >
79154 </ div >
80- < div className = "absolute bottom-3 right-3 bg-black/80 text-white text-xs font-bold px-2 py-1 rounded backdrop-blur-sm" >
155+ < div className = "absolute bottom-2 right-2 bg-black/80 text-white text-xs font-bold px-2 py-1 rounded backdrop-blur-sm" >
81156 YOUTUBE
82157 </ div >
83158 </ div >
84- < div className = "p-6 flex-1 flex flex-col" >
85- < h3 className = "text-lg font-bold text-white mb-3 leading-snug group-hover:text-primary transition-colors duration-300 min-h-[3.5rem] " >
159+ < div className = "p-5 md:p- 6 flex-1 flex flex-col" >
160+ < h3 className = "text-lg font-bold text-white mb-3 leading-snug group-hover:text-primary transition-colors duration-300 line-clamp-2 " >
86161 { video . title }
87162 </ h3 >
88163 < div className = "mt-auto flex items-center justify-between pt-4 border-t border-white/5" >
89164 < span className = "text-sm text-gray-500" >
90165 { new Date ( video . pubDate ) . toLocaleDateString ( undefined , { year : 'numeric' , month : 'short' , day : 'numeric' } ) }
91166 </ span >
92- < span className = "text-sm font-bold text-primary opacity-0 group-hover:opacity-100 transform translate-x-[-10px] group-hover:translate-x-0 transition-all duration-300 pl-3" >
93- Watch Now →
167+ < span className = "inline-flex items-center text-sm font-bold text-primary opacity-0 group-hover:opacity-100 transform translate-x-[-10px] group-hover:translate-x-0 transition-all duration-300" >
168+ Watch Now
169+ < svg className = "w-4 h-4 ml-1.5 transform group-hover:translate-x-1 transition-transform duration-300" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" > < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M17 8l4 4m0 0l-4 4m4-4H3" /> </ svg >
94170 </ span >
95171 </ div >
96172 </ div >
0 commit comments