main #1

Merged
PriyanshuVishwakarma merged 2 commits from main into testing 2026-03-27 07:14:29 +00:00
15 changed files with 3449 additions and 3774 deletions
Showing only changes of commit f0b4719282 - Show all commits

View File

@@ -1,41 +1,34 @@
import { useState, useEffect } from 'react';
import { motion } from 'motion/react';
import {
Target,
Users,
Globe,
Lightbulb,
ArrowRight,
CheckCircle,
Star,
Award,
Brain,
TrendingUp,
Shield,
Heart,
Zap,
Eye,
BookOpen,
Brain,
CheckCircle,
Heart,
Puzzle,
Building,
ArrowLeft
Shield,
Target,
TrendingUp,
Users,
Zap
} from 'lucide-react';
import { BrandedTag } from './about/BrandedTag';
import { PrimaryCTAButton } from './PrimaryCTAButton';
import { CTABannerSection } from './CTABannerSection';
import { TestimonialsSection } from './TestimonialsSection';
import { TeamMemberModal } from './TeamMemberModal';
import { navigateTo } from './Router';
import { Button } from './ui/button';
import { motion } from 'motion/react';
import { useEffect, useState } from 'react';
import Aparna from '../assets/Aparna-Nair.png';
import Balaji from '../assets/Balaji-Chandrakumar.jpeg';
import Diju from '../assets/Diju.jpeg';
import Ramkumar from '../assets/K-Ramkumar.png';
import Muralidharan from '../assets/R-Muralidharan.png';
import Aparna from '../assets/Aparna-Nair.png';
import Swaminathan from '../assets/v-Swaminathan.jpg';
import Balaji from '../assets/Balaji-Chandrakumar.jpeg';
import Ramesh from '../assets/Ramesh-Padmanabhan.jpeg';
import Diju from '../assets/Diju.jpeg';
import Swaminathan from '../assets/v-Swaminathan.jpg';
import svgPaths from '../imports/svg-kw7r0ellyk';
import { useGetAboutUsQuery } from '../redux/services/aboutUsApi';
import { BrandedTag } from './about/BrandedTag';
import { CTABannerSection } from './CTABannerSection';
import { PrimaryCTAButton } from './PrimaryCTAButton';
import { navigateTo } from './Router';
import { TeamMemberModal } from './TeamMemberModal';
import { TestimonialsSection } from './TestimonialsSection';
import { Button } from './ui/button';
import { FullScreenLoader } from './FullScreenLoader';
// Leadership Orientations Data
const leadershipOrientations = [
@@ -272,6 +265,7 @@ After a career break, she joined KLC as Practice Head. She now co-creates leader
}
];
// Loading Skeleton Component
const AboutUsSkeleton = () => (
<div className="animate-pulse">
@@ -303,6 +297,27 @@ export function AboutUs() {
// Fetch About Us data from API
const { data: aboutUsData, isLoading, isError, error } = useGetAboutUsQuery();
const transformTestimonials = (apiTestimonials: any[]) => {
if (!apiTestimonials || apiTestimonials.length === 0) return [];
return apiTestimonials.map((testimonial, index) => ({
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
image: testimonial.profile_photo_url || undefined,
quote: testimonial.content || "",
rating: 5, // Default rating since API doesn't provide rating
isVideo: !!testimonial.video_url,
videoThumbnail: testimonial.video_thumbnail_url || testimonial.profile_photo_url, // If you have thumbnail
videoUrl: testimonial.video_url || undefined
}));
};
// Transform the testimonials
const testimonialsData = transformTestimonials(aboutUsData?.testimonials || []);
const handleMemberClick = (member: typeof staticTeamMembers[0]) => {
setSelectedMember(member);
setIsModalOpen(true);
@@ -362,8 +377,13 @@ export function AboutUs() {
}, []);
// Show loading skeleton while fetching data
if (isLoading) {
return <AboutUsSkeleton />;
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading articles..." />
</div>
);
}
// Show error state if API call fails
@@ -755,7 +775,7 @@ export function AboutUs() {
<h2 className="text-h2 mb-8">{aboutUsData?.our_team_title || "Our Team"}</h2>
<div className="max-w-4xl mx-auto text-center space-y-6">
<p className="text-body-lg text-muted leading-relaxed">
We have a team of 7 consultants and 4 young consultants. All our senior Consultants are ex-business professionals with experience ranging from 15-30 years in varied business functions and carry a deep understanding of the area they are engaging in. Two of them bring in Board room experience. Meet them
{aboutUsData?.our_team_description || "We have a team of 7 consultants and 4 young consultants. All our senior Consultants are ex-business professionals with experience ranging from 15-30 years in varied business functions and carry a deep understanding of the area they are engaging in. Two of them bring in Board room experience. Meet them"}
</p>
</div>
</motion.div>
@@ -807,500 +827,215 @@ export function AboutUs() {
</motion.div>
))}
</div>
{/* Alternative: Use API team data if needed */}
{/* {aboutUsData?.our_team && aboutUsData.our_team.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 lg:gap-12">
{aboutUsData.our_team.map((member, index) => (
<motion.div
key={member.id}
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"
>
<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}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
</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 className="space-y-2">
<h3 className="text-h4 text-black group-hover:text-primary transition-colors duration-300">
{member.name_role.split(' - ')[0]}
</h3>
<p className="text-body text-muted leading-relaxed">
{member.name_role.split(' - ')[1] || ''}
</p>
<p className="text-small text-muted">{member.bio}</p>
</div>
</motion.div>
))}
</div>
)} */}
</div>
</div>
</section>
{/* Section 5: Our Methodology (Static - unchanged) */}
<section className="py-16 lg:py-20" style={{ backgroundColor: '#FFFFFF' }}>
<div className="section-margin-x">
<div className="max-w-6xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<BrandedTag text="Our Methodology" />
<h2 className="text-h2 mb-8">Our Methodology</h2>
</motion.div>
{/* Vertical Timeline Container */}
<div className="relative max-w-6xl mx-auto">
{/* Vertical Line Background - Gray - Ends exactly at Phase 3 dot */}
<div
className="absolute left-4 top-0 w-0.5 bg-gray-300"
style={{
height: 'calc(100% - 1rem)',
zIndex: 1
}}
></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"
style={{
backgroundColor: 'var(--color-primary)',
height: '0%',
maxHeight: 'calc(100% - 1rem)',
zIndex: 2
}}
></div>
{/* Phase 1 - Understanding & Assessment */}
<div 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={{
borderColor: 'var(--color-primary)',
}}
></div>
{/* Column 1: Phase Label */}
<div className="lg:col-span-2">
<div className="branded-tag-system">
<div className="dot"></div>
<span className="text">Phase 1</span>
</div>
</div>
{/* Column 2: Main Heading */}
<div className="lg:col-span-3">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
>
<h3 className="text-h3">Understanding & Assessment</h3>
</motion.div>
</div>
{/* Column 3: Content - Description and Bullet Points */}
<div className="lg:col-span-7">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
>
<p className="text-body-lg text-muted leading-relaxed mb-6">
We believe that leadership is more than skill and style. Leadership is predicated by a person's orientations (typical behaviors) which shapes the leadership abilities and its fit to a context. The broader the fit of the orientation the more versatile a person is in his exercise of leadership in a variety of contexts.
</p>
<div className="mb-8">
<h4 className="text-h4 mb-4">Key Focus Areas:</h4>
<div className="space-y-3">
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Leadership orientations (typical behaviors) assessment.
</span>
</div>
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Action-consequence model application.
</span>
</div>
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Context alignment and strategy integration.
</span>
</div>
</div>
</div>
</motion.div>
</div>
</div>
</div>
{/* Phase 2 - Development & Practice */}
<div 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={{
borderColor: 'var(--color-primary)',
}}
></div>
{/* Column 1: Phase Label */}
<div className="lg:col-span-2">
<div className="branded-tag-system">
<div className="dot"></div>
<span className="text">Phase 2</span>
</div>
</div>
{/* Column 2: Main Heading */}
<div className="lg:col-span-3">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
viewport={{ once: true }}
>
<h3 className="text-h3">Development & Practice</h3>
</motion.div>
</div>
{/* Column 3: Content - Description and Bullet Points */}
<div className="lg:col-span-7">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
viewport={{ once: true }}
>
<p className="text-body-lg text-muted leading-relaxed mb-4">
Thus, in our engagements with our clients we work with the individual using the action-consequence model to link up one's leadership orientations (typical behaviors) to outcomes.
</p>
<p className="text-body-lg text-muted leading-relaxed mb-6">
Leadership is a social skill. Socially aware people shape the thought, emotions and actions of others better. The key to social-awareness is self-awareness. We help our learners become aware, gain insights and discover their fullest leadership potential. This in turn builds organizational leadership capacity & capabilities.
</p>
<div className="mb-8">
<h4 className="text-h4 mb-4">Development Process:</h4>
<div className="space-y-3">
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Self-awareness and insight facilitation.
</span>
</div>
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Leadership potential discovery sessions.
</span>
</div>
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Organizational capacity building.
</span>
</div>
</div>
</div>
</motion.div>
</div>
</div>
</div>
{/* Phase 3 - Implementation & Mastery */}
<div className="relative">
<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={{
borderColor: 'var(--color-primary)',
}}
></div>
{/* Column 1: Phase Label */}
<div className="lg:col-span-2">
<div className="branded-tag-system">
<div className="dot"></div>
<span className="text">Phase 3</span>
</div>
</div>
{/* Column 2: Main Heading */}
<div className="lg:col-span-3">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
viewport={{ once: true }}
>
<h3 className="text-h3">Implementation & Mastery</h3>
</motion.div>
</div>
{/* Column 3: Content - Description and Bullet Points */}
<div className="lg:col-span-7">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
viewport={{ once: true }}
>
<p className="text-body-lg text-muted leading-relaxed mb-6">
Our approach to learning works on the 'Orientation Toning process', which is using 'Restraint and Release' of thought, emotions and actions. This release is moderated according to the demands of a context, without being tied down by one's default settings.
</p>
<div className="mb-8">
<h4 className="text-h4 mb-4">Mastery Framework:</h4>
<div className="space-y-3 mb-8">
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Orientation Toning process implementation.
</span>
</div>
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Context-responsive leadership adaptation.
</span>
</div>
<div className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
Sustainable behavior change integration.
</span>
</div>
</div>
</div>
</motion.div>
</div>
</div>
</div>
{/* Our Philosophy - Full Width Figma Design */}
<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={{
borderColor: 'var(--color-primary)',
}}
></div>
<div className="pl-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.6 }}
viewport={{ once: true }}
>
<div
className="rounded-2xl p-8 lg:p-12"
style={{
background: 'linear-gradient(135deg, var(--color-primary) 0%, #030359 100%)'
}}
>
<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">Our Philosophy</h3>
</div>
{/* Right Section: Content */}
<div className="lg:w-2/3">
{/* Philosophy Description */}
<p className="text-body-white mb-8 opacity-90">
Our philosophy is that leadership begins with orientations the inner drivers and behaviors that shape who we are. These orientations combine to create leadership abilities, which in turn lead to meaningful outcomes. At Kautilya Leadership Centre, we see leadership as a journey of connecting orientations to abilities and outcomes through structured development.
</p>
{/* Three Philosophy Items */}
<div className="flex flex-col gap-4">
{/* Leadership Orientations */}
<div className="flex items-start gap-4">
<div
className="bg-[#f8c301] content-stretch flex items-center justify-center rounded-full shrink-0 size-[32px]"
>
<svg className="block size-[16px]" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
<g>
<path d="M3.33398 8H12.6673" stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
<path d={svgPaths.p2c1c9a80} stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
</g>
</svg>
</div>
<div className="flex flex-col gap-1">
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white">
Leadership Orientations
</p>
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white opacity-80">
Who am I? (inner drivers & behaviors)
</p>
</div>
</div>
{/* Leadership Abilities */}
<div className="flex items-start gap-4">
<div
className="bg-[#f8c301] content-stretch flex items-center justify-center rounded-full shrink-0 size-[32px]"
>
<svg className="block size-[16px]" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
<g>
<path d="M3.33398 8H12.6673" stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
<path d={svgPaths.p2c1c9a80} stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
</g>
</svg>
</div>
<div className="flex flex-col gap-1">
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white">
Leadership Abilities
</p>
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white opacity-80">
What I can do (competencies & skills)
</p>
</div>
</div>
{/* Leadership Outcomes */}
<div className="flex items-start gap-4">
<div
className="bg-[#f8c301] content-stretch flex items-center justify-center rounded-full shrink-0 size-[32px]"
>
<svg className="block size-[16px]" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
<g>
<path d="M3.33398 8H12.6673" stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
<path d={svgPaths.p2c1c9a80} stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
</g>
</svg>
</div>
<div className="flex flex-col gap-1">
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white">
Leadership Outcomes
</p>
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white opacity-80">
What I deliver (results for self & organization)
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</motion.div>
</div>
</div>
{/* Section 5: Our Methodology - Dynamic from API */}
{aboutUsData?.methodology && (
<section className="py-16 lg:py-20" style={{ backgroundColor: '#FFFFFF' }}>
<div className="section-margin-x">
<div className="max-w-6xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="flex justify-center items-center mt-16"
className="text-center mb-16"
>
<PrimaryCTAButton
text="Design your leadership journey"
onClick={() => navigateTo('/contact')}
ariaLabel="Contact us to design your leadership journey"
className="cta-text-black"
/>
<BrandedTag text={aboutUsData.methodology.title || "Our Methodology"} />
{aboutUsData.methodology.subtitle && (
<h2 className="text-h2 mb-8">{aboutUsData.methodology.subtitle}</h2>
)}
</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={{
height: 'calc(100% - 1rem)',
zIndex: 1
}}
></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"
style={{
backgroundColor: 'var(--color-primary)',
height: '0%',
maxHeight: 'calc(100% - 1rem)',
zIndex: 2
}}
></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={{
borderColor: 'var(--color-primary)',
}}
></div>
{/* Column 1: Phase Label */}
<div className="lg:col-span-2">
<div className="branded-tag-system">
<div className="dot"></div>
<span className="text">{phase.phase_label || `Phase ${phase.phase_number}`}</span>
</div>
</div>
{/* Column 2: Main Heading */}
<div className="lg:col-span-3">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: phaseIndex * 0.2 }}
viewport={{ once: true }}
>
<h3 className="text-h3">{phase.title}</h3>
</motion.div>
</div>
{/* Column 3: Content - Description and Bullet Points */}
<div className="lg:col-span-7">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: phaseIndex * 0.2 }}
viewport={{ once: true }}
>
<p className="text-body-lg text-muted leading-relaxed mb-6">
{phase.description}
</p>
{phase.bullet_title && phase.bullets && phase.bullets.length > 0 && (
<div className="mb-8">
<h4 className="text-h4 mb-4">{phase.bullet_title}</h4>
<div className="space-y-3">
{phase.bullets.map((bullet, bulletIndex) => (
<div key={bulletIndex} className="flex items-start gap-3">
<div
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
style={{
backgroundColor: 'var(--color-primary)',
}}
></div>
<span className="text-body text-muted">
{bullet}
</span>
</div>
))}
</div>
</div>
)}
</motion.div>
</div>
</div>
</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={{
borderColor: 'var(--color-primary)',
}}
></div>
<div className="pl-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.6 }}
viewport={{ once: true }}
>
<div
className="rounded-2xl p-8 lg:p-12"
style={{
background: 'linear-gradient(135deg, var(--color-primary) 0%, #030359 100%)'
}}
>
<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) => (
<div key={pointIndex} className="flex items-start gap-4">
<div
className="bg-[#f8c301] content-stretch flex items-center justify-center rounded-full shrink-0 size-[32px]"
>
<svg className="block size-[16px]" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
<g>
<path d="M3.33398 8H12.6673" stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
<path d={svgPaths.p2c1c9a80} stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
</g>
</svg>
</div>
<div className="flex flex-col gap-1">
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white">
{point}
</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</motion.div>
</div>
</div>
)}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
viewport={{ once: true }}
className="flex justify-center items-center mt-16"
>
<PrimaryCTAButton
text="Design your leadership journey"
onClick={() => navigateTo('/contact')}
ariaLabel="Contact us to design your leadership journey"
className="cta-text-black"
/>
</motion.div>
</div>
</div>
</div>
</div>
</section>
</section>
)}
{/* Testimonials Section */}
<TestimonialsSection
customTestimonials={testimonialsData}
title="What Our Clients Say About Us"
subtitle="Hear from leaders who have transformed their approach through our comprehensive leadership development programs."
tagText="Client Success Stories"

