All checks were successful
CodeAnt AI Review - Stage 1 / codeant-review (push) Successful in 58s
363 lines
12 KiB
TypeScript
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>
|
|
);
|
|
} |