Skip to content

Commit 0c96865

Browse files
committed
Fix email input overlap, update YouTube section, improve blog cards and newsletter design
- Fixed email input placeholder overlap with icon by adding spacing - Changed 'Latest Workshops' to 'Latest on Kubesimplify YouTube' - Updated YouTubeFeed to show first 3 from playlist, next 3 from channel - Improved blog cards: reduced gaps, fixed text overflow, better spacing - Redesigned 'Support the Mission' section to be more compact - Enhanced 'Join the Inner Circle' newsletter section design - Fixed PartnershipSection colors for light/dark themes - Updated blog feed to fetch latest posts from Hashnode
1 parent e276eeb commit 0c96865

4 files changed

Lines changed: 319 additions & 96 deletions

File tree

src/components/BlogFeed.js

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,36 @@ import React, { useState, useEffect } from 'react';
22
import Skeleton from '@site/src/components/Skeleton';
33

44
const BlogCard = ({ blog }) => (
5-
<div className="group flex flex-col bg-dark-card rounded-3xl 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">
6-
<a href={`https://blog.kubesimplify.com/${blog.slug}`} target="_blank" rel="noopener noreferrer" className="flex-1 flex flex-col">
7-
<div className="relative overflow-hidden aspect-[4/3] w-full rounded-t-3xl">
5+
<div className="group flex flex-col bg-dark-card 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 h-full">
6+
<a href={`https://blog.kubesimplify.com/${blog.slug}`} target="_blank" rel="noopener noreferrer" className="flex-1 flex flex-col h-full">
7+
<div className="relative w-full aspect-[4/3] flex-shrink-0 rounded-t-2xl overflow-hidden">
88
<img
99
src={blog.coverImage?.url || 'https://blog.kubesimplify.com/img/cover.png'}
1010
alt={blog.title}
1111
className="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700"
1212
/>
1313
<div className="absolute inset-0 bg-gradient-to-t from-dark-bg via-transparent to-transparent opacity-60" />
1414
</div>
15-
<div className="p-8 flex-1 flex flex-col">
16-
<div className="mb-4 flex items-center space-x-2">
17-
<span className="px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-bold uppercase tracking-wider border border-primary/20">
15+
<div className="p-5 md:p-6 flex-1 flex flex-col">
16+
<div className="mb-3 flex items-center flex-wrap gap-2">
17+
<span className="px-2.5 py-1 rounded-full bg-primary/10 text-primary text-xs font-bold uppercase tracking-wider border border-primary/20 whitespace-nowrap">
1818
Article
1919
</span>
20-
<span className="text-gray-500 text-xs font-medium">
20+
<span className="text-gray-500 text-xs font-medium whitespace-nowrap">
2121
{new Date(blog.publishedAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
2222
</span>
2323
</div>
24-
<h3 className="text-xl font-bold text-white mb-3 leading-tight group-hover:text-primary transition-colors duration-300 min-h-[3.5rem]">
24+
<h3 className="text-lg md:text-xl font-bold text-white mb-3 leading-snug group-hover:text-primary transition-colors duration-300 line-clamp-3">
2525
{blog.title}
2626
</h3>
27-
<p className="text-gray-400 text-sm leading-relaxed mb-6">
27+
<p className="text-gray-400 text-sm leading-relaxed mb-4 line-clamp-4 flex-1">
2828
{blog.brief}
2929
</p>
30-
<div className="mt-auto flex items-center text-primary font-bold text-sm pt-4 pl-6 ml-4 pr-4">
31-
Read Article
32-
<svg className="w-4 h-4 ml-2 transform group-hover:translate-x-2 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>
30+
<div className="mt-auto pt-4 border-t border-white/5">
31+
<span className="inline-flex items-center text-primary font-bold text-sm group-hover:gap-2 transition-all duration-300">
32+
Read Article
33+
<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>
34+
</span>
3335
</div>
3436
</div>
3537
</a>
@@ -47,7 +49,7 @@ export default function BlogFeed() {
4749
const query = `
4850
query Publication {
4951
publication(host: "blog.kubesimplify.com") {
50-
posts(first: 3) {
52+
posts(first: 6) {
5153
edges {
5254
node {
5355
title
@@ -72,11 +74,29 @@ export default function BlogFeed() {
7274
body: JSON.stringify({ query }),
7375
});
7476

75-
const { data } = await response.json();
77+
if (!response.ok) {
78+
throw new Error(`HTTP error! status: ${response.status}`);
79+
}
80+
81+
const result = await response.json();
82+
83+
if (result.errors) {
84+
console.error('GraphQL errors:', result.errors);
85+
throw new Error('GraphQL query failed');
86+
}
87+
88+
const { data } = result;
7689

7790
if (data?.publication?.posts?.edges) {
78-
setBlogs(data.publication.posts.edges.map(edge => edge.node));
91+
// Hashnode returns posts in reverse chronological order by default
92+
// Sort by publishedAt to ensure latest first, then take first 3
93+
const sortedBlogs = data.publication.posts.edges
94+
.map(edge => edge.node)
95+
.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt))
96+
.slice(0, 3);
97+
setBlogs(sortedBlogs);
7998
} else {
99+
console.error('No posts found in response:', data);
80100
setError(true);
81101
}
82102
} catch (error) {
@@ -92,7 +112,7 @@ export default function BlogFeed() {
92112

93113
if (error) {
94114
return (
95-
<div className="text-center py-12 bg-dark-card rounded-3xl border border-white/5">
115+
<div className="text-center py-12 bg-dark-card rounded-2xl border border-white/5">
96116
<p className="text-gray-400 mb-4">Unable to load articles at the moment.</p>
97117
<a href="https://blog.kubesimplify.com" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-white font-bold transition-colors">
98118
Visit our Blog
@@ -102,22 +122,22 @@ export default function BlogFeed() {
102122
}
103123

104124
return (
105-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
125+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-5 w-full">
106126
{loading ? (
107127
[...Array(3)].map((_, i) => (
108-
<div key={i} className="bg-dark-card rounded-3xl overflow-hidden h-[500px] border border-white/5">
128+
<div key={i} className="bg-dark-card rounded-2xl overflow-hidden h-[500px] border border-white/5">
109129
<Skeleton className="h-48 w-full" />
110-
<div className="p-8">
111-
<Skeleton className="h-4 w-20 mb-4 rounded-full bg-white/10" />
112-
<Skeleton className="h-8 w-3/4 mb-4 bg-white/10" />
113-
<Skeleton className="h-20 w-full mb-6 bg-white/10" />
130+
<div className="p-5 md:p-6">
131+
<Skeleton className="h-4 w-20 mb-3 rounded-full bg-white/10" />
132+
<Skeleton className="h-8 w-3/4 mb-3 bg-white/10" />
133+
<Skeleton className="h-20 w-full mb-4 bg-white/10" />
114134
<Skeleton className="h-4 w-1/3 bg-white/10" />
115135
</div>
116136
</div>
117137
))
118138
) : (
119139
blogs.map((blog, index) => (
120-
<BlogCard key={index} blog={blog} />
140+
<BlogCard key={blog.slug || index} blog={blog} />
121141
))
122142
)}
123143
</div>

src/components/YouTubeFeed.js

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)