View File

@@ -14,21 +14,22 @@ export interface CartItem {
originalPrice?: string;
category: string;
level: string;
type?: string;
}
interface CartPopupProps {
isOpen: boolean;
onClose: () => void;
cartItems: CartItem[]; // Legacy prop - no longer used but kept for backward compatibility
onRemoveItem: (itemId: string) => void; // Legacy prop - no longer used but kept for backward compatibility
// cartItems: CartItem[]; // Legacy prop - no longer used but kept for backward compatibility
// onRemoveItem: (itemId: string) => void; // Legacy prop - no longer used but kept for backward compatibility
recentlyAddedItem?: CartItem | null;
}
export function CartPopup({
isOpen,
onClose,
cartItems: legacyCartItems, // Renamed to avoid confusion
onRemoveItem: legacyOnRemoveItem, // Renamed to avoid confusion
// cartItems: legacyCartItems, // Renamed to avoid confusion
// onRemoveItem: legacyOnRemoveItem, // Renamed to avoid confusion
recentlyAddedItem
}: CartPopupProps) {
const [showSuccess, setShowSuccess] = useState(false);

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Button } from './ui/button';
import { Badge } from './ui/badge';
import {
Users,
Clock,
import {
Users,
Clock,
Star,
ArrowRight,
ShoppingCart
@@ -47,7 +47,7 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
const handleAddToCart = (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent card click when clicking Add to Cart
if (onAddToCart) {
const cartItem: CartItem = {
id: course.id,
@@ -75,7 +75,7 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute top-4 left-4">
<Badge
<Badge
variant="secondary"
className="px-3 py-1 font-medium"
style={{
@@ -89,26 +89,12 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
{course.category}
</Badge>
</div>
<div className="absolute top-4 right-4">
<Badge
variant="outline"
className="px-3 py-1 font-medium bg-white/90 backdrop-blur-sm"
style={{
fontSize: 'var(--font-small)',
fontFamily: 'var(--font-family-base)',
borderColor: 'var(--color-primary)',
color: 'var(--color-primary)'
}}
>
{course.level}
</Badge>
</div>
</div>
{/* Card Content - Reduced horizontal padding */}
<div className="p-5 flex flex-col flex-1">
{/* Course Title */}
<h3
<h3
className="mb-3 group-hover:text-blue-600 transition-colors leading-snug"
style={{
fontSize: 'var(--font-h4)',
@@ -120,9 +106,9 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
>
{course.title}
</h3>
{/* Course Description - Limited to 2 lines with ellipsis */}
<p
<p
className="mb-5 line-clamp-2 leading-relaxed"
style={{
fontSize: 'var(--font-body)',
@@ -138,14 +124,14 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
>
{course.description}
</p>
{/* Course Meta Information - Reduced bottom margin */}
<div className="flex items-center justify-between mb-5 pt-3 border-t border-gray-100">
<div className="flex items-center gap-5">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-gray-400" />
<span style={{
fontSize: 'var(--font-small)',
<span style={{
fontSize: 'var(--font-small)',
fontFamily: 'var(--font-family-base)',
color: 'var(--color-gray-muted)',
fontWeight: '500'
@@ -155,8 +141,8 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
</div>
<div className="flex items-center gap-2">
<Users className="w-4 h-4 text-gray-400" />
<span style={{
fontSize: 'var(--font-small)',
<span style={{
fontSize: 'var(--font-small)',
fontFamily: 'var(--font-family-base)',
color: 'var(--color-gray-muted)',
fontWeight: '500'
@@ -165,10 +151,10 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
</span>
</div>
</div>
<div className="flex items-center gap-1">
<Star className="w-4 h-4 fill-current text-yellow-400" />
<span
<span
className="font-semibold"
style={{
fontSize: 'var(--font-small)',
@@ -185,7 +171,7 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
<div className="mb-5">
<div className="flex items-center justify-between">
<div className="flex items-baseline gap-3">
<span
<span
className="font-bold"
style={{
fontSize: '1.75rem',
@@ -196,7 +182,7 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
{course.price}
</span>
{course.originalPrice && (
<span
<span
className="line-through"
style={{
fontSize: 'var(--font-body)',
@@ -208,7 +194,7 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
</span>
)}
</div>
{course.originalPrice && (
{/* {course.originalPrice && (
<div className="text-right">
<span
className="text-green-600 font-semibold text-sm"
@@ -219,59 +205,61 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
Save {Math.round(((parseFloat(course.originalPrice.replace('$', '')) - parseFloat(course.price.replace('$', ''))) / parseFloat(course.originalPrice.replace('$', ''))) * 100)}%
</span>
</div>
)}
)} */}
</div>
</div>
{/* Action Buttons - Horizontal Layout with reduced gap */}
<div className="flex flex-row gap-2 mt-auto">
{/* Add to Cart Button - Outline Blue */}
{/* Add to Cart */}
<Button
variant="outline"
onClick={handleAddToCart}
className="flex-1 flex items-center justify-center gap-2 h-11 rounded-lg transition-all duration-200 font-medium"
className="flex-1 flex items-center justify-center gap-1.5 h-9 rounded-md transition-all duration-200 font-medium px-2"
style={{
borderColor: '#04045B',
color: '#04045B',
backgroundColor: 'transparent',
fontSize: 'var(--font-body)',
fontSize: '12px', // ⬅️ reduced
fontFamily: 'var(--font-family-base)',
fontWeight: '500',
borderWidth: '2px'
borderWidth: '1px',
padding: '8px'
}}
onMouseEnter={(e) => {
onMouseEnter={(e: any) => {
e.currentTarget.style.backgroundColor = '#04045B';
e.currentTarget.style.color = 'white';
}}
onMouseLeave={(e) => {
onMouseLeave={(e: any) => {
e.currentTarget.style.backgroundColor = 'transparent';
e.currentTarget.style.color = '#04045B';
}}
>
<ShoppingCart className="w-4 h-4" />
<ShoppingCart className="w-3.5 h-3.5" /> {/* ⬅️ smaller icon */}
Add to Cart
</Button>
{/* Learn More Button - Solid Blue */}
{/* Learn More */}
<Button
className="flex-1 flex items-center justify-center gap-2 h-11 rounded-lg transition-all duration-200 font-medium"
className="flex-1 flex items-center justify-center gap-1.5 h-9 rounded-md transition-all duration-200 font-medium px-2"
style={{
backgroundColor: '#04045B',
color: 'white',
fontSize: 'var(--font-body)',
fontSize: '12px', // ⬅️ reduced
fontFamily: 'var(--font-family-base)',
fontWeight: '500',
border: 'none'
border: 'none',
padding: '8px'
}}
onMouseEnter={(e) => {
onMouseEnter={(e: any) => {
e.currentTarget.style.backgroundColor = '#030359';
}}
onMouseLeave={(e) => {
onMouseLeave={(e: any) => {
e.currentTarget.style.backgroundColor = '#04045B';
}}
>
Learn More
<ArrowRight className="w-4 h-4" />
<ArrowRight className="w-3.5 h-3.5" /> {/* ⬅️ smaller icon */}
</Button>
</div>
</div>

View File

@@ -1,206 +1,48 @@
import React, { useState, useRef, useEffect } from 'react';
import { Button } from './ui/button';
import { Badge } from './ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import {
Play,
Users,
Clock,
ChevronRight,
ChevronLeft,
GraduationCap,
MessageCircle,
Zap,
Video,
Smartphone,
Award,
Building2,
BookOpen,
Star,
Globe,
Target,
TrendingUp,
Lightbulb,
CheckCircle,
ArrowRight,
Calendar,
Search,
ChevronRight,
Clock,
DollarSign,
Filter,
Grid,
List,
X,
DollarSign
Search,
Star,
Users,
X
} from 'lucide-react';
import { motion } from 'motion/react';
import { navigateTo } from './Router';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { BrandedTag } from './about/BrandedTag';
import { PrimaryCTAButton } from './PrimaryCTAButton';
import { CourseCard } from './CourseCard';
import { CartPopup, CartItem } from './CartPopup';
import { useState, useEffect, useMemo, useCallback } from 'react';
import { useCart } from './CartContext';
import { CartItem, CartPopup } from './CartPopup';
import { CourseCard } from './CourseCard';
import { ImageWithFallback } from './figma/ImageWithFallback';
import { navigateTo } from './Router';
import { Badge } from './ui/badge';
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 { useDebounce } from '../redux/hooks/useDebounce';
// Course Categories
const courseCategories = [
'Leadership Fundamentals',
'Decision Making & Strategy',
'Perspective & Risk',
'Communication & Influence',
'Change & Innovation'
];
// Helper function to parse rupee price from string (keep as is)
const parsePriceToNumber = (priceStr: string | number): number => {
if (typeof priceStr === 'number') return priceStr;
const numericStr = priceStr.toString().replace(/[^0-9.-]/g, '');
return parseFloat(numericStr) || 0;
};
// Featured Courses Data - Updated with Rupee pricing
const featuredCourses = [
{
id: '1',
title: 'Strategic Leadership Foundations',
thumbnail: 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=400&h=250&fit=crop',
duration: '12 hours',
level: 'Intermediate',
format: 'Self-paced',
rating: 4.8,
participants: '2,400+',
category: 'Leadership Fundamentals',
description: 'Master the core principles of strategic leadership and organizational vision.',
price: '₹24,817',
originalPrice: '₹33,117'
},
{
id: '2',
title: 'Data-Driven Decision Making',
thumbnail: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=400&h=250&fit=crop',
duration: '8 hours',
level: 'Advanced',
format: 'Cohort-based',
rating: 4.9,
participants: '1,800+',
category: 'Decision Making & Strategy',
description: 'Learn to make strategic decisions using data analytics and business intelligence.',
price: '₹37,267',
originalPrice: '₹45,567'
},
{
id: '3',
title: 'Risk Assessment & Management',
thumbnail: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=250&fit=crop',
duration: '10 hours',
level: 'Intermediate',
format: 'Self-paced',
rating: 4.7,
participants: '3,200+',
category: 'Perspective & Risk',
description: 'Develop expertise in identifying, analyzing, and mitigating organizational risks.',
price: '₹28,967',
originalPrice: '₹37,267'
},
{
id: '4',
title: 'Influential Communication',
thumbnail: 'https://images.unsplash.com/photo-1556761175-b413da4baf72?w=400&h=250&fit=crop',
duration: '6 hours',
level: 'Beginner',
format: 'Self-paced',
rating: 4.8,
participants: '5,100+',
category: 'Communication & Influence',
description: 'Master the art of persuasive communication and stakeholder engagement.',
price: '₹16,517',
originalPrice: '₹20,667'
},
{
id: '5',
title: 'Leading Innovation & Change',
thumbnail: 'https://images.unsplash.com/photo-1542744173-8e7e53415bb0?w=400&h=250&fit=crop',
duration: '14 hours',
level: 'Advanced',
format: 'Cohort-based',
rating: 4.9,
participants: '1,950+',
category: 'Change & Innovation',
description: 'Drive organizational transformation and foster a culture of innovation.',
price: '₹45,567',
originalPrice: '₹53,867'
},
{
id: '6',
title: 'Digital Leadership Essentials',
thumbnail: 'https://images.unsplash.com/photo-1551434678-e076c223a692?w=400&h=250&fit=crop',
duration: '9 hours',
level: 'Intermediate',
format: 'Self-paced',
rating: 4.6,
participants: '2,800+',
category: 'Leadership Fundamentals',
description: 'Navigate the digital transformation as a modern leader.',
price: '₹23,157',
originalPrice: '₹28,967'
},
{
id: '7',
title: 'Crisis Leadership Strategies',
thumbnail: 'https://images.unsplash.com/photo-1584697964358-3e14ca57658b?w=400&h=250&fit=crop',
duration: '7 hours',
level: 'Advanced',
format: 'Cohort-based',
rating: 4.7,
participants: '1,200+',
category: 'Leadership Fundamentals',
description: 'Navigate uncertainty and lead your team through challenging situations with confidence.',
price: '₹33,117',
originalPrice: '₹41,417'
},
{
id: '8',
title: 'Emotional Intelligence for Leaders',
thumbnail: 'https://images.unsplash.com/photo-1559027615-cd4628902d4a?w=400&h=250&fit=crop',
duration: '5 hours',
level: 'Beginner',
format: 'Self-paced',
rating: 4.9,
participants: '4,300+',
category: 'Communication & Influence',
description: 'Develop emotional intelligence to enhance your leadership effectiveness.',
price: '₹14,857',
originalPrice: '₹19,007'
},
{
id: 'ldp-foundations',
title: 'Strategic Leadership Development Program: Foundations',
thumbnail: 'https://images.unsplash.com/photo-1588912914078-2fe5224fd8b8?w=400&h=250&fit=crop',
duration: '40 hours',
level: 'Intermediate',
format: 'Self-paced',
rating: 4.8,
participants: '1,247+',
category: 'Leadership Development',
description: 'Master the fundamentals of effective leadership through evidence-based practices and real-world case studies.',
price: '$599',
originalPrice: '$799'
},
{
id: '9',
title: 'Strategic Risk Analysis',
thumbnail: 'https://images.unsplash.com/photo-1560472355-536de3962603?w=400&h=250&fit=crop',
duration: '11 hours',
level: 'Advanced',
format: 'Self-paced',
rating: 4.8,
participants: '1,500+',
category: 'Perspective & Risk',
description: 'Master advanced risk analysis techniques for strategic decision-making.',
price: '₹39,757',
originalPrice: '₹49,717'
}
];
// Format price with Rupee symbol (keep as is)
const formatPrice = (price: number): string => {
return `${price.toLocaleString('en-IN')}`;
};
export function LearningOnline() {
// UI state
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All Categories');
const [selectedLevel, setSelectedLevel] = useState('All Levels');
const [selectedFormat, setSelectedFormat] = useState('All Formats');
const [selectedCategoryId, setSelectedCategoryId] = useState<string>('');
const [selectedCategoryName, setSelectedCategoryName] = useState('All Categories');
const [selectedPriceRange, setSelectedPriceRange] = useState('All Prices');
const [selectedDuration, setSelectedDuration] = useState('All Durations');
const [selectedRating, setSelectedRating] = useState('All Ratings');
@@ -209,98 +51,248 @@ export function LearningOnline() {
const [currentPage, setCurrentPage] = useState(1);
const coursesPerPage = 9;
// Cart functionality - using global cart context
const { addToCart } = useCart();
const [isCartPopupOpen, setIsCartPopupOpen] = useState(false);
const [recentlyAddedItem, setRecentlyAddedItem] = useState<CartItem | null>(null);
// Debounced search term to avoid too many API calls
const debouncedSearchTerm = useDebounce(searchTerm, 500);
// Get unique values for filters - Updated for Rupees
const categories = ['All Categories', ...courseCategories];
const levels = ['All Levels', ...Array.from(new Set(featuredCourses.map(course => course.level)))];
const formats = ['All Formats', ...Array.from(new Set(featuredCourses.map(course => course.format)))];
const priceRanges = ['All Prices', 'Under ₹20,000', '₹20,000 - ₹35,000', '₹35,000 - ₹50,000', 'Over ₹50,000'];
const durations = ['All Durations', 'Under 6 hours', '6-10 hours', '10-15 hours', 'Over 15 hours'];
const ratings = ['All Ratings', '4.5+ Stars', '4.0+ Stars', '3.5+ Stars'];
const sortOptions = [
{ value: 'Most Popular', label: 'Most Popular' },
{ value: 'newest', label: 'Newest First' },
{ value: 'title', label: 'Title A-Z' },
{ value: 'price_low', label: 'Price: Low to High' },
{ value: 'price_high', label: 'Price: High to Low' },
{ value: 'rating', label: 'Highest Rated' },
{ value: 'duration', label: 'Duration' }
];
// Helper function to parse rupee price
const parseRupeePrice = (priceStr: string) => {
return parseFloat(priceStr.replace('₹', '').replace(/,/g, ''));
};
// Filter and sort courses
const filteredCourses = featuredCourses.filter(course => {
const matchesSearch = course.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
course.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
course.category.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = selectedCategory === 'All Categories' || course.category === selectedCategory;
const matchesLevel = selectedLevel === 'All Levels' || course.level === selectedLevel;
const matchesFormat = selectedFormat === 'All Formats' || course.format === selectedFormat;
// Price filter - Updated for Rupees
const price = parseRupeePrice(course.price);
const matchesPrice = selectedPriceRange === 'All Prices' ||
(selectedPriceRange === 'Under ₹20,000' && price < 20000) ||
(selectedPriceRange === '₹20,000 - ₹35,000' && price >= 20000 && price <= 35000) ||
(selectedPriceRange === '₹35,000 - ₹50,000' && price >= 35000 && price <= 50000) ||
(selectedPriceRange === 'Over ₹50,000' && price > 50000);
// Duration filter
const durationHours = parseInt(course.duration);
const matchesDuration = selectedDuration === 'All Durations' ||
(selectedDuration === 'Under 6 hours' && durationHours < 6) ||
(selectedDuration === '6-10 hours' && durationHours >= 6 && durationHours <= 10) ||
(selectedDuration === '10-15 hours' && durationHours >= 10 && durationHours <= 15) ||
(selectedDuration === 'Over 15 hours' && durationHours > 15);
// Rating filter
const matchesRating = selectedRating === 'All Ratings' ||
(selectedRating === '4.5+ Stars' && course.rating >= 4.5) ||
(selectedRating === '4.0+ Stars' && course.rating >= 4.0) ||
(selectedRating === '3.5+ Stars' && course.rating >= 3.5);
return matchesSearch && matchesCategory && matchesLevel && matchesFormat && matchesPrice && matchesDuration && matchesRating;
}).sort((a, b) => {
switch (sortBy) {
case 'Most Popular':
return parseInt(b.participants.replace(/[^\d]/g, '')) - parseInt(a.participants.replace(/[^\d]/g, ''));
case 'newest':
return a.id.localeCompare(b.id); // Assuming newer courses have higher IDs
case 'title':
return a.title.localeCompare(b.title);
case 'price_low':
return parseRupeePrice(a.price) - parseRupeePrice(b.price);
case 'price_high':
return parseRupeePrice(b.price) - parseRupeePrice(a.price);
case 'rating':
return b.rating - a.rating;
case 'duration':
return parseInt(a.duration) - parseInt(b.duration);
default:
return 0;
}
// Fetch course categories
const { data: categoriesData, isLoading: categoriesLoading } = useGetCourseCategoriesQuery({
limit: 100,
offset: 0
});
// Paginate results
const totalPages = Math.ceil(filteredCourses.length / coursesPerPage);
const sortOptions = [
{ value: 'most_popular', label: 'Most Popular' },
{ value: 'newest', label: 'Newest First' },
{ value: 'title_asc', label: 'Title A-Z' },
{ value: 'price_asc', label: 'Price: Low to High' },
{ value: 'price_desc', label: 'Price: High to Low' },
{ value: 'rating_desc', label: 'Highest Rated' },
{ value: 'duration_asc', label: 'Duration' }
];
const priceRanges = [
'All Prices',
'Under ₹20,000',
'₹20,000 - ₹35,000',
'₹35,000 - ₹50,000',
'Over ₹50,000'
];
const durations = [
'All Durations',
'Under 6 hours',
'6-10 hours',
'10-15 hours',
'Over 15 hours'
];
const ratings = [
'All Ratings',
'4.5+ Stars',
'4.0+ Stars',
'3.5+ Stars'
];
// Build categories list
const categories = useMemo(() => {
const cats = [{ id: '', name: 'All Categories' }];
if (categoriesData?.data?.items) {
categoriesData.data.items.forEach((cat: CourseCategory) => {
cats.push({ id: cat.id, name: cat.category_name });
});
}
return cats;
}, [categoriesData]);
// Helper function to convert UI price range to API format
const getPriceRangeForApi = useCallback((priceRange: string): string | undefined => {
switch (priceRange) {
case 'Under ₹20,000':
return '0-20000';
case '₹20,000 - ₹35,000':
return '20000-35000';
case '₹35,000 - ₹50,000':
return '35000-50000';
case 'Over ₹50,000':
return '50000-999999';
default:
return undefined;
}
}, []);
// Helper function to convert UI duration to API format
const getDurationForApi = useCallback((duration: string): string | undefined => {
switch (duration) {
case 'Under 6 hours':
return '0-6';
case '6-10 hours':
return '6-10';
case '10-15 hours':
return '10-15';
case 'Over 15 hours':
return '15-999';
default:
return undefined;
}
}, []);
// Helper function to convert UI rating to API format
const getRatingForApi = useCallback((rating: string): number | undefined => {
switch (rating) {
case '4.5+ Stars':
return 4.5;
case '4.0+ Stars':
return 4.0;
case '3.5+ Stars':
return 3.5;
default:
return undefined;
}
}, []);
// Helper function to convert sort option to API format
const getSortByForApi = useCallback((sort: string): string | undefined => {
switch (sort) {
case 'Most Popular':
return 'popular';
case 'newest':
return 'newest';
case 'title':
return 'title_asc';
case 'price_low':
return 'price_asc';
case 'price_high':
return 'price_desc';
case 'rating':
return 'rating_desc';
case 'duration':
return 'duration_asc';
default:
return undefined;
}
}, []);
// Build API filters based on current UI state
const apiFilters: GetCoursesParams = useMemo(() => {
const filters: GetCoursesParams = {
limit: 100,
offset: 0,
status: 'publish'
};
// Category filter
if (selectedCategoryId) {
filters.course_category = [selectedCategoryId];
}
// Search query
if (debouncedSearchTerm) {
filters.search_query = debouncedSearchTerm;
}
// Price range
const apiPriceRange = getPriceRangeForApi(selectedPriceRange);
if (apiPriceRange) {
filters.price_range = apiPriceRange;
}
// Duration range
const apiDurationRange = getDurationForApi(selectedDuration);
if (apiDurationRange) {
filters.duration_range = apiDurationRange;
}
// Rating
const apiRating = getRatingForApi(selectedRating);
if (apiRating !== undefined) {
filters.min_rating = apiRating;
}
// Sort by
const apiSortBy = getSortByForApi(sortBy);
if (apiSortBy) {
filters.sort_by = apiSortBy;
}
return filters;
}, [
selectedCategoryId,
debouncedSearchTerm,
selectedPriceRange,
selectedDuration,
selectedRating,
sortBy,
getPriceRangeForApi,
getDurationForApi,
getRatingForApi,
getSortByForApi
]);
// Fetch courses with API filters
const {
data: coursesData,
isLoading: coursesLoading,
isError,
isFetching // To show loading indicator while fetching
} = useGetCoursesQuery(apiFilters);
// Reset to page 1 when filters change
useEffect(() => {
setCurrentPage(1);
}, [
selectedCategoryId,
debouncedSearchTerm,
selectedPriceRange,
selectedDuration,
selectedRating,
sortBy
]);
// Transform API response to course format
const courses = useMemo(() => {
if (!coursesData?.data?.items) return [];
return coursesData.data.items.map((course: Course) => ({
id: course.id,
title: course.course_name,
thumbnail: course.thumbnail_img || 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=400&h=250&fit=crop',
duration: `${course.total_duration || 0} hours`,
level: 'Intermediate',
format: course.retail_type === 'public' ? 'Cohort-based' : 'Self-paced',
rating: course.avg_rating || 4.5,
participants: `${Math.floor(Math.random() * 5000) + 100}+`,
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),
course_status: course.course_status
}));
}, [coursesData]);
// Get total courses count from API response
const totalCoursesCount = coursesData?.data?.pagination_info?.total_count || 0;
// Paginate the courses (since API returns all courses based on filters, we paginate client-side)
const totalPages = Math.ceil(totalCoursesCount / coursesPerPage);
const startIndex = (currentPage - 1) * coursesPerPage;
const currentCourses = filteredCourses.slice(startIndex, startIndex + coursesPerPage);
const currentCourses = courses.slice(startIndex, startIndex + coursesPerPage);
// Handle category change
const handleCategoryChange = (value: string) => {
const selectedCat = categories.find(cat => cat.name === value);
if (selectedCat) {
setSelectedCategoryName(selectedCat.name);
setSelectedCategoryId(selectedCat.id);
} else {
setSelectedCategoryName('All Categories');
setSelectedCategoryId('');
}
};
const clearAllFilters = () => {
setSearchTerm('');
setSelectedCategory('All Categories');
setSelectedLevel('All Levels');
setSelectedFormat('All Formats');
handleCategoryChange('All Categories');
setSelectedPriceRange('All Prices');
setSelectedDuration('All Durations');
setSelectedRating('All Ratings');
@@ -308,14 +300,16 @@ export function LearningOnline() {
};
const hasActiveFilters = searchTerm ||
selectedCategory !== 'All Categories' ||
selectedLevel !== 'All Levels' ||
selectedFormat !== 'All Formats' ||
selectedCategoryName !== 'All Categories' ||
selectedPriceRange !== 'All Prices' ||
selectedDuration !== 'All Durations' ||
selectedRating !== 'All Ratings';
// Cart functions - using global cart context
// Cart functionality
const { addToCart } = useCart();
const [isCartPopupOpen, setIsCartPopupOpen] = useState(false);
const [recentlyAddedItem, setRecentlyAddedItem] = useState<CartItem | null>(null);
const handleAddToCart = (item: CartItem) => {
addToCart(item);
setRecentlyAddedItem(item);
@@ -327,9 +321,34 @@ export function LearningOnline() {
setRecentlyAddedItem(null);
};
// Show loading state
if (coursesLoading || categoriesLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-gray-600">Loading courses...</p>
</div>
</div>
);
}
// Show error state
if (isError) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold text-red-600 mb-4">Error Loading Courses</h2>
<p className="text-gray-600 mb-4">Failed to load courses. Please try again later.</p>
<Button onClick={() => window.location.reload()}>Retry</Button>
</div>
</div>
);
}
return (
<div style={{ backgroundColor: '#FFFFFF' }}>
{/* Hero Banner Digital Learning - Blog Style */}
{/* Hero Banner (keep as is) */}
<section className="relative py-16 overflow-hidden">
<div
className="absolute inset-0"
@@ -349,18 +368,13 @@ export function LearningOnline() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
{/* Eyebrow Text */}
<div className="branded-tag-system-white mb-6 justify-start">
<div className="dot"></div>
<span className="text">DIGITAL LEARNING PLATFORM</span>
</div>
{/* Main Header */}
<h1 className="text-h1-white mb-8" style={{ lineHeight: 'var(--line-height-h1)' }}>
Discover Your Leadership<br />Potential Online
</h1>
{/* Sub Text */}
<div className="max-w-5xl mb-8">
<p className="text-body-lg-white" style={{ lineHeight: '1.7' }}>
Our Leadership Courses are structured packages which are targeted towards building your leadership abilities. Each course is a wholesome package which not only helps you gain awareness about your leadership style but also gives insights to build your leadership abilities. Every course contains curated content targeted towards a specific leadership ability. Each course consists of our proprietary profiling instruments Leadership Profilers, conceptual videos and experiences of leaders Leadership Webcasts, as well as additional content to supplement learning.
@@ -371,11 +385,10 @@ export function LearningOnline() {
</div>
</section>
{/* Search and Controls Section */}
{/* Search and Controls Section (keep as is) */}
<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
@@ -392,14 +405,13 @@ export function LearningOnline() {
/>
</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
onClick={() => setViewMode('grid')}
className={`p-2 transition-colors ${viewMode === 'grid'
? 'text-white'
: 'bg-white text-gray-600 hover:bg-gray-50'
? 'text-white'
: 'bg-white text-gray-600 hover:bg-gray-50'
}`}
style={{
backgroundColor: viewMode === 'grid' ? 'var(--color-primary)' : undefined
@@ -411,8 +423,8 @@ export function LearningOnline() {
<button
onClick={() => setViewMode('list')}
className={`p-2 transition-colors ${viewMode === 'list'
? 'text-white'
: 'bg-white text-gray-600 hover:bg-gray-50'
? 'text-white'
: 'bg-white text-gray-600 hover:bg-gray-50'
}`}
style={{
backgroundColor: viewMode === 'list' ? 'var(--color-primary)' : undefined
@@ -428,7 +440,7 @@ export function LearningOnline() {
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
{sortOptions.map((option) => (
{sortOptions.map((option: any) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
@@ -481,52 +493,14 @@ export function LearningOnline() {
<label className="block text-small mb-2 font-medium text-gray-700">
Category
</label>
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
<Select value={selectedCategoryName} onValueChange={handleCategoryChange}>
<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>
{/* 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>
{/* 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 key={category.name} value={category.name} className="text-small">
{category.name}
</SelectItem>
))}
</SelectContent>
@@ -595,10 +569,13 @@ export function LearningOnline() {
</div>
</div>
{/* Right Content Area - Scrollable Courses */}
{/* Right Content Area */}
<div className="col-span-12 lg:col-span-9">
<div className="mb-4 text-small text-muted">
Showing {currentCourses.length} of {filteredCourses.length} courses
<div className="mb-4 text-small text-muted flex justify-between items-center">
<span>Showing {currentCourses.length} of {totalCoursesCount} courses</span>
{isFetching && (
<span className="text-xs text-blue-600 animate-pulse">Updating results...</span>
)}
</div>
{/* Courses Results */}
@@ -702,15 +679,18 @@ export function LearningOnline() {
<Button
variant="outline"
size="sm"
onClick={(e) => {
onClick={(e: any) => {
e.stopPropagation();
handleAddToCart({
id: course.id,
title: course.title,
price: course.price,
originalPrice: course.originalPrice,
thumbnail: course.thumbnail,
type: 'course'
category: course.category, // ✅ FIX
level: course.level, // ✅ FIX
type: 'course' // optional (if you added in interface)
});
}}
className="flex items-center gap-2 hover:bg-blue-50 hover:border-blue-300"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ import { PrimaryCTAButton } from "../components/PrimaryCTAButton";
import { BrandedTag } from "../components/about/BrandedTag";
import { useNavigate } from "react-router-dom";
import { useGetHomepageQuery } from "../redux/services/homepageApi";
import { FullScreenLoader } from "../components/FullScreenLoader";
const HomePage: React.FC = () => {
const navigate = useNavigate();
@@ -25,6 +26,14 @@ const HomePage: React.FC = () => {
const highlightCards = data?.highlight_cards ?? [];
const ctaBands = data?.cta_bands ?? [];
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<FullScreenLoader text="Loading Homepage..." />
</div>
);
}
return (
<>
<HeroSection heroSections={heroSections} isLoading={isLoading} />

View File

@@ -0,0 +1,17 @@
import { useState, useEffect } from 'react';
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}

View File

@@ -20,6 +20,17 @@ export interface HowWeWorkItem {
display_order: number;
}
export interface Testimonial {
id: string;
profile_xid: string;
name: string;
designation: string;
content: string;
video_url: string;
display_order: number;
testimonial_page_type: string;
}
export interface StatItem {
id: string;
number: number;
@@ -43,11 +54,36 @@ export interface AboutUsData {
how_we_work_title: string;
who_we_are_title: string;
our_team_title: string;
our_team_description: string;
how_we_work: HowWeWorkItem[];
stat_section: StatItem[];
our_team: TeamMember[];
methodology: Methodology;
philosophy: Philosophy;
testimonials: Testimonial[];
}
export interface Methodology {
title: string;
subtitle: string;
phases: Phase[];
}
export interface Phase {
id?: string;
phase_number: number;
phase_label: string;
title: string;
description: string;
bullet_title: string;
bullets: string[];
display_order: number;
}
export interface Philosophy {
title: string;
description: string;
points: string[];
}
export interface AboutUsResponse {
success: boolean;
status: number;

View File

@@ -0,0 +1,169 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import baseQueryWithReauth from "./baseQuery";
/* ================= TYPES ================= */
export type CourseStatus =
| "publish"
| "unpublish"
| "archive"
| "processing"
| "in_draft";
export interface GetCoursesParams {
limit?: number;
offset?: number;
status?: CourseStatus;
search_query?: string;
course_category?: string[];
price_range?: string;
duration_range?: string;
min_rating?: number;
sort_by?: string;
}
export interface Course {
id: string;
course_name: string;
course_desc: string;
thumbnail_img: string;
course_category_xid: string;
course_category_name: string;
best_value: number;
avg_rating: number;
total_reviews: number;
retail_type: string;
price: number;
is_certificate_available: boolean;
course_status: CourseStatus;
updated_at: string;
total_duration: number;
no_of_modules: number;
}
export interface PaginationInfo {
total_count: number;
limit: number;
offset: number;
applied_filters: {
status: string | null;
course_category_xid: string[] | null;
content_types_xid: string[] | null;
search_query: string | null;
price_range: string | null;
duration_range: string | null;
min_rating: number | null;
sort_by: string | null;
};
}
export interface CourseListResponse {
success: boolean;
status: number;
message: string;
data: {
pagination_info: PaginationInfo;
items: Course[];
};
}
/* ================= PREPOPULATE TYPES ================= */
export interface CourseCategory {
id: string;
category_name: string;
category_code: string;
display_order: number;
is_active: boolean;
}
export interface GetCourseCategoriesParams {
limit?: number;
offset?: number;
is_active?: boolean;
}
export interface CourseCategoriesResponse {
success: boolean;
status: number;
message: string;
data: {
pagination_info: {
total_count: number;
limit: number;
offset: number;
};
items: CourseCategory[];
};
}
/* ================= API ================= */
export const courseApi = createApi({
reducerPath: "courseApi",
baseQuery: baseQueryWithReauth,
tagTypes: ["Course", "CourseCategories"],
endpoints: (builder) => ({
// GET Courses
getCourses: builder.query<CourseListResponse, GetCoursesParams | void>({
query: (params) => {
const searchParams = new URLSearchParams();
if (params) {
if (params.limit) searchParams.append("limit", params.limit.toString());
if (params.offset) searchParams.append("offset", params.offset.toString());
if (params.status) searchParams.append("status", params.status);
if (params.search_query) searchParams.append("search_query", params.search_query);
if (params.price_range) searchParams.append("price_range", params.price_range);
if (params.duration_range) searchParams.append("duration_range", params.duration_range);
if (params.min_rating !== undefined)
searchParams.append("min_rating", params.min_rating.toString());
if (params.sort_by) searchParams.append("sort_by", params.sort_by);
// ✅ array support
if (params.course_category?.length) {
params.course_category.forEach((cat) => {
searchParams.append("course_category", cat);
});
}
}
const queryString = searchParams.toString();
return queryString
? `admin/course/list?${queryString}`
: `admin/course/list`;
},
providesTags: (result) =>
result
? [
...result.data.items.map(({ id }) => ({
type: "Course" as const,
id,
})),
{ type: "Course", id: "LIST" },
]
: [{ type: "Course", id: "LIST" }],
}),
// GET Course Categories (Prepopulate)
getCourseCategories: builder.query<CourseCategoriesResponse, GetCourseCategoriesParams | void>({
query: (params) => {
const searchParams = new URLSearchParams();
if (params) {
if (params.limit) searchParams.append("limit", params.limit.toString());
if (params.offset) searchParams.append("offset", params.offset.toString());
if (params.is_active !== undefined) searchParams.append("is_active", params.is_active.toString());
}
const queryString = searchParams.toString();
return queryString
? `admin/prepopulate/course-categories/list?${queryString}`
: `admin/prepopulate/course-categories/list`;
},
providesTags: ["CourseCategories"],
}),
}),
});
export const { useGetCoursesQuery, useGetCourseCategoriesQuery } = courseApi;

View File

@@ -0,0 +1,20 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import baseQueryWithReauth from "./baseQuery";
export const sercicesApi = createApi({
reducerPath: "sercicesApi",
baseQuery: baseQueryWithReauth,
tagTypes: ["services"],
endpoints: (builder) => ({
// GET services LIST
getServiceList: builder.query<any, { service_type: string }>({
query: ({ service_type }) => ({
url: `/admin/service-page/list`,
params: { service_type },
}),
}),
}),
});
export const { useGetServiceListQuery } = sercicesApi;

View File

@@ -4,6 +4,8 @@ import { faqApi } from "../services/faqApi";
import { contactUsApi } from "../services/contactUsApi";
import { blogApi } from "../services/blogApi";
import { aboutUsApi } from "../services/aboutUsApi";
import { sercicesApi } from "../services/sercicesApi";
import { courseApi } from "../services/courseApi";
export const store = configureStore({
reducer: {
@@ -12,6 +14,8 @@ export const store = configureStore({
[contactUsApi.reducerPath]: contactUsApi.reducer,
[blogApi.reducerPath]: blogApi.reducer,
[aboutUsApi.reducerPath]: aboutUsApi.reducer,
[sercicesApi.reducerPath]: sercicesApi.reducer,
[courseApi.reducerPath]: courseApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
@@ -20,6 +24,8 @@ export const store = configureStore({
contactUsApi.middleware,
blogApi.middleware,
aboutUsApi.middleware,
sercicesApi.middleware,
courseApi.middleware,
),
});