working left on homepage blog

This commit is contained in:
priyanshuvish
2026-03-27 12:43:34 +05:30
parent f0b4719282
commit b907162457
23 changed files with 2973 additions and 2030 deletions

View File

@@ -30,56 +30,10 @@ import { TestimonialsSection } from './TestimonialsSection';
import { Button } from './ui/button';
import { FullScreenLoader } from './FullScreenLoader';
// Leadership Orientations Data
const leadershipOrientations = [
{ name: 'Thinking', icon: Brain, description: 'Strategic and analytical mindset' },
{ name: 'Risk Appetite', icon: TrendingUp, description: 'Calculated risk-taking approach' },
{ name: 'Power', icon: Shield, description: 'Authority and influence dynamics' },
{ name: 'Interpersonal/Political', icon: Users, description: 'Relationship and network building' },
{ name: 'Ambition', icon: Target, description: 'Drive for achievement and growth' },
{ name: 'Trust', icon: Heart, description: 'Building confidence and reliability' },
{ name: 'Learning', icon: BookOpen, description: 'Continuous development mindset' },
{ name: 'Nurturance', icon: Heart, description: 'Supporting and developing others' },
{ name: 'Result/Closure', icon: CheckCircle, description: 'Focus on outcomes and completion' }
];
// Our Uniqueness Data
const uniquenessPoints = [
{
icon: Target,
title: 'Context & Strategy Alignment',
description: 'We align our work to the client\'s specific context & strategy'
},
{
icon: BookOpen,
title: 'Research-Anchored Approach',
description: 'Our work is anchored on research and work of scholars'
},
{
icon: Users,
title: 'Client-Specific Needs',
description: 'We blend this with the specific needs of the client'
},
{
icon: Puzzle,
title: 'Co-Creation Process',
description: 'We co-create the design with our clients'
}
];
// Benefits Data
const benefits = [
'We use proprietary exercises, custom written cases, curated films and proprietary tools',
'Facilitate insights on the connect between one\'s leadership orientations and their leadership abilities',
'We create learning at an individual level and at a group level',
'Our designs focus on application and practice',
'We bring in the connect of the learning to the Business contexts',
'We recommend that the Leadership intervention is designed for a period of 12-15 months with multiple touch points which can constitute a combination of classroom, fire side chats, one-on-one sessions, address by an expert, use of profilers, accessing online content on concepts and accomplished leaders\' experiences'
];
// Team Members Data with Full Profiles (Static - can be kept or also fetched from API if needed)
const staticTeamMembers = [
{
// Static detailed team member data for modal
const staticTeamMembersDetails = {
'Mr. K Ramkumar': {
name: 'Mr. K Ramkumar',
role: 'Managing Director',
image: Ramkumar,
@@ -102,12 +56,12 @@ He co-created the ICICI Manipal Academy for Banking and Insurance, which inducte
'Executive Director on ICICI Bank Board',
'Created ICICI Manipal Academy (12,000 leaders trained)',
'Founded ICICI Academy for Skills (35,000+ youth skilled)',
'Author of Leveraging Human Capital (McGraw Hill)'
'Author of "Leveraging Human Capital" (McGraw Hill)'
],
clientWork: 'Guided leadership development across ICICI Group and worked with Manipal Global Education to groom future banking leaders.',
boardRoles: 'Former Board Member of ICICI Prudential Life, ICICI Ventures; served on CSR and leadership committees.'
},
{
'Mr. R. Muralidharan': {
name: 'Mr. R. Muralidharan',
role: 'Practice Head Leadership Development',
image: Muralidharan,
@@ -133,7 +87,7 @@ At ICICI Bank, he was part of the founding team in 1994 and rose to become GM
clientWork: 'Worked with public and private banks, financial services firms, and non-profits on leadership and customer service transformation.',
boardRoles: 'Held board positions in business and non-profits; Vice-Chair, Customer Service Excellence Foundation.'
},
{
'Ms. Aparna Nair': {
name: 'Ms. Aparna Nair',
role: 'Practice Head Leadership Development',
image: Aparna,
@@ -159,7 +113,7 @@ She is certified in MBTI and OPQ, has applied Balanced Scorecard frameworks, and
clientWork: 'Worked with Godrej & Boyce, ICICI Prudential, Citi WAI, WNS, ThyssenKrupp, and others across pharma, BFSI, retail, auto, and private equity.',
boardRoles: 'Independent Woman Director; held leadership positions at ICICI Bank and Blue Dart-FedEx.'
},
{
'Mr. V. Swaminathan': {
name: 'Mr. V. Swaminathan',
role: 'Practice Head Leadership Development',
image: Swaminathan,
@@ -185,7 +139,7 @@ He stepped down as Joint President of Kotak Mahindra Bank in 2021 before joining
clientWork: 'Extensive work with BFSI organizations and leadership development initiatives at scale.',
boardRoles: 'Key member of Kotaks Management Committee; contributor to strategic boards within Kotak divisions.'
},
{
'Mr. Balaji Chandrakumar': {
name: 'Mr. Balaji Chandrakumar',
role: 'Practice Head Leadership Development',
image: Balaji,
@@ -211,7 +165,7 @@ Earlier, he worked in consulting with top Indian firms and began his HR journey
clientWork: 'Worked with leading companies in telecom, food, and HR consulting sectors across India and SE Asia.',
boardRoles: 'Advisor in HR capability building across organizations.'
},
{
'Mr. Ramesh Padmanabhan': {
name: 'Mr. Ramesh Padmanabhan',
role: 'Practice Head Leadership Development',
image: Ramesh,
@@ -237,7 +191,7 @@ He has consistently worked on capability building and leadership development alo
clientWork: 'Significant work in BFSI sector; tailored leadership development for managers and executives.',
boardRoles: 'Served on MANCOM and strategic boards in ICICI, Dhanlaxmi, and ADCB India.'
},
{
'Ms. Diju S': {
name: 'Ms. Diju S',
role: 'Practice Head Leadership Development',
image: Diju,
@@ -263,35 +217,19 @@ After a career break, she joined KLC as Practice Head. She now co-creates leader
clientWork: 'Worked with BFSI clients and KLC partners to create custom leadership programs.',
boardRoles: 'Active in leadership forums and project management at KLC.'
}
];
};
// Loading Skeleton Component
const AboutUsSkeleton = () => (
<div className="animate-pulse">
{/* Hero Section Skeleton */}
<section className="relative min-h-[85vh] flex flex-col bg-gray-200">
<div className="absolute inset-0 bg-gray-300"></div>
<div className="relative z-10 flex-1 flex items-center">
<div className="w-full section-margin-x">
<div className="max-w-6xl">
<div className="h-16 bg-gray-400 rounded-lg w-3/4 mb-8"></div>
<div className="h-24 bg-gray-400 rounded-lg w-2/3 mb-8"></div>
<div className="h-12 bg-gray-400 rounded-lg w-48"></div>
</div>
</div>
</div>
</section>
{/* Add more skeleton sections as needed */}
<div className="py-24 text-center text-gray-500">Loading...</div>
</div>
);
// Helper function to get member details by name
const getMemberDetails = (nameRole: string) => {
// Extract the name from "Name - Role" format
const name = nameRole.split(' - ')[0];
return staticTeamMembersDetails[name as keyof typeof staticTeamMembersDetails] || null;
};
export function AboutUs() {
const [isVisible, setIsVisible] = useState(false);
const [expandedValue, setExpandedValue] = useState<string | null>('context');
const [selectedMember, setSelectedMember] = useState<typeof staticTeamMembers[0] | null>(null);
const [selectedMember, setSelectedMember] = useState<any>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
// Fetch About Us data from API
@@ -304,13 +242,13 @@ export function AboutUs() {
id: testimonial.id || index,
name: testimonial.name || "Anonymous",
role: testimonial.designation || "Client",
company: undefined, // Company not provided in API response
avatar: testimonial.profile_photo_url || undefined, // If you have profile photo in API
company: undefined,
avatar: testimonial.profile_photo_url || undefined,
image: testimonial.profile_photo_url || undefined,
quote: testimonial.content || "",
rating: 5, // Default rating since API doesn't provide rating
rating: 5,
isVideo: !!testimonial.video_url,
videoThumbnail: testimonial.video_thumbnail_url || testimonial.profile_photo_url, // If you have thumbnail
videoThumbnail: testimonial.video_thumbnail_url || testimonial.profile_photo_url,
videoUrl: testimonial.video_url || undefined
}));
};
@@ -318,8 +256,29 @@ export function AboutUs() {
// Transform the testimonials
const testimonialsData = transformTestimonials(aboutUsData?.testimonials || []);
const handleMemberClick = (member: typeof staticTeamMembers[0]) => {
setSelectedMember(member);
// Get team members from API
const apiTeamMembers = aboutUsData?.our_team || [];
const handleMemberClick = (member: any) => {
// Get detailed static data for the clicked member
const memberDetails = getMemberDetails(member.name_role);
if (memberDetails) {
setSelectedMember(memberDetails);
} else {
// Fallback to API data if no static details found
setSelectedMember({
name: member.name_role.split(' - ')[0],
role: member.name_role.split(' - ')[1] || 'Team Member',
image: member.photo_url,
experience: member.bio,
fullBio: member.bio,
expertise: [],
education: '',
achievements: [],
clientWork: '',
boardRoles: ''
});
}
setIsModalOpen(true);
};
@@ -376,8 +335,6 @@ export function AboutUs() {
};
}, []);
// Show loading skeleton while fetching data
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
@@ -386,7 +343,6 @@ export function AboutUs() {
);
}
// Show error state if API call fails
if (isError) {
return (
<div className="min-h-screen flex items-center justify-center">
@@ -480,7 +436,6 @@ export function AboutUs() {
<h2 className="text-h2 mb-8">{aboutUsData?.how_we_work_title || "How We Work"}</h2>
</motion.div>
{/* Four Key Points Grid - Using API data if available, otherwise fallback to static */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 lg:gap-12">
{(aboutUsData?.how_we_work && aboutUsData.how_we_work.length > 0) ? (
aboutUsData.how_we_work.map((item, index) => (
@@ -512,7 +467,6 @@ export function AboutUs() {
</motion.div>
))
) : (
// Fallback to static data if API data is not available
<>
<motion.div
initial={{ opacity: 0, y: 30 }}
@@ -622,9 +576,7 @@ export function AboutUs() {
transition={{ duration: 0.6 }}
viewport={{ once: true }}
>
{/* Split Layout - Left: Eyebrow Text, Right: Main Heading */}
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-12 mb-16">
{/* Left Side - Eyebrow Text */}
<div className="lg:col-span-1">
<div className="branded-tag-system">
<div className="dot"></div>
@@ -632,7 +584,6 @@ export function AboutUs() {
</div>
</div>
{/* Right Side - Main Heading */}
<div className="lg:col-span-4">
<h2 className="text-h1 leading-tight" style={{
fontSize: 'clamp(2.5rem, 5vw, 4rem)',
@@ -644,7 +595,6 @@ export function AboutUs() {
</div>
</div>
{/* Updated Statistics Grid - Dynamic from API */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8 lg:gap-12 pt-12 border-t border-gray-200">
{(aboutUsData?.stat_section && aboutUsData.stat_section.length > 0) ? (
aboutUsData.stat_section.map((stat, index) => (
@@ -670,7 +620,6 @@ export function AboutUs() {
</motion.div>
))
) : (
// Fallback to static statistics if API data is not available
<>
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -759,11 +708,10 @@ export function AboutUs() {
</div>
</section>
{/* Section 4: Our Team - Dynamic from API */}
{/* Section 4: Our Team - Dynamic from API (outer grid from API, modal from static) */}
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
<div className="section-margin-x">
<div className="max-w-6xl mx-auto">
{/* Centered Header Section */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@@ -780,52 +728,63 @@ export function AboutUs() {
</div>
</motion.div>
{/* Team Members Grid - Using static team members with full profiles */}
{/* Team Members Grid - Using API data for outer display */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 lg:gap-12">
{staticTeamMembers.map((member, index) => (
<motion.div
key={member.name}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
viewport={{ once: true }}
className="text-left cursor-pointer"
onClick={() => handleMemberClick(member)}
>
<div className="relative mb-6 group">
<div className="aspect-square rounded-2xl overflow-hidden bg-gray-100 shadow-lg group-hover:shadow-xl transition-all duration-300">
<img
src={member.image}
alt={member.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
</div>
{apiTeamMembers.map((member, index) => {
const name = member.name_role.split(' - ')[0];
const role = member.name_role.split(' - ')[1] || 'Team Member';
{/* Hover Overlay */}
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-all duration-300 rounded-2xl flex items-center justify-center">
<div className="opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div
className="px-4 py-2 rounded-lg text-white text-small"
style={{
backgroundColor: '#04045B',
fontFamily: 'var(--font-family-base)',
fontWeight: '500'
return (
<motion.div
key={member.id || index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
viewport={{ once: true }}
className="text-left cursor-pointer"
onClick={() => handleMemberClick(member)}
>
<div className="relative mb-6 group">
<div className="aspect-square rounded-2xl overflow-hidden bg-gray-100 shadow-lg group-hover:shadow-xl transition-all duration-300">
<img
src={member.photo_url}
alt={member.alt_text || name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
onError={(e) => {
(e.target as HTMLImageElement).src = 'https://ui-avatars.com/api/?name=' + encodeURIComponent(name) + '&background=04045B&color=fff&size=200';
}}
>
View Profile
/>
</div>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-all duration-300 rounded-2xl flex items-center justify-center">
<div className="opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div
className="px-4 py-2 rounded-lg text-white text-small"
style={{
backgroundColor: '#04045B',
fontFamily: 'var(--font-family-base)',
fontWeight: '500'
}}
>
View Profile
</div>
</div>
</div>
</div>
</div>
<div className="space-y-2">
<h3 className="text-h4 text-black group-hover:text-primary transition-colors duration-300">{member.name}</h3>
<p className="text-body text-muted leading-relaxed">
{member.role}
</p>
</div>
</motion.div>
))}
<div className="space-y-2">
<h3 className="text-h4 text-black group-hover:text-primary transition-colors duration-300">{name}</h3>
<p className="text-body text-muted leading-relaxed font-medium">
{role}
</p>
{/* Bio Section */}
<p className="text-small text-muted leading-relaxed mt-2 line-clamp-3">
{member.bio || "No bio available"}
</p>
</div>
</motion.div>
);
})}
</div>
</div>
</div>
@@ -849,9 +808,7 @@ export function AboutUs() {
)}
</motion.div>
{/* Vertical Timeline Container */}
<div className="relative max-w-6xl mx-auto">
{/* Vertical Line Background - Gray */}
<div
className="absolute left-4 top-0 w-0.5 bg-gray-300"
style={{
@@ -860,7 +817,6 @@ export function AboutUs() {
}}
></div>
{/* Vertical Line Fill - Blue - Animated on Scroll */}
<div
id="timeline-fill-line"
className="absolute left-4 top-0 w-0.5 transition-all duration-1000 ease-out"
@@ -872,13 +828,11 @@ export function AboutUs() {
}}
></div>
{/* Map through phases from API - Create a copy before sorting */}
{[...(aboutUsData.methodology.phases || [])]
.sort((a, b) => (a.display_order || 0) - (b.display_order || 0))
.map((phase, phaseIndex) => (
<div key={phase.id || phaseIndex} className="relative pb-20">
<div className="grid lg:grid-cols-12 gap-8 lg:gap-12 pl-12">
{/* Phase dot positioned absolutely */}
<div
className="absolute left-3 top-1 w-2.5 h-2.5 rounded-full bg-white border-2 z-10"
style={{
@@ -886,7 +840,6 @@ export function AboutUs() {
}}
></div>
{/* Column 1: Phase Label */}
<div className="lg:col-span-2">
<div className="branded-tag-system">
<div className="dot"></div>
@@ -894,7 +847,6 @@ export function AboutUs() {
</div>
</div>
{/* Column 2: Main Heading */}
<div className="lg:col-span-3">
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -906,7 +858,6 @@ export function AboutUs() {
</motion.div>
</div>
{/* Column 3: Content - Description and Bullet Points */}
<div className="lg:col-span-7">
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -944,10 +895,8 @@ export function AboutUs() {
</div>
))}
{/* Our Philosophy - Dynamic from API */}
{aboutUsData?.philosophy && (
<div className="relative pb-20">
{/* Phase dot positioned absolutely */}
<div
className="absolute left-3 top-1 w-2.5 h-2.5 rounded-full bg-white border-2 z-10"
style={{
@@ -969,19 +918,15 @@ export function AboutUs() {
}}
>
<div className="flex flex-col lg:flex-row gap-8 lg:gap-12">
{/* Left Section: Heading */}
<div className="lg:w-1/3">
<h3 className="text-h3 text-white mb-6 lg:mb-0">{aboutUsData.philosophy.title || "Our Philosophy"}</h3>
</div>
{/* Right Section: Content */}
<div className="lg:w-2/3">
{/* Philosophy Description */}
<p className="text-body-white mb-8 opacity-90">
{aboutUsData.philosophy.description}
</p>
{/* Philosophy Points */}
{aboutUsData.philosophy.points && aboutUsData.philosophy.points.length > 0 && (
<div className="flex flex-col gap-4">
{aboutUsData.philosophy.points.map((point, pointIndex) => (
@@ -1041,14 +986,10 @@ export function AboutUs() {
tagText="Client Success Stories"
/>
{/* CTA Banner Section */}
<CTABannerSection />
{/* Team Member Modal */}
<TeamMemberModal
member={selectedMember}
isOpen={isModalOpen}
onClose={handleCloseModal}
{/* CTA Banner Section - Dynamic from API */}
<CTABannerSection
ctaSection={aboutUsData?.cta_section}
isLoading={isLoading}
/>
</div>
);

View File

@@ -5,21 +5,20 @@ import { PrimaryCTAButton } from "./PrimaryCTAButton";
import { navigateTo } from "./Router";
interface CTABannerSectionProps {
ctaBands?: Array<{
ctaSection?: {
id: string;
background_image_url: string;
background_image_alt_text: string;
text: string;
cta_text: string;
cta_destination: string;
}>;
description: string;
landing_page_type: string;
service_type: string | null;
};
isLoading?: boolean;
}
export function CTABannerSection({ ctaBands = [], isLoading }: CTABannerSectionProps) {
// Get the first CTA band or use default values
const ctaBand = ctaBands && ctaBands.length > 0 ? ctaBands[0] : null;
export function CTABannerSection({ ctaSection, isLoading }: CTABannerSectionProps) {
if (isLoading) {
return (
<section className="relative h-[700px] overflow-hidden bg-gray-100 animate-pulse">
@@ -31,8 +30,8 @@ export function CTABannerSection({ ctaBands = [], isLoading }: CTABannerSectionP
);
}
// If no CTA band is available, don't render anything
if (!ctaBand) {
// If no CTA section data is available, don't render anything
if (!ctaSection) {
return null;
}
@@ -41,8 +40,8 @@ export function CTABannerSection({ ctaBands = [], isLoading }: CTABannerSectionP
{/* Background Image */}
<div className="absolute inset-0">
<ImageWithFallback
src={ctaBand.background_image_url || "https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2940&q=80"}
alt={ctaBand.background_image_alt_text || "Professional team collaborating in modern office"}
src={ctaSection.background_image_url || "https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2940&q=80"}
alt="Background image for call to action section"
className="w-full h-full object-cover"
/>
@@ -65,11 +64,11 @@ export function CTABannerSection({ ctaBands = [], isLoading }: CTABannerSectionP
{/* Branded Tag */}
<BrandedTag text="Next Steps" variant="white" />
{/* Main Headline - Use API text or fallback */}
{/* Main Headline */}
<h2
className="text-h2-white mb-8"
className="text-h2-white mb-4"
>
{ctaBand.text || "Ready to transform your leadership?"}
{ctaSection.text || "Ready to transform your leadership?"}
<span
className="italic"
style={{ color: 'var(--color-brand-accent)' }}
@@ -79,20 +78,22 @@ export function CTABannerSection({ ctaBands = [], isLoading }: CTABannerSectionP
to start your development journey now.
</h2>
{/* Description */}
{ctaSection.description && (
<p
className="text-body-white mb-6 opacity-90"
>
{ctaSection.description}
</p>
)}
{/* CTA Button */}
<PrimaryCTAButton
text={ctaBand.cta_text || "Schedule a Consultation"}
onClick={() => navigateTo(ctaBand.cta_destination || '/contact?topic=consulting')}
text={ctaSection.cta_text || "Schedule a Consultation"}
onClick={() => navigateTo(ctaSection.cta_destination || '/contact?topic=consulting')}
ariaLabel="Schedule a consultation with our leadership experts"
className="cta-banner-yellow"
/>
{/* Supporting Text */}
<p
className="text-body-white mt-6 opacity-90"
>
Connect with our leadership experts to discuss your organization's specific development needs.
</p>
</div>
</div>
</section>

View File

@@ -22,7 +22,7 @@ export interface Course {
level: string;
format: string;
rating: number;
participants: string;
reviews: string;
category: string;
description: string;
price: string;
@@ -147,7 +147,7 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
color: 'var(--color-gray-muted)',
fontWeight: '500'
}}>
{course.participants}
{course.reviews}
</span>
</div>
</div>
@@ -265,4 +265,4 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
</div>
</motion.div>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,14 @@ import { Button } from './ui/button';
import { Card } from './ui/card';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { useGetCoursesQuery, Course, GetCoursesParams, useGetCourseCategoriesQuery, CourseCategory } from '../redux/services/courseApi';
import {
courseApi,
useGetCoursesQuery,
Course,
GetCoursesParams,
useGetCourseCategoriesQuery,
CourseCategory
} from '../redux/services/courseApi';
import { useDebounce } from '../redux/hooks/useDebounce';
// Helper function to parse rupee price from string (keep as is)
@@ -50,6 +57,7 @@ export function LearningOnline() {
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [currentPage, setCurrentPage] = useState(1);
const coursesPerPage = 9;
const prefetchCourseById = courseApi.usePrefetch('getcoursebyid');
// Debounced search term to avoid too many API calls
const debouncedSearchTerm = useDebounce(searchTerm, 500);
@@ -260,12 +268,12 @@ export function LearningOnline() {
level: 'Intermediate',
format: course.retail_type === 'public' ? 'Cohort-based' : 'Self-paced',
rating: course.avg_rating || 4.5,
participants: `${Math.floor(Math.random() * 5000) + 100}+`,
reviews: `${course.total_reviews || 0} review${(course.total_reviews || 0) === 1 ? '' : 's'}`,
category: course.course_category_name || 'General',
categoryId: course.course_category_xid || '',
description: course.course_desc || `Master ${course.course_name} with our comprehensive program.`,
price: formatPrice(course.price),
originalPrice: formatPrice(course.price * 1.25),
price: formatPrice(course.best_value || 0),
originalPrice: formatPrice(course.price || 0),
course_status: course.course_status
}));
}, [coursesData]);
@@ -321,6 +329,11 @@ export function LearningOnline() {
setRecentlyAddedItem(null);
};
const handleCourseClick = useCallback((courseId: string) => {
prefetchCourseById(courseId, { force: true });
navigateTo(`/course/${courseId}`);
}, [prefetchCourseById]);
// Show loading state
if (coursesLoading || categoriesLoading) {
return (
@@ -595,6 +608,7 @@ export function LearningOnline() {
<CourseCard
course={course}
className="h-[560px] flex flex-col w-full"
onClick={() => handleCourseClick(course.id)}
onAddToCart={handleAddToCart}
/>
</div>
@@ -609,7 +623,7 @@ export function LearningOnline() {
<Card
key={course.id}
className="overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer group"
onClick={() => navigateTo(`/course/${course.id}`)}
onClick={() => handleCourseClick(course.id)}
>
<div className="flex flex-col md:flex-row">
<div className="md:w-80 flex-shrink-0">
@@ -665,7 +679,7 @@ export function LearningOnline() {
</div>
<div className="flex items-center gap-1">
<Users className="w-4 h-4 text-gray-400" />
<span className="text-small text-gray-600">{course.participants}</span>
<span className="text-small text-gray-600">{course.reviews}</span>
</div>
</div>
</div>
@@ -761,4 +775,4 @@ export function LearningOnline() {
/>
</div>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ import { useState, useRef, useEffect } from "react";
import { BrandedTag } from "./about/BrandedTag";
interface Testimonial {
id?: number;
id?: number | string;
name: string;
role: string;
company?: string;
@@ -16,8 +16,13 @@ interface Testimonial {
isVideo?: boolean;
videoThumbnail?: string;
videoUrl?: string;
designation?: string;
content?: string;
video_url?: string;
profile_xid?: string;
}
// Default testimonials as fallback
const defaultTestimonialsData: Testimonial[] = [
{
id: 1,
@@ -52,38 +57,6 @@ const defaultTestimonialsData: Testimonial[] = [
isVideo: true,
videoThumbnail: "https://images.unsplash.com/photo-1560472355-109703aa3edc?w=600&h=300&fit=crop",
videoUrl: "https://example.com/testimonial-video-2.mp4"
},
{
id: 4,
name: "David Thompson",
role: "Senior Manager",
company: "Enterprise Solutions",
avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face",
quote: "The personalized coaching and development programs have been game-changing for our organization's leadership pipeline and succession planning initiatives.",
rating: 5,
isVideo: false
},
{
id: 5,
name: "Lisa Wang",
role: "Product Manager",
company: "Digital Ventures",
avatar: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=400&fit=crop&crop=face",
quote: "KLC has transformed how we think about leadership in the digital age. The insights and strategies have been invaluable for our team's growth and innovation culture.",
rating: 5,
isVideo: true,
videoThumbnail: "https://images.unsplash.com/photo-1559136555-9303baea8ebd?w=600&h=300&fit=crop",
videoUrl: "https://example.com/testimonial-video-3.mp4"
},
{
id: 6,
name: "Robert Kim",
role: "Regional Director",
company: "Global Corp",
avatar: "https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?w=400&h=400&fit=crop&crop=face",
quote: "The leadership development framework provided by KLC has been instrumental in building a more cohesive and effective leadership team across our regions.",
rating: 4,
isVideo: false
}
];
@@ -138,12 +111,18 @@ function VideoModal({ isOpen, onClose, videoUrl }: {
);
}
// Individual Testimonial Card - Updated with Landing Page Design Standards
// Individual Testimonial Card
function TestimonialCard({ testimonial, onPlayVideo }: {
testimonial: Testimonial;
onPlayVideo: (videoUrl: string) => void;
}) {
const avatarSrc = testimonial.avatar || testimonial.image;
const isVideo = testimonial.isVideo || !!testimonial.video_url;
const videoUrl = testimonial.videoUrl || testimonial.video_url || "";
const role = testimonial.role || testimonial.designation || "";
const quote = testimonial.quote || testimonial.content || "";
const name = testimonial.name || "";
const rating = testimonial.rating || 5;
return (
<motion.div
@@ -162,14 +141,14 @@ function TestimonialCard({ testimonial, onPlayVideo }: {
}}
>
{/* Video Testimonials */}
{testimonial.isVideo ? (
{isVideo ? (
<div
className="relative h-full cursor-pointer overflow-hidden group rounded-xl"
onClick={() => onPlayVideo(testimonial.videoUrl || "")}
onClick={() => onPlayVideo(videoUrl)}
>
<ImageWithFallback
src={testimonial.videoThumbnail || avatarSrc || ""}
alt={`${testimonial.name} video testimonial`}
src={testimonial.videoThumbnail || avatarSrc || "https://images.unsplash.com/photo-1552664730-d307ca884978?w=600&h=300&fit=crop"}
alt={`${name} video testimonial`}
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
@@ -203,16 +182,16 @@ function TestimonialCard({ testimonial, onPlayVideo }: {
<div className="w-10 h-10 rounded-full overflow-hidden bg-white shadow-lg flex-shrink-0">
<ImageWithFallback
src={avatarSrc || ""}
alt={testimonial.name}
alt={name}
className="w-full h-full object-cover"
/>
</div>
<div className="min-w-0 flex-1">
<h4 className="font-semibold text-white mb-1 text-sm">
{testimonial.name}
{name}
</h4>
<p className="text-xs text-white/80 truncate">
{testimonial.role}
{role}
{testimonial.company && `${testimonial.company}`}
</p>
</div>
@@ -223,7 +202,7 @@ function TestimonialCard({ testimonial, onPlayVideo }: {
<Star
key={star}
size={14}
className={star <= testimonial.rating ? 'fill-current text-yellow-400' : 'text-white/40'}
className={star <= rating ? 'fill-current text-yellow-400' : 'text-white/40'}
/>
))}
</div>
@@ -239,16 +218,16 @@ function TestimonialCard({ testimonial, onPlayVideo }: {
<div className="w-12 h-12 rounded-full overflow-hidden bg-gray-100 flex-shrink-0">
<ImageWithFallback
src={avatarSrc || ""}
alt={testimonial.name}
alt={name}
className="w-full h-full object-cover"
/>
</div>
<div className="min-w-0">
<h4 className="font-semibold text-black mb-1 text-sm">
{testimonial.name}
{name}
</h4>
<p className="text-xs text-gray-600">
{testimonial.role}
{role}
</p>
{testimonial.company && (
<p className="text-xs text-gray-500 font-medium">
@@ -264,7 +243,7 @@ function TestimonialCard({ testimonial, onPlayVideo }: {
<Star
key={star}
size={14}
className={star <= testimonial.rating ? 'fill-current text-yellow-400' : 'text-gray-300'}
className={star <= rating ? 'fill-current text-yellow-400' : 'text-gray-300'}
/>
))}
</div>
@@ -277,7 +256,7 @@ function TestimonialCard({ testimonial, onPlayVideo }: {
"
</span>
<span className="relative z-10">
{testimonial.quote}
{quote}
</span>
</div>
</blockquote>

View File

@@ -44,7 +44,7 @@ import { navigateTo } from './Router';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { BrandedTag } from './about/BrandedTag';
import { PrimaryCTAButton } from './PrimaryCTAButton';
import { toast } from 'sonner@2.0.3';
import { toast } from 'sonner';
import { getWebinarBySlug, sharedWebinarsData, type WebinarData } from '../data/webinarsData';
interface WebinarDetailProps {

View File

@@ -1,151 +1,100 @@
import React, { useState, useRef, useEffect } from 'react';
import { Button } from './ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Slider } from './ui/slider';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { PrimaryCTAButton } from './PrimaryCTAButton';
import { navigateTo } from './Router';
import { sharedWebinarsData, type WebinarData } from '../data/webinarsData';
import { WebcastCTABanner } from './WebcastCTABanner';
import {
Search,
Calendar,
Clock,
Users,
Play,
ArrowRight,
ChevronLeft,
ChevronRight,
Clock,
Eye,
Filter,
Grid,
List,
SortAsc,
Eye,
Play,
Search,
Star,
ChevronLeft,
ChevronRight,
Users,
X
} from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { useWebinarListQuery, type WebinarItem } from '../redux/services/webinarApi';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { FullScreenLoader } from './FullScreenLoader';
import { navigateTo } from './Router';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { Card, CardContent } from './ui/card';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Slider } from './ui/slider';
import { WebcastCTABanner } from './WebcastCTABanner';
// Status options with proper mapping to API values
const statusOptions = [
{ value: 'scheduled', label: '📅 Scheduled', color: 'bg-blue-100 text-blue-800 border-blue-200' },
{ value: 'live', label: '🔴 Live', color: 'bg-red-100 text-red-800 border-red-200' },
{ value: 'ended', label: '✅ Ended', color: 'bg-gray-100 text-gray-800 border-gray-200' },
{ value: 'cancelled', label: '❌ Cancelled', color: 'bg-red-50 text-red-600 border-red-200' }
];
const sortOptions = [
{ value: 'most_popular', label: 'Most Popular' },
{ value: 'newest', label: 'Newest First' },
{ value: 'oldest', label: 'Oldest First' },
{ value: 'title', label: 'Title A-Z' },
{ value: 'duration', label: 'Duration' }
];
// Static tags for all webinars
const staticTags = ['Leadership', 'Executive Development', 'Strategy', 'Innovation', 'Change Management', 'Business Growth', 'Team Building', 'Digital Transformation'];
export function Webinars() {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All Categories');
const [selectedFormat, setSelectedFormat] = useState('All Formats');
const [selectedLevel, setSelectedLevel] = useState('All Levels');
// Updated state for multi-select status pills
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]);
// Updated state for duration slider (min, max in minutes)
const [durationRange, setDurationRange] = useState([0, 120]);
// Attendee range slider state
const [attendeeRange, setAttendeeRange] = useState([0, 5000]);
const [sortBy, setSortBy] = useState('Most Popular');
const [sortBy, setSortBy] = useState('most_popular');
const [viewType, setViewType] = useState<'grid' | 'list'>('grid');
const [currentPage, setCurrentPage] = useState(1);
const webinarsPerPage = 6;
const containerRef = useRef<HTMLDivElement>(null);
// Use shared webinars data instead of local mock data
const webinars = sharedWebinarsData;
// Get unique values for filters from shared data
const categories = ['All Categories', ...Array.from(new Set(webinars.map(webinar => webinar.category)))];
const formats = ['All Formats', ...Array.from(new Set(webinars.map(webinar => webinar.format)))];
const levels = ['All Levels', ...Array.from(new Set(webinars.map(webinar => webinar.level)))];
// Status options for pills - updated to match shared data structure
const statusOptions = [
{ value: 'upcoming', label: '📅 Upcoming', color: 'bg-blue-100 text-blue-800 border-blue-200' },
{ value: 'live', label: '🔴 Live', color: 'bg-red-100 text-red-800 border-red-200' },
{ value: 'recorded', label: '▶️ Recorded', color: 'bg-green-100 text-green-800 border-green-200' },
{ value: 'featured', label: '⭐ Featured', color: 'bg-yellow-100 text-yellow-800 border-yellow-200' }
];
const sortOptions = [
{ value: 'Most Popular', label: 'Most Popular' },
{ value: 'newest', label: 'Newest First' },
{ value: 'oldest', label: 'Oldest First' },
{ value: 'title', label: 'Title A-Z' },
{ value: 'duration', label: 'Duration' }
];
// Helper function to convert attendees string to number
const parseAttendees = (attendeesStr: string): number => {
const numStr = attendeesStr.replace(/[^\d]/g, '');
return parseInt(numStr) || 0;
};
// Helper function to convert duration string to minutes
const parseDuration = (durationStr: string): number => {
const numStr = durationStr.replace(/[^\d]/g, '');
return parseInt(numStr) || 0;
};
// Filter and sort webinars
const filteredWebinars = webinars.filter(webinar => {
const matchesSearch = webinar.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
webinar.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
webinar.presenter.toLowerCase().includes(searchTerm.toLowerCase()) ||
webinar.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesCategory = selectedCategory === 'All Categories' || webinar.category === selectedCategory;
const matchesFormat = selectedFormat === 'All Formats' || webinar.format === selectedFormat;
const matchesLevel = selectedLevel === 'All Levels' || webinar.level === selectedLevel;
const matchesStatus = selectedStatuses.length === 0 ||
selectedStatuses.some(status => {
if (status === 'featured') return webinar.featured;
return webinar.status === status;
});
const durationMinutes = parseDuration(webinar.duration);
const matchesDuration = durationMinutes >= durationRange[0] && durationMinutes <= durationRange[1];
const attendeeCount = parseAttendees(webinar.attendees);
const matchesAttendees = attendeeCount >= attendeeRange[0] && attendeeCount <= attendeeRange[1];
return matchesSearch && matchesCategory && matchesFormat && matchesLevel && matchesStatus && matchesDuration && matchesAttendees;
}).sort((a, b) => {
switch (sortBy) {
case 'Most Popular':
// Add logic for "Most Popular" - you might want to use views, attendees, or featured status
return (b.featured ? 1 : 0) - (a.featured ? 1 : 0) ||
parseAttendees(b.attendees) - parseAttendees(a.attendees);
case 'newest':
return new Date(b.date).getTime() - new Date(a.date).getTime();
case 'oldest':
return new Date(a.date).getTime() - new Date(b.date).getTime();
case 'title':
return a.title.localeCompare(b.title);
case 'duration':
return parseDuration(b.duration) - parseDuration(a.duration);
default:
return 0;
}
// Fetch webinars from API
const {
data: webinarResponse,
isLoading,
isError,
} = useWebinarListQuery({
limit: 100,
offset: 0,
search: searchTerm || undefined,
status: selectedStatuses.length > 0 ? selectedStatuses : undefined,
minDuration: durationRange[0] > 0 ? durationRange[0] : undefined,
maxDuration: durationRange[1] < 120 ? durationRange[1] : undefined,
minAttendees: attendeeRange[0] > 0 ? attendeeRange[0] : undefined,
maxAttendees: attendeeRange[1] < 5000 ? attendeeRange[1] : undefined,
sortBy: sortBy as any,
});
// Statistics
const stats = {
total: webinars.length,
upcoming: webinars.filter(w => w.status === 'upcoming').length,
live: webinars.filter(w => w.status === 'live').length,
recorded: webinars.filter(w => w.status === 'recorded').length,
featured: webinars.filter(w => w.featured).length,
categories: new Set(webinars.map(w => w.category)).size
const webinars = webinarResponse?.data?.items || [];
// Get random tags for each webinar (3 random tags from staticTags)
const getRandomTags = (seed: string) => {
// Use the webinar ID as seed to get consistent tags for each webinar
const hash = seed.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
const shuffled = [...staticTags];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled.slice(0, 3);
};
// Paginate results
const totalPages = Math.ceil(filteredWebinars.length / webinarsPerPage);
const currentWebinars = filteredWebinars.slice((currentPage - 1) * webinarsPerPage, currentPage * webinarsPerPage);
console.log('Filtered webinars:', filteredWebinars.length);
console.log('Total pages:', totalPages);
console.log('Current page:', currentPage);
console.log('Current webinars count:', currentWebinars.length);
// Get unique categories from API data
const categories = [
'All Categories',
...Array.from(new Set(webinars.map(webinar => webinar.session_title?.split(' ')[0] || 'General')))
];
// Helper functions
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
@@ -154,26 +103,81 @@ console.log('Current webinars count:', currentWebinars.length);
});
};
const formatDuration = (minutes: number) => {
if (minutes >= 60) {
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
}
return `${minutes}min`;
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'live':
return <Badge className="bg-red-600 text-white animate-pulse">LIVE NOW</Badge>;
case 'scheduled':
return <Badge className="bg-blue-600 text-white">SCHEDULED</Badge>;
case 'ended':
return <Badge className="bg-gray-600 text-white">ENDED</Badge>;
case 'cancelled':
return <Badge className="bg-red-400 text-white">CANCELLED</Badge>;
default:
return null;
}
};
const getActionText = (status: string) => {
switch (status) {
case 'live':
return 'Join Now';
case 'scheduled':
return 'Register';
case 'ended':
return 'Watch Recording';
case 'cancelled':
return 'Cancelled';
default:
return 'Learn More';
}
};
// Statistics
const stats = {
total: webinars.length,
scheduled: webinars.filter(w => w.webinar_status === 'scheduled').length,
live: webinars.filter(w => w.webinar_status === 'live').length,
ended: webinars.filter(w => w.webinar_status === 'ended').length,
cancelled: webinars.filter(w => w.webinar_status === 'cancelled').length,
categories: categories.length - 1
};
// Filter webinars
const filteredWebinars = webinars.filter(webinar => {
const matchesCategory = selectedCategory === 'All Categories' ||
(webinar.session_title && webinar.session_title.toLowerCase().includes(selectedCategory.toLowerCase()));
return matchesCategory;
});
// Paginate results
const totalPages = Math.ceil(filteredWebinars.length / webinarsPerPage);
const currentWebinars = filteredWebinars.slice((currentPage - 1) * webinarsPerPage, currentPage * webinarsPerPage);
const clearAllFilters = () => {
setSearchTerm('');
setSelectedCategory('All Categories');
setSelectedFormat('All Formats');
setSelectedLevel('All Levels');
setSelectedStatuses([]);
setDurationRange([0, 120]);
setAttendeeRange([0, 5000]);
setSortBy('Most Popular');
setSortBy('most_popular');
};
const hasActiveFilters = searchTerm ||
selectedCategory !== 'All Categories' ||
selectedFormat !== 'All Formats' ||
selectedLevel !== 'All Levels' ||
selectedStatuses.length > 0 ||
durationRange[0] !== 0 || durationRange[1] !== 120 ||
attendeeRange[0] !== 0 || attendeeRange[1] !== 5000;
// Status pill toggle function
const toggleStatus = (status: string) => {
setSelectedStatuses(prev =>
prev.includes(status)
@@ -182,95 +186,78 @@ console.log('Current webinars count:', currentWebinars.length);
);
};
// Reset to page 1 when filters change
useEffect(() => {
setCurrentPage(1);
}, [searchTerm, selectedCategory, selectedFormat, selectedLevel, selectedStatuses, durationRange, attendeeRange, sortBy]);
}, [searchTerm, selectedCategory, selectedStatuses, durationRange, attendeeRange, sortBy]);
// Updated WebinarCard component that navigates to consistent route
const WebinarCard = ({ webinar }: { webinar: WebinarData }) => {
const WebinarCard = ({ webinar }: { webinar: WebinarItem }) => {
const handleCardClick = () => {
// Navigate to consistent webinar detail route
navigateTo(`/webinar/${webinar.slug}`);
};
const getStatusBadge = () => {
switch (webinar.status) {
case 'live':
return <Badge className="bg-red-600 text-white animate-pulse">LIVE</Badge>;
case 'upcoming':
return <Badge className="bg-blue-600 text-white">UPCOMING</Badge>;
case 'recorded':
return <Badge className="bg-green-600 text-white">RECORDED</Badge>;
default:
return null;
if (webinar.webinar_status !== 'cancelled') {
navigateTo(`/webinar/${webinar.id}`);
}
};
const getActionText = () => {
switch (webinar.status) {
case 'live':
return 'Join Now';
case 'upcoming':
return 'Register';
case 'recorded':
return 'Watch Recording';
default:
return 'Learn More';
}
};
const isCancelled = webinar.webinar_status === 'cancelled';
const webinarTags = getRandomTags(webinar.id);
if (viewType === 'list') {
return (
<Card
className="mb-4 cursor-pointer transition-all duration-300 hover:shadow-lg hover:transform hover:-translate-y-1"
className={`mb-4 cursor-pointer transition-all duration-300 hover:shadow-lg hover:transform hover:-translate-y-1 ${isCancelled ? 'opacity-75' : ''}`}
onClick={handleCardClick}
style={isCancelled ? { cursor: 'not-allowed' } : {}}
>
<CardContent className="p-6">
<div className="flex gap-6">
{/* Thumbnail */}
<div className="flex-shrink-0 w-32 h-24 rounded-lg overflow-hidden">
<ImageWithFallback
src={webinar.thumbnail}
alt={webinar.title}
className="w-full h-full object-cover"
/>
<div className="flex-shrink-0 w-32 h-24 rounded-lg overflow-hidden bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center">
<Play className="w-8 h-8 text-gray-400" />
</div>
{/* Content */}
<div className="flex-1">
<div className="flex justify-between items-start mb-2">
<div className="flex items-center gap-2">
{getStatusBadge()}
{webinar.featured && (
<Badge className="bg-yellow-100 text-yellow-800">Featured</Badge>
)}
{getStatusBadge(webinar.webinar_status)}
</div>
<span className="text-small text-gray-500">{formatDate(webinar.date)}</span>
<span className="text-small text-gray-500">{formatDate(webinar.session_datetime)}</span>
</div>
<h3 className="text-h4 mb-2 line-clamp-2">{webinar.title}</h3>
<p className="text-body text-gray-600 mb-3 line-clamp-2">{webinar.description}</p>
<h3 className="text-h4 mb-2 line-clamp-2">{webinar.session_title}</h3>
<p className="text-body text-gray-600 mb-3 line-clamp-2">
{webinar.description || 'No description available'}
</p>
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-3">
{webinarTags.map((tag, idx) => (
<Badge key={idx} variant="outline" className="text-xs bg-gray-50">
{tag}
</Badge>
))}
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 text-small text-gray-500">
<span className="flex items-center gap-1">
<Users className="w-4 h-4" />
{webinar.presenter}
{webinar.owner || 'Kautilya Leadership'}
</span>
<span className="flex items-center gap-1">
<Clock className="w-4 h-4" />
{webinar.duration}
{formatDuration(webinar.duration_minutes)}
</span>
<span className="flex items-center gap-1">
<Eye className="w-4 h-4" />
{webinar.attendees}
Max {webinar.max_attendee.toLocaleString()}
</span>
</div>
<div className="flex items-center gap-2 text-primary font-medium">
<span className="text-small">{getActionText()}</span>
<ArrowRight className="w-4 h-4" />
</div>
{!isCancelled && (
<div className="flex items-center gap-2 font-medium" style={{ color: '#04045b' }}>
<span className="text-small">{getActionText(webinar.webinar_status)}</span>
<ArrowRight className="w-4 h-4" />
</div>
)}
</div>
</div>
</div>
@@ -279,100 +266,122 @@ console.log('Current webinars count:', currentWebinars.length);
);
}
// Grid View
return (
<Card
className="cursor-pointer transition-all duration-300 hover:shadow-lg hover:transform hover:-translate-y-2 group overflow-hidden"
className={`cursor-pointer transition-all duration-300 hover:shadow-lg hover:transform hover:-translate-y-2 group overflow-hidden ${isCancelled ? 'opacity-75' : ''}`}
onClick={handleCardClick}
style={isCancelled ? { cursor: 'not-allowed' } : {}}
>
{/* Image */}
<div className="aspect-video relative overflow-hidden">
<ImageWithFallback
src={webinar.thumbnail}
alt={webinar.title}
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
<div className="aspect-video relative overflow-hidden bg-gradient-to-br from-gray-100 to-gray-200">
<div className="w-full h-full flex items-center justify-center">
<Play className="w-12 h-12 text-gray-400" />
</div>
{/* Status Badge */}
<div className="absolute top-4 left-4">
{getStatusBadge()}
{getStatusBadge(webinar.webinar_status)}
</div>
{/* Featured Badge */}
{webinar.featured && (
<div className="absolute top-4 right-4">
<Badge className="bg-yellow-100 text-yellow-800 border border-yellow-200">
<Star className="w-3 h-3 mr-1" />
Featured
</Badge>
</div>
)}
{/* Play Icon Overlay */}
<div className="absolute inset-0 bg-black bg-opacity-40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<div className="bg-white bg-opacity-90 rounded-full p-3">
<Play className="w-6 h-6 text-gray-800" />
{!isCancelled && (
<div className="absolute inset-0 bg-black bg-opacity-40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<div className="bg-white bg-opacity-90 rounded-full p-3">
<Play className="w-6 h-6 text-gray-800" />
</div>
</div>
</div>
)}
</div>
{/* Content */}
<CardContent className="p-6">
<div className="flex items-center justify-between mb-2">
<Badge variant="secondary" className="text-xs">
{webinar.category}
{webinar.recurring_webinar ? 'Recurring' : 'One-time'}
</Badge>
<span className="text-small text-gray-500">{formatDate(webinar.date)}</span>
<span className="text-small text-gray-500">{formatDate(webinar.session_datetime)}</span>
</div>
<h3 className="text-h4 mb-3 line-clamp-2 group-hover:text-primary transition-colors">
{webinar.title}
{webinar.session_title}
</h3>
<p className="text-body text-gray-600 mb-4 line-clamp-2">
{webinar.description}
{webinar.description || 'No description available'}
</p>
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-4">
{webinarTags.slice(0, 2).map((tag, idx) => (
<Badge key={idx} variant="outline" className="text-xs bg-gray-50">
{tag}
</Badge>
))}
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 text-small text-gray-500">
<Users className="w-4 h-4" />
<span>{webinar.presenter}</span>
<span>{webinar.owner || 'Kautilya Leadership'}</span>
</div>
<div className="flex items-center justify-between text-small text-gray-500">
<div className="flex items-center gap-1">
<Clock className="w-4 h-4" />
<span>{webinar.duration}</span>
<span>{formatDuration(webinar.duration_minutes)}</span>
</div>
<div className="flex items-center gap-1">
<Eye className="w-4 h-4" />
<span>{webinar.attendees}</span>
<span>Max {webinar.max_attendee.toLocaleString()}</span>
</div>
</div>
</div>
<div className="flex items-center justify-between mt-4 pt-4 border-t">
<div className="flex items-center gap-1">
{webinar.tags.slice(0, 2).map((tag, index) => (
<Badge key={index} variant="outline" className="text-xs">
{tag}
</Badge>
))}
{!isCancelled && (
<div className="flex items-center justify-between mt-4 pt-4 border-t">
<div className="flex items-center gap-1 text-xs text-gray-500">
{webinar.require_registration && (
<>
<Star className="w-3 h-3 text-yellow-500" />
<span>Registration Required</span>
</>
)}
</div>
<div className="flex items-center gap-2 font-medium group-hover:translate-x-1 transition-transform" style={{ color: '#04045b' }}>
<span className="text-small">{getActionText(webinar.webinar_status)}</span>
<ArrowRight className="w-4 h-4" />
</div>
</div>
<div className="flex items-center gap-2 text-primary font-medium group-hover:translate-x-1 transition-transform">
<span className="text-small">{getActionText()}</span>
<ArrowRight className="w-4 h-4" />
</div>
</div>
)}
</CardContent>
</Card>
);
};
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading webinars..." />
</div>
);
}
if (isError) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<div className="text-center">
<p className="text-red-600 mb-4">Error loading webinars. Please try again later.</p>
<Button onClick={() => window.location.reload()}>Retry</Button>
</div>
</div>
);
}
return (
<div style={{ backgroundColor: '#FFFFFF' }}>
{/* Hero Section with Background Image */}
{/* Hero Section */}
<section className="relative h-[400px] overflow-hidden">
{/* Background Image */}
<div className="absolute inset-0">
<ImageWithFallback
src="https://images.unsplash.com/photo-1652265540589-46f91535337b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxidXNpbmVzcyUyMHByZXNlbnRhdGlvbiUyMHdlYmluYXIlMjBjb25mZXJlbmNlfGVufDF8fHx8MTc1NTg1NDI3MHww&ixlib=rb-4.1.0&q=80&w=1080"
@@ -382,14 +391,12 @@ console.log('Current webinars count:', currentWebinars.length);
<div className="absolute inset-0 bg-black/60" />
</div>
{/* Hero Content */}
<div className="relative h-full flex flex-col justify-center section-margin-x">
<div className="text-center">
<h1 className="text-h1-white mb-6">
Leadership Webcasts &<br />
Expert Insights
</h1>
<p className="text-body-lg-white max-w-3xl mx-auto">
Explore our comprehensive collection of expert insights, research, and practical guidance
to elevate your leadership journey and drive organizational excellence.
@@ -397,8 +404,7 @@ console.log('Current webinars count:', currentWebinars.length);
</div>
</div>
{/* Statistics Strip at Bottom */}
<div className="absolute bottom-0 left-0 right-0">
<div className="absolute bottom-0 left-0 right-0">
<div className="bg-black/80 backdrop-blur-sm px-8 py-6">
<div className="section-margin-x">
<div className="grid grid-cols-3 gap-8 text-center">
@@ -424,12 +430,11 @@ console.log('Current webinars count:', currentWebinars.length);
<section className="py-8" style={{ backgroundColor: '#FFFFFF' }}>
<div className="section-margin-x">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
{/* Search Bar */}
<div className="relative max-w-md flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
type="text"
placeholder="Search webcasts..."
placeholder="Search webinars..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-3 text-body rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-200 w-full bg-gray-50"
@@ -441,7 +446,6 @@ console.log('Current webinars count:', currentWebinars.length);
/>
</div>
{/* View Toggle and Sort */}
<div className="flex items-center gap-4">
<div className="flex items-center border border-gray-300 rounded-lg overflow-hidden">
<button
@@ -451,7 +455,7 @@ console.log('Current webinars count:', currentWebinars.length);
: 'bg-white text-gray-600 hover:bg-gray-50'
}`}
style={{
backgroundColor: viewType === 'grid' ? 'var(--color-primary)' : undefined
backgroundColor: viewType === 'grid' ? '#04045b' : undefined
}}
aria-label="Grid view"
>
@@ -464,7 +468,7 @@ console.log('Current webinars count:', currentWebinars.length);
: 'bg-white text-gray-600 hover:bg-gray-50'
}`}
style={{
backgroundColor: viewType === 'list' ? 'var(--color-primary)' : undefined
backgroundColor: viewType === 'list' ? '#04045b' : undefined
}}
aria-label="List view"
>
@@ -489,31 +493,28 @@ console.log('Current webinars count:', currentWebinars.length);
</div>
</section>
{/* Main Content Section with Sidebar */}
{/* Main Content Section */}
<section className="pb-16" style={{ backgroundColor: '#FFFFFF' }}>
<div className="section-margin-x">
<div className="grid grid-cols-12 gap-8">
{/* Left Sidebar - Sticky Filters */}
{/* Left Sidebar Filters */}
<div className="col-span-12 lg:col-span-3">
<div className="sticky top-4">
<Card className="bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden">
{/* Filter Header */}
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="p-1.5 rounded-md" style={{ backgroundColor: 'rgba(4, 4, 91, 0.1)' }}>
<Filter className="w-3.5 h-3.5" style={{ color: 'var(--color-primary)' }} />
<Filter className="w-3.5 h-3.5" style={{ color: '#04045b' }} />
</div>
<h3 className="text-body font-semibold text-gray-800">
Filters
</h3>
<h3 className="text-body font-semibold text-gray-800">Filters</h3>
</div>
{hasActiveFilters && (
<Button
variant="ghost"
size="sm"
onClick={clearAllFilters}
className="text-xs px-2 py-1 rounded-md transition-colors filter-clear-btn"
className="text-xs px-2 py-1 rounded-md transition-colors"
>
<X className="w-3 h-3 mr-1" />
Clear
@@ -522,67 +523,9 @@ console.log('Current webinars count:', currentWebinars.length);
</div>
</div>
{/* Filter Content */}
<div className="p-4">
<div className="space-y-6">
{/* Category Filter */}
<div className="filter-section">
<label className="block text-small mb-2 font-medium text-gray-700">
Category
</label>
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
<SelectValue placeholder="All Categories" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category} value={category} className="text-small">
{category}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Format Filter */}
<div className="filter-section">
<label className="block text-small mb-2 font-medium text-gray-700">
Format
</label>
<Select value={selectedFormat} onValueChange={setSelectedFormat}>
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
<SelectValue placeholder="All Formats" />
</SelectTrigger>
<SelectContent>
{formats.map((format) => (
<SelectItem key={format} value={format} className="text-small">
{format}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Level Filter */}
<div className="filter-section">
<label className="block text-small mb-2 font-medium text-gray-700">
Level
</label>
<Select value={selectedLevel} onValueChange={setSelectedLevel}>
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
<SelectValue placeholder="All Levels" />
</SelectTrigger>
<SelectContent>
{levels.map((level) => (
<SelectItem key={level} value={level} className="text-small">
{level}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Status Filter - Multi-select Pills */}
{/* Status Filter */}
<div className="filter-section">
<label className="block text-small mb-3 font-medium text-gray-700">
Status
@@ -611,10 +554,10 @@ console.log('Current webinars count:', currentWebinars.length);
)}
</div>
{/* Duration Filter - Slider */}
{/* Duration Filter */}
<div className="filter-section">
<label className="block text-small mb-3 font-medium text-gray-700">
Duration
Duration (minutes)
</label>
<div className="px-2">
<Slider
@@ -629,19 +572,13 @@ console.log('Current webinars count:', currentWebinars.length);
<span>{durationRange[0]} min</span>
<span>{durationRange[1]} min</span>
</div>
<div className="mt-1 text-center text-xs text-gray-400">
{durationRange[0] === 0 && durationRange[1] === 120
? 'All durations'
: `${durationRange[0]}-${durationRange[1]} minutes`
}
</div>
</div>
</div>
{/* Attendee Count Filter - Slider */}
{/* Attendee Filter */}
<div className="filter-section">
<label className="block text-small mb-3 font-medium text-gray-700">
Attendees
Max Attendees
</label>
<div className="px-2">
<Slider
@@ -656,12 +593,6 @@ console.log('Current webinars count:', currentWebinars.length);
<span>{attendeeRange[0].toLocaleString()}</span>
<span>{attendeeRange[1].toLocaleString()}+</span>
</div>
<div className="mt-1 text-center text-xs text-gray-400">
{attendeeRange[0] === 0 && attendeeRange[1] === 5000
? 'Any size'
: `${attendeeRange[0].toLocaleString()}-${attendeeRange[1].toLocaleString()}+`
}
</div>
</div>
</div>
</div>
@@ -672,24 +603,22 @@ console.log('Current webinars count:', currentWebinars.length);
{/* Right Main Content */}
<div className="col-span-12 lg:col-span-9">
{/* Results Header */}
<div className="flex items-center justify-between mb-6">
<div className="text-body text-gray-600">
Showing {currentWebinars.length} of {filteredWebinars.length} webcasts
Showing {currentWebinars.length} of {filteredWebinars.length} webinars
</div>
<div className="text-small text-gray-500">
Page {currentPage} of {totalPages}
</div>
</div>
{/* Content Area */}
<div ref={containerRef}>
{currentWebinars.length === 0 ? (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<Search className="w-12 h-12 mx-auto mb-4" />
</div>
<h3 className="text-h4 mb-2">No webcasts found</h3>
<h3 className="text-h4 mb-2">No webinars found</h3>
<p className="text-body text-gray-600 mb-4">
Try adjusting your filters or search terms
</p>
@@ -701,7 +630,6 @@ console.log('Current webinars count:', currentWebinars.length);
</div>
) : (
<>
{/* Grid View */}
{viewType === 'grid' ? (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 mb-8">
{currentWebinars.map((webinar) => (
@@ -709,7 +637,6 @@ console.log('Current webinars count:', currentWebinars.length);
))}
</div>
) : (
/* List View */
<div className="space-y-4 mb-8">
{currentWebinars.map((webinar) => (
<WebinarCard key={webinar.id} webinar={webinar} />
@@ -723,64 +650,46 @@ console.log('Current webinars count:', currentWebinars.length);
<Button
variant="outline"
size="sm"
onClick={() => {
setCurrentPage(prev => Math.max(1, prev - 1));
containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}}
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
className="flex items-center gap-1 border-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronLeft className="w-4 h-4" />
Previous
</Button>
<div className="flex items-center gap-1">
{Array.from({ length: totalPages }, (_, i) => {
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => {
const page = i + 1;
// Show limited pages for better UX
if (totalPages > 7) {
const showPage =
page === 1 ||
page === totalPages ||
(page >= currentPage - 1 && page <= currentPage + 1);
if (!showPage) {
if (page === currentPage - 2 || page === currentPage + 2) {
return <span key={page} className="px-2">...</span>;
}
return null;
}
}
return (
<Button
key={page}
variant={currentPage === page ? "default" : "outline"}
size="sm"
onClick={() => {
setCurrentPage(page);
containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}}
className={`min-w-10 ${currentPage === page
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'border-gray-300 text-gray-700 hover:bg-gray-50'
}`}
onClick={() => setCurrentPage(page)}
className="min-w-10"
style={currentPage === page ? { backgroundColor: '#04045b' } : {}}
>
{page}
</Button>
);
})}
{totalPages > 5 && <span className="px-2">...</span>}
{totalPages > 5 && (
<Button
variant={currentPage === totalPages ? "default" : "outline"}
size="sm"
onClick={() => setCurrentPage(totalPages)}
className="min-w-10"
style={currentPage === totalPages ? { backgroundColor: '#04045b' } : {}}
>
{totalPages}
</Button>
)}
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
setCurrentPage(prev => Math.min(totalPages, prev + 1));
containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}}
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages}
className="flex items-center gap-1 border-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
<ChevronRight className="w-4 h-4" />
@@ -795,7 +704,6 @@ console.log('Current webinars count:', currentWebinars.length);
</div>
</section>
{/* Webcast CTA Banner */}
<WebcastCTABanner />
</div>
);

View File

@@ -166,6 +166,16 @@ interface ServicePageData {
video_url: string | null;
display_order: number;
}>;
cta_section: {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string;
};
}
// Map API icons to Lucide icons
@@ -189,17 +199,17 @@ const getIconComponent = (iconUrl: string) => {
'/icons/coaching.svg': MessageCircle,
'/icons/compass.svg': Compass,
};
return iconMap[iconUrl] || Target;
};
export function CultureCompetence() {
const [expandedPhase, setExpandedPhase] = useState<number | null>(0);
const { data: apiResponse, isLoading, error } = useGetServiceListQuery({
service_type: 'culture_and_competence_consulting'
const { data: apiResponse, isLoading, error } = useGetServiceListQuery({
service_type: 'culture_and_competence_consulting'
});
const apiData = apiResponse?.data as ServicePageData | undefined;
useEffect(() => {
@@ -207,13 +217,13 @@ export function CultureCompetence() {
}, []);
// Loading state
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Culture Competence..." />
</div>
);
}
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Culture Competence..." />
</div>
);
}
// Error state
if (error) {
@@ -266,7 +276,7 @@ export function CultureCompetence() {
const designationParts = testimonial.designation?.split(',') || [];
const role = designationParts[0]?.trim() || '';
const company = designationParts[1]?.trim() || '';
return {
id: parseInt(testimonial.id) || 0,
name: testimonial.name || '',
@@ -473,254 +483,254 @@ export function CultureCompetence() {
{/* 4. Our Approach */}
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
<div className="section-margin-x">
<div className="w-full">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<BrandedTag text="Our Approach" />
<h2 className="text-h2 mb-8 text-[#26231A]">
{apiData?.approach_section?.title || "Our Approach to Leadership Development"}
</h2>
<p className="text-body-lg text-[#6F6F6F] max-w-3xl mx-auto">
{apiData?.approach_section?.description || "A systematic, research-backed methodology that transforms leadership potential into measurable business impact."}
</p>
</div>
<div className="section-margin-x">
<div className="w-full">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<BrandedTag text="Our Approach" />
<h2 className="text-h2 mb-8 text-[#26231A]">
{apiData?.approach_section?.title || "Our Approach to Leadership Development"}
</h2>
<p className="text-body-lg text-[#6F6F6F] max-w-3xl mx-auto">
{apiData?.approach_section?.description || "A systematic, research-backed methodology that transforms leadership potential into measurable business impact."}
</p>
</div>
{/* Flowchart Container with Connecting Lines */}
<div className="relative mb-16 flex flex-col items-center">
{/* Only render if approach_cards exist and have items */}
{apiData?.approach_section?.approach_cards && apiData.approach_section.approach_cards.length > 0 && (
<>
{/* Desktop: Horizontal Flowchart */}
<div className="hidden lg:block w-full max-w-5xl">
<div className="relative">
{/* Row 1: First 3 approach cards from API */}
<div className="grid grid-cols-3 gap-8 mb-12 relative w-full">
{apiData.approach_section.approach_cards.slice(0, 3).map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
return (
<div key={card.id} className={`bg-white border-2 ${idx === 1 ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${idx === 1 ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
{/* Flowchart Container with Connecting Lines */}
<div className="relative mb-16 flex flex-col items-center">
{/* Only render if approach_cards exist and have items */}
{apiData?.approach_section?.approach_cards && apiData.approach_section.approach_cards.length > 0 && (
<>
{/* Desktop: Horizontal Flowchart */}
<div className="hidden lg:block w-full max-w-5xl">
<div className="relative">
{/* Row 1: First 3 approach cards from API */}
<div className="grid grid-cols-3 gap-8 mb-12 relative w-full">
{apiData.approach_section.approach_cards.slice(0, 3).map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
return (
<div key={card.id} className={`bg-white border-2 ${idx === 1 ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${idx === 1 ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
))}
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
</div>
)}
</div>
);
})}
{/* Arrows between first 3 cards */}
{apiData.approach_section.approach_cards.length >= 2 && (
<div className="absolute top-1/2 left-[calc(33.33%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] -ml-1" />
</div>
)}
{apiData.approach_section.approach_cards.length >= 3 && (
<div className="absolute top-1/2 left-[calc(66.66%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
</div>
)}
</div>
);
})}
{/* Arrows between first 3 cards */}
{apiData.approach_section.approach_cards.length >= 2 && (
<div className="absolute top-1/2 left-[calc(33.33%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] -ml-1" />
</div>
)}
{apiData.approach_section.approach_cards.length >= 3 && (
<div className="absolute top-1/2 left-[calc(66.66%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
</div>
)}
</div>
{/* Vertical Connector - Center Flow Down */}
{apiData.approach_section.approach_cards.length > 3 && (
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] rotate-90" />
</div>
</div>
)}
{/* Vertical Connector - Center Flow Down */}
{apiData.approach_section.approach_cards.length > 3 && (
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] rotate-90" />
{/* Row 2: Next 2 approach cards (if available) */}
{apiData.approach_section.approach_cards.length >= 4 && (
<div className="grid grid-cols-2 gap-8 w-full max-w-3xl mx-auto mb-12 relative">
{apiData.approach_section.approach_cards.slice(3, 5).map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
const isFirstOfPair = idx === 0;
return (
<div key={card.id} className={`bg-white border-2 ${isFirstOfPair ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${isFirstOfPair ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
</div>
)}
</div>
);
})}
{/* Arrow between the two cards */}
{apiData.approach_section.approach_cards.length >= 5 && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
</div>
)}
</div>
)}
{/* Final Vertical Connector - Center Flow Down to Outcome */}
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] rotate-90" />
</div>
</div>
{/* Row 3: Expected Outcome - Use API outcomes data */}
<div className="flex justify-center w-full">
<div className="bg-[#04045B] text-white rounded-xl p-8 w-full max-w-2xl border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
</div>
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
</div>
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
)}
{/* Row 2: Next 2 approach cards (if available) */}
{apiData.approach_section.approach_cards.length >= 4 && (
<div className="grid grid-cols-2 gap-8 w-full max-w-3xl mx-auto mb-12 relative">
{apiData.approach_section.approach_cards.slice(3, 5).map((card, idx) => {
{/* Tablet & Mobile: Vertical Flowchart */}
<div className="lg:hidden space-y-8">
{/* Map all approach cards vertically */}
{apiData.approach_section.approach_cards.map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
const isFirstOfPair = idx === 0;
const isEven = idx % 2 === 0;
return (
<div key={card.id} className={`bg-white border-2 ${isFirstOfPair ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${isFirstOfPair ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
<div key={card.id} className="relative">
<div className={`bg-white border-2 ${isEven ? 'border-[#04045B]' : 'border-[#F8C301]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300`}>
<div className={`w-12 h-12 ${isEven ? 'bg-[#04045B]' : 'bg-[#F8C301]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
</div>
)}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
{/* Connector Arrow */}
{idx < apiData.approach_section.approach_cards.length - 1 && (
<div className="flex justify-center my-4">
<ArrowRight className={`w-8 h-8 ${isEven ? 'text-[#F8C301]' : 'text-[#04045B]'} rotate-90`} />
</div>
)}
</div>
);
})}
{/* Arrow between the two cards */}
{apiData.approach_section.approach_cards.length >= 5 && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
{/* Expected Outcome - Use API outcomes data */}
<div className="bg-[#04045B] text-white rounded-xl p-8 border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
</div>
)}
</div>
)}
{/* Final Vertical Connector - Center Flow Down to Outcome */}
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] rotate-90" />
</div>
</div>
{/* Row 3: Expected Outcome - Use API outcomes data */}
<div className="flex justify-center w-full">
<div className="bg-[#04045B] text-white rounded-xl p-8 w-full max-w-2xl border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
</div>
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
</div>
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* Tablet & Mobile: Vertical Flowchart */}
<div className="lg:hidden space-y-8">
{/* Map all approach cards vertically */}
{apiData.approach_section.approach_cards.map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
const isEven = idx % 2 === 0;
return (
<div key={card.id} className="relative">
<div className={`bg-white border-2 ${isEven ? 'border-[#04045B]' : 'border-[#F8C301]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300`}>
<div className={`w-12 h-12 ${isEven ? 'bg-[#04045B]' : 'bg-[#F8C301]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
</div>
))}
</div>
)}
</div>
{/* Connector Arrow */}
{idx < apiData.approach_section.approach_cards.length - 1 && (
<div className="flex justify-center my-4">
<ArrowRight className={`w-8 h-8 ${isEven ? 'text-[#F8C301]' : 'text-[#04045B]'} rotate-90`} />
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
</div>
)}
</div>
</div>
);
})}
</>
)}
</div>
{/* Expected Outcome - Use API outcomes data */}
<div className="bg-[#04045B] text-white rounded-xl p-8 border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
{/* Framework Effectiveness - Stats Section */}
{apiData?.stats_section && apiData.stats_section.stat_cards && apiData.stats_section.stat_cards.length > 0 && (
<div className="bg-gray-50 rounded-xl p-8">
<div className="text-center mb-8">
<h3 className="text-h3 text-[#26231A] mb-4">{apiData.stats_section.title || "Framework Effectiveness"}</h3>
<p className="text-body text-[#6F6F6F] max-w-2xl mx-auto">
{apiData.stats_section.description || "Measurable results that demonstrate the impact of our leadership development approach."}
</p>
</div>
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{apiData.stats_section.stat_cards.map((stat) => {
const IconComponent = getIconComponent(stat.icon_url);
return (
<div key={stat.id} className="text-center bg-white rounded-lg p-6">
<div className="w-14 h-14 bg-[#04045B] rounded-lg flex items-center justify-center mx-auto mb-3">
{IconComponent ? <IconComponent className="w-7 h-7 text-white" /> : <TrendingUp className="w-7 h-7 text-white" />}
</div>
<div className="text-h2 text-[#04045B] mb-2">{stat.value || "0"}</div>
<p className="text-body text-[#6F6F6F]">{stat.label || ""}</p>
</div>
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
);
})}
</div>
</div>
</div>
</>
)}
</div>
{/* Framework Effectiveness - Stats Section */}
{apiData?.stats_section && apiData.stats_section.stat_cards && apiData.stats_section.stat_cards.length > 0 && (
<div className="bg-gray-50 rounded-xl p-8">
<div className="text-center mb-8">
<h3 className="text-h3 text-[#26231A] mb-4">{apiData.stats_section.title || "Framework Effectiveness"}</h3>
<p className="text-body text-[#6F6F6F] max-w-2xl mx-auto">
{apiData.stats_section.description || "Measurable results that demonstrate the impact of our leadership development approach."}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{apiData.stats_section.stat_cards.map((stat) => {
const IconComponent = getIconComponent(stat.icon_url);
return (
<div key={stat.id} className="text-center bg-white rounded-lg p-6">
<div className="w-14 h-14 bg-[#04045B] rounded-lg flex items-center justify-center mx-auto mb-3">
{IconComponent ? <IconComponent className="w-7 h-7 text-white" /> : <TrendingUp className="w-7 h-7 text-white" />}
</div>
<div className="text-h2 text-[#04045B] mb-2">{stat.value || "0"}</div>
<p className="text-body text-[#6F6F6F]">{stat.label || ""}</p>
</div>
);
})}
)}
</div>
</div>
)}
</div>
</div>
</div>
</section>
</div>
</section>
{/* 5. Sample Program Format */}
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
@@ -870,41 +880,45 @@ export function CultureCompetence() {
/>
{/* 8. CTA Section */}
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.hero_section?.background_image_url || ''}
alt={apiData.hero_section?.background_image_alt_text || 'Culture & Competence Consulting'}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
Ready to transform your culture?
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to align strategy and performance.
</h2>
<StandardCTAButton
text="Schedule a Culture Consultation"
onClick={() => navigateTo('/contact?topic=culture-competence')}
ariaLabel="Schedule a culture and competence consultation"
{apiData.cta_section && (
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.cta_section.background_image_url}
alt={apiData.cta_section.text}
className="w-full h-full object-cover"
/>
<p className="text-body-white mt-6 opacity-90">
Connect with our culture transformation experts to discuss building a high-performance culture aligned with your business strategy.
</p>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
</div>
</section>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
{apiData.cta_section.text}
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to align strategy and performance.
</h2>
<StandardCTAButton
text={apiData.cta_section.cta_text}
onClick={() => navigateTo(apiData.cta_section.cta_destination)}
ariaLabel={apiData.cta_section.cta_text}
/>
{apiData.cta_section.description && (
<p className="text-body-white mt-6 opacity-90">
{apiData.cta_section.description}
</p>
)}
</div>
</div>
</section>
)}
</div>
);
}

View File

@@ -169,6 +169,16 @@ interface ServicePageData {
video_url: string | null;
display_order: number;
}>;
cta_section: {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string;
};
}
// Map API icons to Lucide icons
@@ -191,18 +201,18 @@ const getIconComponent = (iconUrl: string) => {
'/icons/network.svg': Network,
'/icons/user.svg': User,
};
return iconMap[iconUrl] || MessageCircle;
};
export function ExecutiveCoaching() {
const [expandedUseCase, setExpandedUseCase] = useState<number | null>(null);
const [expandedPhase, setExpandedPhase] = useState<number | null>(0);
const { data: apiResponse, isLoading, error } = useGetServiceListQuery({
service_type: 'coaching_and_mentoring'
const { data: apiResponse, isLoading, error } = useGetServiceListQuery({
service_type: 'coaching_and_mentoring'
});
const apiData = apiResponse?.data as ServicePageData | undefined;
useEffect(() => {
@@ -210,13 +220,13 @@ export function ExecutiveCoaching() {
}, []);
// Loading state
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Executive Coaching..." />
</div>
);
}
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Executive Coaching..." />
</div>
);
}
// Error state
if (error) {
@@ -269,7 +279,7 @@ export function ExecutiveCoaching() {
const designationParts = testimonial.designation?.split(',') || [];
const role = designationParts[0]?.trim() || '';
const company = designationParts[1]?.trim() || '';
return {
id: parseInt(testimonial.id) || 0,
name: testimonial.name || '',
@@ -482,254 +492,254 @@ export function ExecutiveCoaching() {
{/* 4. Our Approach */}
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
<div className="section-margin-x">
<div className="w-full">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<BrandedTag text="Our Approach" />
<h2 className="text-h2 mb-8 text-[#26231A]">
{apiData?.approach_section?.title || "Our Approach to Leadership Development"}
</h2>
<p className="text-body-lg text-[#6F6F6F] max-w-3xl mx-auto">
{apiData?.approach_section?.description || "A systematic, research-backed methodology that transforms leadership potential into measurable business impact."}
</p>
</div>
<div className="section-margin-x">
<div className="w-full">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<BrandedTag text="Our Approach" />
<h2 className="text-h2 mb-8 text-[#26231A]">
{apiData?.approach_section?.title || "Our Approach to Leadership Development"}
</h2>
<p className="text-body-lg text-[#6F6F6F] max-w-3xl mx-auto">
{apiData?.approach_section?.description || "A systematic, research-backed methodology that transforms leadership potential into measurable business impact."}
</p>
</div>
{/* Flowchart Container with Connecting Lines */}
<div className="relative mb-16 flex flex-col items-center">
{/* Only render if approach_cards exist and have items */}
{apiData?.approach_section?.approach_cards && apiData.approach_section.approach_cards.length > 0 && (
<>
{/* Desktop: Horizontal Flowchart */}
<div className="hidden lg:block w-full max-w-5xl">
<div className="relative">
{/* Row 1: First 3 approach cards from API */}
<div className="grid grid-cols-3 gap-8 mb-12 relative w-full">
{apiData.approach_section.approach_cards.slice(0, 3).map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
return (
<div key={card.id} className={`bg-white border-2 ${idx === 1 ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${idx === 1 ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
{/* Flowchart Container with Connecting Lines */}
<div className="relative mb-16 flex flex-col items-center">
{/* Only render if approach_cards exist and have items */}
{apiData?.approach_section?.approach_cards && apiData.approach_section.approach_cards.length > 0 && (
<>
{/* Desktop: Horizontal Flowchart */}
<div className="hidden lg:block w-full max-w-5xl">
<div className="relative">
{/* Row 1: First 3 approach cards from API */}
<div className="grid grid-cols-3 gap-8 mb-12 relative w-full">
{apiData.approach_section.approach_cards.slice(0, 3).map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
return (
<div key={card.id} className={`bg-white border-2 ${idx === 1 ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${idx === 1 ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
))}
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
</div>
)}
</div>
);
})}
{/* Arrows between first 3 cards */}
{apiData.approach_section.approach_cards.length >= 2 && (
<div className="absolute top-1/2 left-[calc(33.33%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] -ml-1" />
</div>
)}
{apiData.approach_section.approach_cards.length >= 3 && (
<div className="absolute top-1/2 left-[calc(66.66%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
</div>
)}
</div>
);
})}
{/* Arrows between first 3 cards */}
{apiData.approach_section.approach_cards.length >= 2 && (
<div className="absolute top-1/2 left-[calc(33.33%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] -ml-1" />
</div>
)}
{apiData.approach_section.approach_cards.length >= 3 && (
<div className="absolute top-1/2 left-[calc(66.66%-2rem)] -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
</div>
)}
</div>
{/* Vertical Connector - Center Flow Down */}
{apiData.approach_section.approach_cards.length > 3 && (
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] rotate-90" />
</div>
</div>
)}
{/* Vertical Connector - Center Flow Down */}
{apiData.approach_section.approach_cards.length > 3 && (
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#F8C301]"></div>
<ArrowRight className="w-6 h-6 text-[#F8C301] rotate-90" />
{/* Row 2: Next 2 approach cards (if available) */}
{apiData.approach_section.approach_cards.length >= 4 && (
<div className="grid grid-cols-2 gap-8 w-full max-w-3xl mx-auto mb-12 relative">
{apiData.approach_section.approach_cards.slice(3, 5).map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
const isFirstOfPair = idx === 0;
return (
<div key={card.id} className={`bg-white border-2 ${isFirstOfPair ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${isFirstOfPair ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
</div>
)}
</div>
);
})}
{/* Arrow between the two cards */}
{apiData.approach_section.approach_cards.length >= 5 && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
</div>
)}
</div>
)}
{/* Final Vertical Connector - Center Flow Down to Outcome */}
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] rotate-90" />
</div>
</div>
{/* Row 3: Expected Outcome - Use API outcomes data */}
<div className="flex justify-center w-full">
<div className="bg-[#04045B] text-white rounded-xl p-8 w-full max-w-2xl border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
</div>
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
</div>
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
)}
{/* Row 2: Next 2 approach cards (if available) */}
{apiData.approach_section.approach_cards.length >= 4 && (
<div className="grid grid-cols-2 gap-8 w-full max-w-3xl mx-auto mb-12 relative">
{apiData.approach_section.approach_cards.slice(3, 5).map((card, idx) => {
{/* Tablet & Mobile: Vertical Flowchart */}
<div className="lg:hidden space-y-8">
{/* Map all approach cards vertically */}
{apiData.approach_section.approach_cards.map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
const isFirstOfPair = idx === 0;
const isEven = idx % 2 === 0;
return (
<div key={card.id} className={`bg-white border-2 ${isFirstOfPair ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
<div className={`w-12 h-12 ${isFirstOfPair ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
<div key={card.id} className="relative">
<div className={`bg-white border-2 ${isEven ? 'border-[#04045B]' : 'border-[#F8C301]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300`}>
<div className={`w-12 h-12 ${isEven ? 'bg-[#04045B]' : 'bg-[#F8C301]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
</div>
)}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
</div>
))}
{/* Connector Arrow */}
{idx < apiData.approach_section.approach_cards.length - 1 && (
<div className="flex justify-center my-4">
<ArrowRight className={`w-8 h-8 ${isEven ? 'text-[#F8C301]' : 'text-[#04045B]'} rotate-90`} />
</div>
)}
</div>
);
})}
{/* Arrow between the two cards */}
{apiData.approach_section.approach_cards.length >= 5 && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-0 flex items-center">
<div className="w-16 h-0.5 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
{/* Expected Outcome - Use API outcomes data */}
<div className="bg-[#04045B] text-white rounded-xl p-8 border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
</div>
)}
</div>
)}
{/* Final Vertical Connector - Center Flow Down to Outcome */}
<div className="flex justify-center mb-6">
<div className="flex flex-col items-center">
<div className="w-0.5 h-12 bg-[#04045B]"></div>
<ArrowRight className="w-6 h-6 text-[#04045B] rotate-90" />
</div>
</div>
{/* Row 3: Expected Outcome - Use API outcomes data */}
<div className="flex justify-center w-full">
<div className="bg-[#04045B] text-white rounded-xl p-8 w-full max-w-2xl border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
</div>
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
</div>
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* Tablet & Mobile: Vertical Flowchart */}
<div className="lg:hidden space-y-8">
{/* Map all approach cards vertically */}
{apiData.approach_section.approach_cards.map((card, idx) => {
const IconComponent = getIconComponent(card.icon_url);
const isEven = idx % 2 === 0;
return (
<div key={card.id} className="relative">
<div className={`bg-white border-2 ${isEven ? 'border-[#04045B]' : 'border-[#F8C301]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300`}>
<div className={`w-12 h-12 ${isEven ? 'bg-[#04045B]' : 'bg-[#F8C301]'} rounded-lg flex items-center justify-center mb-4`}>
{IconComponent ? <IconComponent className="w-6 h-6 text-white" /> : <Target className="w-6 h-6 text-white" />}
</div>
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
{card.bullets && card.bullets.length > 0 && (
<div className="space-y-2">
{card.bullets.map((bullet, bulletIdx) => (
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
{bullet}
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
</div>
))}
</div>
)}
</div>
{/* Connector Arrow */}
{idx < apiData.approach_section.approach_cards.length - 1 && (
<div className="flex justify-center my-4">
<ArrowRight className={`w-8 h-8 ${isEven ? 'text-[#F8C301]' : 'text-[#04045B]'} rotate-90`} />
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
</div>
)}
</div>
</div>
);
})}
</>
)}
</div>
{/* Expected Outcome - Use API outcomes data */}
<div className="bg-[#04045B] text-white rounded-xl p-8 border-4 border-[#F8C301] shadow-xl">
<div className="flex items-center gap-3 mb-4">
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] ? (() => {
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
return OutcomeIcon ? <OutcomeIcon className="w-10 h-10 text-[#F8C301]" /> : <TrendingUp className="w-10 h-10 text-[#F8C301]" />;
})() : <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
<h3 className="text-h4 text-white">
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
</h3>
{/* Framework Effectiveness - Stats Section */}
{apiData?.stats_section && apiData.stats_section.stat_cards && apiData.stats_section.stat_cards.length > 0 && (
<div className="bg-gray-50 rounded-xl p-8">
<div className="text-center mb-8">
<h3 className="text-h3 text-[#26231A] mb-4">{apiData.stats_section.title || "Framework Effectiveness"}</h3>
<p className="text-body text-[#6F6F6F] max-w-2xl mx-auto">
{apiData.stats_section.description || "Measurable results that demonstrate the impact of our leadership development approach."}
</p>
</div>
<p className="text-body text-white mb-4">
{apiData.approach_section.outcomes?.[0]?.description || "A systematic, measurable leadership pipeline that accelerates talent development and succession readiness."}
</p>
<div className="space-y-2">
{apiData.approach_section.outcomes?.[0]?.bullets && apiData.approach_section.outcomes[0].bullets.length > 0 ? (
apiData.approach_section.outcomes[0].bullets.slice(0, 2).map((bullet, idx) => (
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">{bullet}</span>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{apiData.stats_section.stat_cards.map((stat) => {
const IconComponent = getIconComponent(stat.icon_url);
return (
<div key={stat.id} className="text-center bg-white rounded-lg p-6">
<div className="w-14 h-14 bg-[#04045B] rounded-lg flex items-center justify-center mx-auto mb-3">
{IconComponent ? <IconComponent className="w-7 h-7 text-white" /> : <TrendingUp className="w-7 h-7 text-white" />}
</div>
<div className="text-h2 text-[#04045B] mb-2">{stat.value || "0"}</div>
<p className="text-body text-[#6F6F6F]">{stat.label || ""}</p>
</div>
))
) : (
<div className="flex items-center gap-2 text-[#F8C301]">
<CheckCircle className="w-6 h-6" />
<span className="text-body text-white">Proven ROI on Leadership Investment</span>
</div>
)}
);
})}
</div>
</div>
</div>
</>
)}
</div>
{/* Framework Effectiveness - Stats Section */}
{apiData?.stats_section && apiData.stats_section.stat_cards && apiData.stats_section.stat_cards.length > 0 && (
<div className="bg-gray-50 rounded-xl p-8">
<div className="text-center mb-8">
<h3 className="text-h3 text-[#26231A] mb-4">{apiData.stats_section.title || "Framework Effectiveness"}</h3>
<p className="text-body text-[#6F6F6F] max-w-2xl mx-auto">
{apiData.stats_section.description || "Measurable results that demonstrate the impact of our leadership development approach."}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{apiData.stats_section.stat_cards.map((stat) => {
const IconComponent = getIconComponent(stat.icon_url);
return (
<div key={stat.id} className="text-center bg-white rounded-lg p-6">
<div className="w-14 h-14 bg-[#04045B] rounded-lg flex items-center justify-center mx-auto mb-3">
{IconComponent ? <IconComponent className="w-7 h-7 text-white" /> : <TrendingUp className="w-7 h-7 text-white" />}
</div>
<div className="text-h2 text-[#04045B] mb-2">{stat.value || "0"}</div>
<p className="text-body text-[#6F6F6F]">{stat.label || ""}</p>
</div>
);
})}
)}
</div>
</div>
)}
</div>
</div>
</div>
</section>
</div>
</section>
{/* 5. Sample Program Format */}
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
@@ -879,41 +889,45 @@ export function ExecutiveCoaching() {
/>
{/* 8. CTA Section */}
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.hero_section?.background_image_url || ''}
alt={apiData.hero_section?.background_image_alt_text || 'Coaching & Mentoring'}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
Ready to accelerate your leadership?
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
for personalized coaching now.
</h2>
<StandardCTAButton
text="Schedule a Coaching Consultation"
onClick={() => navigateTo('/contact?topic=coaching-mentoring')}
ariaLabel="Schedule a coaching and mentoring consultation"
{apiData.cta_section && (
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.cta_section.background_image_url}
alt={apiData.cta_section.text}
className="w-full h-full object-cover"
/>
<p className="text-body-white mt-6 opacity-90">
Connect with our executive coaching experts to discuss personalized leadership development that transforms your effectiveness and career trajectory.
</p>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
</div>
</section>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
{apiData.cta_section.text}
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
for personalized coaching now.
</h2>
<StandardCTAButton
text={apiData.cta_section.cta_text}
onClick={() => navigateTo(apiData.cta_section.cta_destination)}
ariaLabel={apiData.cta_section.cta_text}
/>
{apiData.cta_section.description && (
<p className="text-body-white mt-6 opacity-90">
{apiData.cta_section.description}
</p>
)}
</div>
</div>
</section>
)}
</div>
);
}

View File

@@ -178,6 +178,16 @@ interface ServicePageData {
video_url: string | null;
display_order: number;
}>;
cta_section: {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string;
};
}
// Map API icons to Lucide icons
@@ -217,13 +227,13 @@ export function LeadershipDevelopment() {
window.scrollTo(0, 0);
}, []);
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Leadership Development..." />
</div>
);
}
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Leadership Development..." />
</div>
);
}
if (error || !apiData) {
return (
@@ -858,41 +868,45 @@ export function LeadershipDevelopment() {
/>
{/* 8. CTA Section */}
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.hero_section.background_image_url}
alt={apiData.hero_section.background_image_alt_text}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
Ready to transform your leadership?
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to drive exceptional strategic outcomes.
</h2>
<StandardCTAButton
text="Schedule a Leadership Consultation"
onClick={() => navigateTo('/contact?topic=leadership-development')}
ariaLabel="Schedule a leadership development consultation"
{apiData.cta_section && (
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.cta_section.background_image_url}
alt={apiData.cta_section.text}
className="w-full h-full object-cover"
/>
<p className="text-body-white mt-6 opacity-90">
Connect with our leadership development experts to discuss building visionary leadership capability in your organization.
</p>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
</div>
</section>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
{apiData.cta_section.text}
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to drive exceptional strategic outcomes.
</h2>
<StandardCTAButton
text={apiData.cta_section.cta_text}
onClick={() => navigateTo(apiData.cta_section.cta_destination)}
ariaLabel={apiData.cta_section.cta_text}
/>
{apiData.cta_section.description && (
<p className="text-body-white mt-6 opacity-90">
{apiData.cta_section.description}
</p>
)}
</div>
</div>
</section>
)}
</div>
);
}

View File

@@ -178,6 +178,16 @@ interface ServicePageData {
video_url: string | null;
display_order: number;
}>;
cta_section: {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string;
};
}
// Map API icons to Lucide icons
@@ -882,41 +892,45 @@ export function LeadershipPipelineDevelopment() {
/>
{/* 8. CTA Section */}
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.hero_section.background_image_url}
alt={apiData.hero_section.background_image_alt_text}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
Ready to build your leadership pipeline?
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to eliminate succession gaps now.
</h2>
<StandardCTAButton
text="Schedule a Pipeline Consultation"
onClick={() => navigateTo('/contact?topic=leadership-pipeline')}
ariaLabel="Schedule a leadership pipeline consultation"
{apiData.cta_section && (
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.cta_section.background_image_url}
alt={apiData.cta_section.text}
className="w-full h-full object-cover"
/>
<p className="text-body-white mt-6 opacity-90">
Connect with our pipeline development experts to discuss building sustainable leadership capability across your organization.
</p>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
</div>
</section>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
{apiData.cta_section.text}
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to eliminate succession gaps now.
</h2>
<StandardCTAButton
text={apiData.cta_section.cta_text}
onClick={() => navigateTo(apiData.cta_section.cta_destination)}
ariaLabel={apiData.cta_section.cta_text}
/>
{apiData.cta_section.description && (
<p className="text-body-white mt-6 opacity-90">
{apiData.cta_section.description}
</p>
)}
</div>
</div>
</section>
)}
</div>
);
}

View File

@@ -165,6 +165,16 @@ interface ServicePageData {
video_url: string | null;
display_order: number;
}>;
cta_section: {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string;
};
}
// Map API icons to Lucide icons
@@ -868,41 +878,45 @@ export function ManagementDevelopment() {
/>
{/* 8. CTA Section */}
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.hero_section?.background_image_url || ''}
alt={apiData.hero_section?.background_image_alt_text || 'Management Development'}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
Ready to build exceptional managers?
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to drive team performance now.
</h2>
<StandardCTAButton
text="Schedule a Management Consultation"
onClick={() => navigateTo('/contact?topic=management-development')}
ariaLabel="Schedule a management development consultation"
{apiData.cta_section && (
<section className="relative h-[700px] overflow-hidden">
<div className="absolute inset-0">
<ImageWithFallback
src={apiData.cta_section.background_image_url}
alt={apiData.cta_section.text}
className="w-full h-full object-cover"
/>
<p className="text-body-white mt-6 opacity-90">
Connect with our management development experts to discuss building exceptional managers who drive team engagement and results.
</p>
<div className="absolute inset-0 bg-black/30" />
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
</div>
</div>
</section>
<div className="relative h-full flex items-center justify-end section-margin-x">
<div
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
style={{ backgroundColor: 'var(--color-brand-primary)' }}
>
<BrandedTag text="Next Steps" variant="white" />
<h2 className="text-h2-white mb-8">
{apiData.cta_section.text}
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
{" "}Get in touch{" "}
</span>
to drive team performance now.
</h2>
<StandardCTAButton
text={apiData.cta_section.cta_text}
onClick={() => navigateTo(apiData.cta_section.cta_destination)}
ariaLabel={apiData.cta_section.cta_text}
/>
{apiData.cta_section.description && (
<p className="text-body-white mt-6 opacity-90">
{apiData.cta_section.description}
</p>
)}
</div>
</div>
</section>
)}
</div>
);
}

View File

@@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog@1.1.6";
import { XIcon } from "lucide-react@0.487.0";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import { cn } from "./utils";

View File

@@ -25,6 +25,19 @@ const HomePage: React.FC = () => {
const stats = data?.stats_sections ?? [];
const highlightCards = data?.highlight_cards ?? [];
const ctaBands = data?.cta_bands ?? [];
const ctaSection = data?.cta_section;
// Transform testimonial section data to match Testimonial interface
const testimonialData = data?.testimonial_section?.map((item: any) => ({
id: item.id,
name: item.name,
role: item.designation,
quote: item.content,
videoUrl: item.video_url,
isVideo: !!item.video_url,
rating: 5, // Default rating, can be updated from API if available
avatar: item.profile_xid ? `https://example.com/avatars/${item.profile_xid}.jpg` : undefined,
})) || [];
if (isLoading) {
return (
@@ -101,9 +114,15 @@ const HomePage: React.FC = () => {
<VirtualSpaceSection />
</div>
<TestimonialsSection />
{/* Pass testimonial data to the TestimonialsSection */}
<TestimonialsSection
customTestimonials={testimonialData}
title="What Our Clients Say"
subtitle="Hear from industry leaders who have transformed their organizations with our solutions."
tagText="Client Stories"
/>
<InsightsSection />
<CTABannerSection ctaBands={ctaBands} isLoading={isLoading} />
<CTABannerSection ctaSection={ctaSection} isLoading={isLoading} />
</>
);
};

View File

@@ -47,6 +47,16 @@ export interface TeamMember {
alt_text: string;
bio: string;
}
export interface CtaData {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string | null;
}
export interface AboutUsData {
hero_section: HeroSection;
@@ -61,6 +71,7 @@ export interface AboutUsData {
methodology: Methodology;
philosophy: Philosophy;
testimonials: Testimonial[];
cta_section: CtaData;
}
export interface Methodology {

View File

@@ -67,6 +67,196 @@ export interface CourseListResponse {
};
}
export interface CourseReview {
id: string;
rating: number;
comment: string;
video_url: string | null;
reviewer_name: string;
profile_image: string | null;
bio: string | null;
created_at: string;
}
export interface CourseFaq {
id: string;
question: string;
answer: string;
}
export interface CourseTargetAudience {
id: string;
course_xid: string;
course_icon_xid: string;
title: string;
description: string;
display_order: number;
}
export interface CourseLearningOutcome {
id: string;
course_xid: string;
title: string;
description: string;
display_order: number;
}
export interface CourseLearningStructure {
id: string;
course_xid: string;
title: string;
description: string;
display_order: number;
}
export interface CourseFacultyCredential {
id: string;
course_xid: string;
course_faculty_xid: string;
credential_name: string;
display_order: number;
}
export interface CourseFaculty {
id: string;
course_xid: string;
faculty_name: string;
faculty_title: string;
faculty_organization_name: string;
faculty_biography: string;
display_order: number;
expertises: string[] | null;
credentials: CourseFacultyCredential[];
}
export interface CourseLessonResource {
id: string;
course_xid: string;
module_xid: string;
lesson_xid: string;
content_xid: string;
content_type_xid: string;
content_title: string;
total_duration: number | null;
content_type_name: string;
is_active: boolean;
}
export interface CourseLesson {
id: string;
course_xid: string;
module_xid: string;
lesson_title: string;
lesson_description: string;
is_lock_lesson: boolean;
display_order: number;
lesson_resources: CourseLessonResource[];
}
export interface CourseModule {
id: string;
course_xid: string;
module_name: string;
display_order: number;
lessons: CourseLesson[];
}
export interface CourseResource {
id: string;
course_xid: string;
content_xid: string;
content_type_xid: string;
display_order: number | null;
}
export interface CourseCertificateTemplate {
id: string;
template_name: string;
template_code: string;
display_order: number;
is_active: boolean;
}
export interface CourseCertificate {
id: string;
course_xid: string;
certificate_template_xid: string;
company_logo_url: string | null;
institution_name: string;
program_title: string;
signatory_name: string;
signatory_title: string;
digital_signature_url: string | null;
minimum_pass_percentage: number;
complete_all_lesson_required: boolean;
certificate_template: CourseCertificateTemplate;
}
export interface RecommendedCourse {
id: string;
course_name: string;
course_desc: string;
thumbnail_img: string;
price: string;
best_value: number;
duration: number;
recommended_course_reviews: {
id: string;
rating: number;
comment: string;
reviewer_name: string;
}[];
}
export interface CourseDetail {
id: string;
course_name: string;
course_desc: string;
thumbnail_img: string;
course_category_xid: string;
duration: number;
retail_type: string;
price: string | number;
best_value: number;
target_audience_desc: string | null;
learning_outcomes_desc: string | null;
learning_structure_desc: string | null;
our_methodology_desc: string | null;
is_certificate_available: boolean;
course_status: CourseStatus;
benefit_section: string | null;
learning_section: string | null;
structure_section: string | null;
approach_section: string | null;
faculty_section: string | null;
avg_rating: number;
total_reviews: number;
reviews: CourseReview[];
course_faqs: CourseFaq[];
total_modules: number;
total_lessons: number;
formatted_duration: string;
course_category_name: string;
course_language_xids: string[];
course_target_audiences: CourseTargetAudience[];
course_learning_outcomes: CourseLearningOutcome[];
course_learning_structures: CourseLearningStructure[];
course_facilities: CourseFaculty[];
modules: CourseModule[];
course_resources: CourseResource[];
course_certificate: CourseCertificate | null;
recommended_courses: RecommendedCourse[];
}
export interface CourseDetailResponse {
success: boolean;
status: number;
message: string;
data: CourseDetail;
errors: unknown;
correlation_id: string;
}
/* ================= PREPOPULATE TYPES ================= */
export interface CourseCategory {
@@ -163,7 +353,19 @@ export const courseApi = createApi({
},
providesTags: ["CourseCategories"],
}),
// GET Course By Id
getcoursebyid: builder.query<CourseDetailResponse, string>({
query: (course_id) => `admin/course/${course_id}`,
providesTags: (_result, _error, course_id) => [{ type: "Course", id: course_id }],
}),
}),
});
export const { useGetCoursesQuery, useGetCourseCategoriesQuery } = courseApi;
export const {
useGetCoursesQuery,
useGetCourseCategoriesQuery,
useGetcoursebyidQuery,
} = courseApi;

View File

@@ -28,6 +28,7 @@ export interface StatItem {
/* ================= HIGHLIGHT CARD ================= */
export interface HighlightCard {
id?: string;
card_title: string;
icon_url: string;
accessible_label: string;
@@ -46,6 +47,31 @@ export interface CtaBand {
cta_destination: string;
}
/* ================= TESTIMONIAL TYPES ================= */
export interface TestimonialItem {
id: string;
profile_xid: string;
name: string;
designation: string;
content: string;
video_url: string | null;
display_order: number;
}
/* ================= CTA SECTION TYPES ================= */
export interface CtaSection {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
landing_page_type: string;
service_type: string | null;
}
/* ================= RESPONSE ================= */
export interface HomePageResponse {
@@ -57,6 +83,8 @@ export interface HomePageResponse {
stats_sections: StatItem[];
highlight_cards: HighlightCard[];
cta_bands: CtaBand[];
cta_section: CtaSection;
testimonial_section: TestimonialItem[];
};
errors: any;
correlation_id: string;

View File

@@ -0,0 +1,122 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import baseQueryWithReauth from "./baseQuery";
export interface KautilyaPageResponse {
success: boolean;
status: number;
message: string;
data: {
hero_sections: {
id: string;
background_image_url: string;
background_image_alt_text: string;
headline: string;
subtext: string;
cta_text: string;
cta_destination: string;
};
our_story: {
id: string;
tag: string;
title: string;
content: string;
image_url: string;
};
why_choose_us: {
id: string;
tag: string;
title: string;
description: string;
cards: Array<{
id: string;
title: string;
description: string;
image_url: string;
icon: string;
display_order: number;
bullets: Array<{
id: string;
text: string;
}>;
}>;
};
facility_features: {
id: string;
title: string;
description: string;
features: Array<{
id: string;
title: string;
description: string;
image_url: string;
sub_title: string;
sub_description: string;
display_order: number;
points: Array<{
id: string;
text: string;
}>;
}>;
};
visual_tour: {
id: string;
title: string;
description: string;
categories: Array<{
id: string;
name: string;
display_order: number;
images: Array<{
id: string;
image_url: string;
title: string;
subtitle: string;
display_order: number;
}>;
}>;
};
daily_experience: {
id: string;
title: string;
description: string;
items: Array<{
id: string;
label: string;
title: string;
description: string;
image_url: string;
display_order: number;
}>;
};
cta_section: {
id: string;
background_image_url: string;
text: string;
cta_text: string;
cta_destination: string;
description: string;
};
};
errors: any;
correlation_id: string;
}
export const learningFacilityApi = createApi({
reducerPath: "learningFacilityApi",
baseQuery: baseQueryWithReauth,
tagTypes: ["KautilyaPage"],
endpoints: (builder) => ({
getKautilyaPage: builder.query<
KautilyaPageResponse["data"],
{ }
>({
query: ({ }) => ({
url: "/admin/kautilya-page/get",
}),
transformResponse: (response: KautilyaPageResponse) => response.data,
providesTags: [{ type: "KautilyaPage", id: "LIST" }],
}),
}),
});
export const { useGetKautilyaPageQuery } = learningFacilityApi;

View File

@@ -0,0 +1,128 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import baseQueryWithReauth from "./baseQuery";
/* ================= TYPES ================= */
export interface WebinarItem {
id: string;
session_title: string;
description: string | null;
session_datetime: string;
duration_minutes: number;
timezone_xid: string;
max_attendee: number;
passcode: string;
require_registration: boolean;
recurring_webinar: boolean;
webinar_status: "scheduled" | "live" | "ended" | "cancelled";
owner?: string;
}
export interface WebinarListData {
total: number;
limit: number;
offset: number;
items: WebinarItem[];
}
export interface WebinarListResponse {
success: boolean;
status: number;
message: string;
data: WebinarListData;
errors: any;
correlation_id: string;
}
/* ================= QUERY PARAM TYPE ================= */
export interface WebinarListParams {
limit?: number;
offset?: number;
search?: string;
status?: string[]; // ✅ multiple status
fromDate?: string;
toDate?: string;
minDuration?: number;
maxDuration?: number;
minAttendees?: number; // ✅ NEW
maxAttendees?: number; // ✅ NEW
sortBy?: "most_popular" | "newest" | "oldest" | "title" | "duration";
}
/* ================= API ================= */
export const webinarApi = createApi({
reducerPath: "webinarApi",
baseQuery: baseQueryWithReauth,
tagTypes: ["Webinar"],
endpoints: (builder) => ({
webinarList: builder.query<WebinarListResponse, WebinarListParams>({
query: ({
limit = 10,
offset = 0,
search,
status,
fromDate,
toDate,
minDuration,
maxDuration,
minAttendees,
maxAttendees,
sortBy,
}) => {
const params = new URLSearchParams();
params.append("limit", String(limit));
params.append("offset", String(offset));
if (search) {
params.append("search_term", search); // ✅ FIXED NAME
}
if (status && status.length > 0) {
status.forEach((s) =>
params.append("session_status", s) // ✅ array support
);
}
if (fromDate) {
params.append("from_date", fromDate);
}
if (toDate) {
params.append("to_date", toDate);
}
if (minDuration !== undefined) {
params.append("min_duration", String(minDuration));
}
if (maxDuration !== undefined) {
params.append("max_duration", String(maxDuration));
}
if (minAttendees !== undefined) {
params.append("min_attendee", String(minAttendees)); // ✅ NEW
}
if (maxAttendees !== undefined) {
params.append("max_attendee", String(maxAttendees)); // ✅ NEW
}
if (sortBy) {
params.append("sort_by", sortBy);
}
return `/admin/webinars/list?${params.toString()}`;
},
providesTags: ["Webinar"],
}),
}),
});
/* ================= EXPORT HOOK ================= */
export const { useWebinarListQuery } = webinarApi;

View File

@@ -6,6 +6,8 @@ import { blogApi } from "../services/blogApi";
import { aboutUsApi } from "../services/aboutUsApi";
import { sercicesApi } from "../services/sercicesApi";
import { courseApi } from "../services/courseApi";
import { learningFacilityApi } from "../services/learningFacilityApi";
import { webinarApi } from "../services/webinarApi";
export const store = configureStore({
reducer: {
@@ -16,6 +18,9 @@ export const store = configureStore({
[aboutUsApi.reducerPath]: aboutUsApi.reducer,
[sercicesApi.reducerPath]: sercicesApi.reducer,
[courseApi.reducerPath]: courseApi.reducer,
[learningFacilityApi.reducerPath]: learningFacilityApi.reducer,
[webinarApi.reducerPath]: webinarApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
@@ -26,6 +31,8 @@ export const store = configureStore({
aboutUsApi.middleware,
sercicesApi.middleware,
courseApi.middleware,
learningFacilityApi.middleware,
webinarApi.middleware,
),
});

View File

@@ -167,6 +167,9 @@
/* Small text */
--font-small: 0.875rem;
/* 14px */
/* Extra small text */
--font-extra-small: 0.75rem;
/* 12px */
--line-height-small: 1.5;
--font-weight-small: 400;
@@ -601,6 +604,14 @@ html {
color: var(--color-black);
}
.text-small-extra {
font-size: var(--font-extra-small);
line-height: var(--line-height-small);
font-weight: var(--font-weight-small);
font-family: var(--font-family-base);
color: var(--color-black);
}
.text-eyebrow {
font-size: var(--font-eyebrow);
line-height: var(--line-height-eyebrow);
@@ -5294,4 +5305,31 @@ html {
font-size: calc(var(--font-h2) * 0.8); /* reduce by 20% */
line-height: calc(var(--line-height-h2) * 0.9);
}
}
}
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: 20px;
}
/* Optional: Show scrollbar only on hover */
.custom-scrollbar:hover::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
}
.custom-scrollbar:hover {
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}