Files
testingcodeantrepo/.gitea/workflows/src/components/InsightsSection.tsx
WDI-Ideas 9c679f896f
All checks were successful
CodeAnt AI Review - Stage 1 / codeant-review (push) Successful in 58s
first commit
2026-03-30 01:02:06 +05:30

363 lines
12 KiB
TypeScript

import { ArrowUpRight } from "lucide-react";
import { ImageWithFallback } from "./figma/ImageWithFallback";
import { BrandedTag } from "./about/BrandedTag";
import { StandardCTAButton } from "./StandardCTAButton";
import { navigateTo } from "./Router";
import { useGetFeaturedBlogsQuery } from "../redux/services/homepageApi";
import { FullScreenLoader } from "./FullScreenLoader";
import { getSlugWithId } from "../utils/urlHelpers";
// Interface for featured blog items from API
interface FeaturedBlog {
id: string;
title: string;
short_description: string | null;
slug_name: string;
banner_img: string | null;
updated_at: string;
content_category: string;
content_type: string;
tags: string[];
featured: boolean;
featured_order: number;
}
// Insight Card Component Interface
interface InsightCardData {
id: string;
title: string;
description?: string;
date: string;
tags: string[];
image: string;
slug: string;
}
// Insight Tag Component
function InsightTag({ text }: { text: string }) {
return (
<span
className="px-3 py-1 text-sm font-medium rounded-full"
style={{
backgroundColor: 'var(--color-brand-accent)',
color: 'var(--color-brand-black)'
}}
>
{text}
</span>
);
}
// Arrow Icon Component
function ArrowIcon() {
return (
<div className="p-2 rounded-full transition-all duration-300 group-hover:bg-gray-100"
style={{ backgroundColor: 'rgba(0, 0, 0, 0.05)' }}>
<ArrowUpRight className="w-4 h-4" style={{ color: 'var(--color-brand-black)' }} />
</div>
);
}
// Calendar Icon Component
function CalendarIcon() {
return (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clipRule="evenodd" />
</svg>
);
}
// Explore All Button Component
function ExploreAllButton() {
return (
<StandardCTAButton
text="Explore All"
onClick={() => navigateTo('/learning/articles')}
ariaLabel="Explore all leadership insights and ideas"
/>
);
}
// Large Insight Card Component
function LargeInsightCard({ card }: { card: InsightCardData }) {
const handleClick = () => {
if (card.slug && card.id) {
const url = getSlugWithId(card.slug, card.id);
navigateTo(`/learning/articles/${url}`);
}
};
const hasLink = !!(card.slug && card.id);
return (
<div
className={`relative h-[500px] rounded-xl overflow-hidden group ${hasLink ? 'cursor-pointer' : ''}`}
onClick={hasLink ? handleClick : undefined}
role={hasLink ? 'button' : undefined}
tabIndex={hasLink ? 0 : undefined}
onKeyDown={hasLink ? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
} : undefined}
>
{/* Background Image */}
<div className="absolute inset-0 transition-transform duration-300 group-hover:scale-105">
<ImageWithFallback
src={card.image}
alt={card.title}
className="w-full h-full object-cover"
/>
</div>
{/* White Content Box */}
<div className="insight-card-white-box absolute bottom-6 left-6 right-6 bg-white rounded-xl p-6 shadow-lg transition-all duration-300 group-hover:shadow-xl">
{/* Top section with tags and arrow */}
<div className="insight-card-header flex items-start justify-between mb-4">
<div className="insight-card-tags flex gap-2 flex-wrap">
{card.tags.slice(0, 2).map((tag, index) => (
<InsightTag key={index} text={tag} />
))}
</div>
<ArrowIcon />
</div>
{/* Bottom section with content and date */}
<div className="insight-card-footer">
<div className="insight-card-text-content mb-4">
<h3 className="insight-card-title text-xl font-bold mb-3 leading-tight"
style={{ color: 'var(--color-brand-black)' }}>
{card.title}
</h3>
{card.description && (
<p className="insight-card-description text-gray-600 text-sm leading-relaxed">
{card.description}
</p>
)}
</div>
<div className="insight-card-date flex items-center gap-2 text-gray-500">
<CalendarIcon />
<span className="insight-card-date-text text-sm font-medium">{card.date}</span>
</div>
</div>
</div>
</div>
);
}
// Small Insight Card Component
function SmallInsightCard({ card }: { card: InsightCardData }) {
const handleClick = () => {
if (card.slug && card.id) {
const url = getSlugWithId(card.slug, card.id);
navigateTo(`/learning/articles/${url}`);
}
};
const hasLink = !!(card.slug && card.id);
return (
<div
className={`relative h-[235px] rounded-xl overflow-hidden group ${hasLink ? 'cursor-pointer' : ''}`}
onClick={hasLink ? handleClick : undefined}
role={hasLink ? 'button' : undefined}
tabIndex={hasLink ? 0 : undefined}
onKeyDown={hasLink ? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
} : undefined}
>
{/* Background Image */}
<div className="absolute inset-0 transition-transform duration-300 group-hover:scale-105">
<ImageWithFallback
src={card.image}
alt={card.title}
className="w-full h-full object-cover"
/>
</div>
{/* White Content Box */}
<div className="insight-card-white-box absolute bottom-4 left-4 right-4 bg-white rounded-xl p-4 shadow-lg transition-all duration-300 group-hover:shadow-xl">
{/* Top section with tags and arrow */}
<div className="insight-card-header flex items-start justify-between mb-3">
<div className="insight-card-tags flex gap-2 flex-wrap">
{card.tags.slice(0, 2).map((tag, index) => (
<span
key={index}
className="px-2.5 py-1 text-xs font-medium rounded-full"
style={{
backgroundColor: 'var(--color-brand-accent)',
color: 'var(--color-brand-black)'
}}
>
{tag}
</span>
))}
</div>
<div className="p-1.5 rounded-full transition-all duration-300 group-hover:bg-gray-100"
style={{ backgroundColor: 'rgba(0, 0, 0, 0.05)' }}>
<ArrowUpRight className="w-3.5 h-3.5" style={{ color: 'var(--color-brand-black)' }} />
</div>
</div>
{/* Bottom section with content and date */}
<div className="insight-card-footer">
<div className="insight-card-text-content mb-3">
<h3 className="insight-card-title text-base font-bold leading-tight"
style={{ color: 'var(--color-brand-black)' }}>
{card.title}
</h3>
</div>
<div className="insight-card-date flex items-center gap-2 text-gray-500">
<CalendarIcon />
<span className="insight-card-date-text text-sm font-medium">{card.date}</span>
</div>
</div>
</div>
</div>
);
}
// Format date function
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
export function InsightsSection() {
// Fetch featured blogs from API
const { data: featuredBlogs, isLoading, isError } = useGetFeaturedBlogsQuery({ limit: 3 });
// Transform API data to match InsightCardData format
const transformToInsightCard = (blog: FeaturedBlog): InsightCardData => ({
id: blog.id,
title: blog.title,
description: blog.short_description || undefined,
date: formatDate(blog.updated_at),
tags: blog.tags || [],
image: blog.banner_img || 'https://images.unsplash.com/photo-1481627834876-b7833e8f5570?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&w=1080',
slug: blog.slug_name,
});
// Handle loading state
if (isLoading) {
return (
<section
className="py-24"
style={{
backgroundColor: '#F7F7FD',
paddingTop: '8.75rem',
paddingBottom: '8.75rem'
}}
>
<div className="max-w-7xl mx-auto section-margin-x">
<div className="flex items-center justify-center min-h-[400px]">
<FullScreenLoader text="Loading insights..." />
</div>
</div>
</section>
);
}
// Handle error or no data state
if (isError || !featuredBlogs || featuredBlogs.length === 0) {
return (
<section
className="py-24"
style={{
backgroundColor: '#F7F7FD',
paddingTop: '8.75rem',
paddingBottom: '8.75rem'
}}
>
<div className="max-w-7xl mx-auto section-margin-x">
<BrandedTag text="Leadership Insights" />
<div className="insights-container">
{/* Header */}
<div className="insights-header flex flex-col lg:flex-row lg:items-center lg:justify-between mb-16 gap-8 text-center lg:text-left">
<h2
className="insights-title text-3xl md:text-4xl lg:text-5xl font-bold leading-tight"
style={{ color: 'var(--color-brand-black)' }}
>
Leadership Insights & Ideas
</h2>
<ExploreAllButton />
</div>
{/* Show message when no featured blogs available */}
<div className="text-center py-12">
<p className="text-gray-600">No featured insights available at the moment. Check back soon!</p>
</div>
</div>
</div>
</section>
);
}
// Ensure we have at least 3 blogs for the layout (use placeholders if needed)
const cards = featuredBlogs.map(transformToInsightCard);
// The layout expects: first card (large) + two small cards
const largeCard = cards[0];
const smallCards = cards.slice(1, 3);
// If we don't have enough cards, we can still render with what we have
const hasLargeCard = !!largeCard;
const hasSmallCards = smallCards.length > 0;
return (
<section
className="py-24"
style={{
backgroundColor: '#F7F7FD',
paddingTop: '8.75rem',
paddingBottom: '8.75rem'
}}
>
<div className="max-w-7xl mx-auto section-margin-x">
<BrandedTag text="Leadership Insights" />
<div className="insights-container">
{/* Header */}
<div className="insights-header flex flex-col lg:flex-row lg:items-center lg:justify-between mb-16 gap-8 text-center lg:text-left">
<h2
className="insights-title text-3xl md:text-4xl lg:text-5xl font-bold leading-tight"
style={{ color: 'var(--color-brand-black)' }}
>
Leadership Insights & Ideas
</h2>
<ExploreAllButton />
</div>
{/* Main Grid Layout */}
<div className="insights-grid grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6 lg:gap-8">
{/* Large Card - Takes full height on left */}
{hasLargeCard && (
<div className="lg:row-span-2">
<LargeInsightCard card={largeCard} />
</div>
)}
{/* Small Cards - Stack on right */}
{hasSmallCards && (
<div className="flex flex-col gap-4 md:gap-6 lg:gap-8">
{smallCards.map((card:any, index:any) => (
<SmallInsightCard key={card.id || index} card={card} />
))}
</div>
)}
</div>
</div>
</div>
</section>
);
}