working left on homepage blog
This commit is contained in:
@@ -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 Kotak’s 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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
122
src/redux/services/learningFacilityApi.ts
Normal file
122
src/redux/services/learningFacilityApi.ts
Normal 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;
|
||||
128
src/redux/services/webinarApi.ts
Normal file
128
src/redux/services/webinarApi.ts
Normal 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;
|
||||
@@ -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,
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user