Merge pull request 'main' (#6) from main into testing
All checks were successful
Klc-admin-rewamp-Frontend / Build and Test PR (push) Has been skipped
Klc-admin-rewamp-Frontend / Deploying code in Server (push) Successful in 27s

Reviewed-on: #6
This commit is contained in:
2026-04-22 07:47:43 +00:00
17 changed files with 2457 additions and 2441 deletions

View File

@@ -31,7 +31,6 @@ import { WebinarsPage } from "./components/WebinarsPage";
import HomePage from './pages/HomePage'; import HomePage from './pages/HomePage';
import { AboutUs } from './components/AboutUs'; import { AboutUs } from './components/AboutUs';
import { Services } from './components/Services'; import { Services } from './components/Services';
import { LearningFacilityNew } from './components/LearningFacilityNew';
import { FooterNew } from './components/FooterNew'; import { FooterNew } from './components/FooterNew';
import { Privacy } from "./pages/Privacy"; import { Privacy } from "./pages/Privacy";
import { TermsCondition } from "./pages/TermsCondition"; import { TermsCondition } from "./pages/TermsCondition";
@@ -39,6 +38,8 @@ import { FAQ } from "./pages/FAQ";
import { LeadershipPipelineDevelopment } from "./components/services/LeadershipPipelineDevelopment"; import { LeadershipPipelineDevelopment } from "./components/services/LeadershipPipelineDevelopment";
import { LeadershipDevelopment } from "./components/services/LeadershipDevelopment"; import { LeadershipDevelopment } from "./components/services/LeadershipDevelopment";
import { KautilyaFacility } from "./components/KautilyaFacility"; import { KautilyaFacility } from "./components/KautilyaFacility";
import { LearningFacilityPage } from "./components/LearningFacilityPage";
import WebinarDetail from "./components/WebinarDetail";
// import EnrollPlaceholder from "./components/EnrollPlaceholder"; // import EnrollPlaceholder from "./components/EnrollPlaceholder";
// import ForgotPasswordPlaceholder from "./components/ForgotPasswordPlaceholder"; // import ForgotPasswordPlaceholder from "./components/ForgotPasswordPlaceholder";
// import DashboardPlaceholder from "./components/DashboardPlaceholder"; // import DashboardPlaceholder from "./components/DashboardPlaceholder";
@@ -85,6 +86,7 @@ export default function App() {
{/* Webinars Pages */} {/* Webinars Pages */}
<Route path="/webinars" element={<WebinarsPage />} /> <Route path="/webinars" element={<WebinarsPage />} />
<Route path="/webinars/:webinar_id" element={<WebinarDetail />} />
<Route path="/webinars-legacy" element={<WebinarsListing />} /> <Route path="/webinars-legacy" element={<WebinarsListing />} />
{/* Learning Online */} {/* Learning Online */}
@@ -115,7 +117,7 @@ export default function App() {
<Route path="/programme/:slug" element={<ProgrammeDetail />} /> <Route path="/programme/:slug" element={<ProgrammeDetail />} />
{/* Learning Facility */} {/* Learning Facility */}
<Route path="/learning-facility" element={<LearningFacilityNew />} /> <Route path="/learning-facility" element={<LearningFacilityPage />} />
{/* Privacy policy */} {/* Privacy policy */}
<Route path="/privacy-policy" element={<Privacy />} /> <Route path="/privacy-policy" element={<Privacy />} />
<Route path="/term-condition" element={<TermsCondition />} /> <Route path="/term-condition" element={<TermsCondition />} />

View File

@@ -12,8 +12,8 @@ interface CTABannerSectionProps {
cta_text: string; cta_text: string;
cta_destination: string; cta_destination: string;
description: string; description: string;
landing_page_type: string; landing_page_type?: string;
service_type: string | null; service_type?: string | null;
}; };
isLoading?: boolean; isLoading?: boolean;
} }

View File

@@ -104,7 +104,10 @@ export function CourseCard({ course, onClick, className, onAddToCart }: CourseCa
fontFamily: 'var(--font-family-base)' fontFamily: 'var(--font-family-base)'
}} }}
> >
{course.title} {course.title
?.split(' ')
.slice(0, 3)
.join(' ') + (course.title.split(' ').length > 3 ? '...' : '')}
</h3> </h3>
{/* Course Description - Limited to 2 lines with ellipsis */} {/* Course Description - Limited to 2 lines with ellipsis */}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -445,13 +445,13 @@ export function ProgrammeDetail({ slug }: ProgrammeDetailProps) {
courseDetail.is_certificate_available ? 'Certified' : 'Non-Certified' courseDetail.is_certificate_available ? 'Certified' : 'Non-Certified'
].filter(Boolean), ].filter(Boolean),
previewVideoUrl: previewVideoUrl:
courseDetail.reviews.find((review) => review.video_url)?.video_url || (courseDetail.reviews ?? []).find((review) => review.video_url)?.video_url ||
mockProgrammeData.previewVideoUrl, mockProgrammeData.previewVideoUrl,
highlights: highlights:
courseDetail.course_learning_outcomes.slice(0, 5).map((item) => item.title) || (courseDetail.course_learning_outcomes ?? []).slice(0, 5).map((item) => item.title) ||
mockProgrammeData.highlights, mockProgrammeData.highlights,
deliveryMethods: courseDetail.modules.length deliveryMethods: (courseDetail.modules ?? []).length
? courseDetail.modules.map((module) => module.module_name) ? (courseDetail.modules ?? []).map((module) => module.module_name)
: mockProgrammeData.deliveryMethods, : mockProgrammeData.deliveryMethods,
credentials: credentials:
courseDetail.course_certificate?.program_title || courseDetail.course_certificate?.program_title ||
@@ -476,9 +476,9 @@ export function ProgrammeDetail({ slug }: ProgrammeDetailProps) {
return courseDetail.modules.map((module, index) => ({ return courseDetail.modules.map((module, index) => ({
moduleNumber: index + 1, moduleNumber: index + 1,
title: module.module_name, title: module.module_name,
duration: `${module.lessons.length} lesson${module.lessons.length === 1 ? '' : 's'}`, duration: `${(module.lessons ?? []).length} lesson${(module.lessons ?? []).length === 1 ? '' : 's'}`,
deliveryStyle: module.lessons.some((lesson) => !lesson.is_lock_lesson) ? 'Self-Paced' : 'Locked', deliveryStyle: (module.lessons ?? []).some((lesson) => !lesson.is_lock_lesson) ? 'Self-Paced' : 'Locked',
topics: module.lessons.map((lesson) => lesson.lesson_title) topics: (module.lessons ?? []).map((lesson) => lesson.lesson_title)
})); }));
}, [courseDetail]); }, [courseDetail]);
@@ -493,7 +493,7 @@ export function ProgrammeDetail({ slug }: ProgrammeDetailProps) {
bio: member.faculty_biography, bio: member.faculty_biography,
image: '', image: '',
linkedinUrl: '', linkedinUrl: '',
credentials: member.credentials.map((credential) => credential.credential_name), credentials: (member.credentials ?? []).map((credential) => credential.credential_name),
expertise: member.expertises || [] expertise: member.expertises || []
})); }));
}, [courseDetail]); }, [courseDetail]);
@@ -536,7 +536,9 @@ export function ProgrammeDetail({ slug }: ProgrammeDetailProps) {
const useCases = useMemo(() => { const useCases = useMemo(() => {
if (!courseDetail?.modules?.length) return mockUseCases; if (!courseDetail?.modules?.length) return mockUseCases;
return courseDetail.modules.flatMap((module) => module.lessons.map((lesson) => lesson.lesson_title)).slice(0, 6); return courseDetail.modules
.flatMap((module) => (module.lessons ?? []).map((lesson) => lesson.lesson_title))
.slice(0, 6);
}, [courseDetail]); }, [courseDetail]);
const relatedProgrammes = mockRelatedProgrammes; const relatedProgrammes = mockRelatedProgrammes;
@@ -1006,16 +1008,8 @@ export function ProgrammeDetail({ slug }: ProgrammeDetailProps) {
<Dialog open={showCertificatePreview} onOpenChange={setShowCertificatePreview}> <Dialog open={showCertificatePreview} onOpenChange={setShowCertificatePreview}>
<DialogPortal> <DialogPortal>
<DialogOverlay className="fixed inset-0 bg-black/70 z-[9999]" /> <DialogOverlay className="fixed inset-0 bg-black/70 z-[9999]" />
<DialogContent className="fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] w-full max-w-3xl z-[9999] focus:outline-none bg-transparent border-none shadow-none p-0"> <DialogContent className="fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] w-full max-w-4xl z-[9999] focus:outline-none bg-transparent border-none shadow-none p-0">
<div className="relative"> <div className="relative">
{/* <DialogClose asChild>
<button
className="absolute -top-12 right-0 text-white hover:bg-white/20 rounded-full p-1 z-10 focus:outline-none"
aria-label="Close"
>
<X className="w-5 h-5" />
</button>
</DialogClose> */}
{/* Certificate Card - Authentic Certificate Style */} {/* Certificate Card - Authentic Certificate Style */}
<div className="bg-white rounded-lg shadow-2xl overflow-hidden max-h-[90vh] overflow-y-auto custom-scrollbar"> <div className="bg-white rounded-lg shadow-2xl overflow-hidden max-h-[90vh] overflow-y-auto custom-scrollbar">

View File

@@ -41,19 +41,53 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react'; import { motion, AnimatePresence } from 'motion/react';
import { navigateTo } from './Router'; import { navigateTo } from './Router';
import { useParams } from 'react-router-dom';
import { ImageWithFallback } from './figma/ImageWithFallback'; import { ImageWithFallback } from './figma/ImageWithFallback';
import { BrandedTag } from './about/BrandedTag'; import { BrandedTag } from './about/BrandedTag';
import { PrimaryCTAButton } from './PrimaryCTAButton'; import { PrimaryCTAButton } from './PrimaryCTAButton';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { getWebinarBySlug, sharedWebinarsData, type WebinarData } from '../data/webinarsData'; import { useGetWebinarByIdQuery } from '../redux/services/webinarApi';
interface WebinarDetailProps { interface WebinarDetailProps {
params: { slug: string }; params?: { webinar_id: string };
} }
export default function WebinarDetail({ params }: WebinarDetailProps) { // Helper function to format date
// Get webinar data from shared data source const formatDate = (dateTimeString: string) => {
const [webinar, setWebinar] = useState<WebinarData | null>(null); const date = new Date(dateTimeString);
return {
date: date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}),
time: date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true
}),
full: date
};
};
// Helper to get webinar status based on date and API status
const getWebinarStatus = (sessionDatetime: string, webinarStatus: string): 'upcoming' | 'live' | 'recorded' => {
const now = new Date();
const sessionDate = new Date(sessionDatetime);
if (webinarStatus === 'cancelled') return 'recorded';
if (webinarStatus === 'live') return 'live';
if (sessionDate > now) return 'upcoming';
return 'recorded';
};
export default function WebinarDetail({ params }: WebinarDetailProps = {}) {
const routeParams = useParams<{ webinar_id: string }>();
const webinarId = routeParams.webinar_id || params?.webinar_id;
const { data: webinarData, isLoading, error } = useGetWebinarByIdQuery(webinarId as string, {
skip: !webinarId
});
const [timeLeft, setTimeLeft] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0 }); const [timeLeft, setTimeLeft] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0 });
const [isRegistered, setIsRegistered] = useState(false); const [isRegistered, setIsRegistered] = useState(false);
const [showRegistrationForm, setShowRegistrationForm] = useState(false); const [showRegistrationForm, setShowRegistrationForm] = useState(false);
@@ -75,22 +109,76 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
const videoRef = useRef<HTMLVideoElement>(null); const videoRef = useRef<HTMLVideoElement>(null);
const shareMenuRef = useRef<HTMLDivElement>(null); const shareMenuRef = useRef<HTMLDivElement>(null);
// Load webinar data on component mount // Transform API data to match your component's expected format
useEffect(() => { const webinar = webinarData?.data ? {
const webinarData = getWebinarBySlug(params.slug); id: webinarData.data.id,
if (webinarData) { title: webinarData.data.session_title,
setWebinar(webinarData); description: webinarData.data.description || '',
} else { thumbnail: webinarData.data.media?.file_name
// Fallback: redirect to webinars page if webinar not found ? `/api/media/${webinarData.data.media.id}`
navigateTo('/webinars'); : '/images/default-webinar.jpg',
} theme: webinarData.data.webinar_category_id || 'Webinar',
}, [params.slug]); date: formatDate(webinarData.data.session_datetime).date,
time: formatDate(webinarData.data.session_datetime).time,
timezone: 'IST', // You might need to fetch timezone name from timezone_xid
duration: `${webinarData.data.duration_minutes} minutes`,
status: getWebinarStatus(webinarData.data.session_datetime, webinarData.data.webinar_status),
attendees: `${webinarData.data.max_attendee}`, // Max capacity or registered count
maxAttendees: webinarData.data.max_attendee,
registrationOpen: webinarData.data.require_registration && webinarData.data.webinar_status === 'scheduled',
recordingReady: webinarData.data.webinar_status === 'ended',
zoomUrl: '#', // Not provided in API, might need additional endpoint
recordingUrl: '#', // Not provided in API
price: 'Free', // Not in API, adjust as needed
format: 'Virtual Event',
host: {
name: webinarData.data.speakers?.[0]?.name || 'Host Name',
title: webinarData.data.speakers?.[0]?.designation || 'Host',
company: webinarData.data.speakers?.[0]?.company || '',
bio: webinarData.data.speakers?.[0]?.bio || '',
avatar: webinarData.data.speakers?.[0]?.image_url || '/images/default-avatar.jpg',
linkedin: '#'
},
panelists: webinarData.data.speakers?.filter(s => !s.is_host).map(speaker => ({
id: speaker.id,
name: speaker.name,
title: speaker.designation,
company: speaker.company,
bio: speaker.bio,
avatar: speaker.image_url || '/images/default-avatar.jpg',
linkedin: '#'
})) || [],
abstract: webinarData.data.about_this_session?.description || webinarData.data.description || '',
keyTakeaways: webinarData.data.about_this_session?.points?.map(p => p.point) || [],
agenda: webinarData.data.agenda_items?.map(item => ({
time: formatDate(item.start_time).time,
title: item.title,
description: item.description
})) || [],
faqs: [] as Array<{ question: string; answer: string }>, // Not in API response
tags: [] as string[], // Not in API response, derive from category or other fields
relatedProgrammes: webinarData.data.course_links?.map(courseLink => ({
id: courseLink.course.id,
slug: courseLink.course.course_name.toLowerCase().replace(/\s+/g, '-'),
title: courseLink.course.course_name,
description: courseLink.course.course_desc,
category: courseLink.course.course_category_name,
level: courseLink.course.retail_type === 'private' ? 'Private' : 'Public',
duration: courseLink.course.total_duration ? `${courseLink.course.total_duration} mins` : 'Self-paced',
participants: courseLink.course.total_reviews || 0,
rating: courseLink.course.avg_rating || 0,
price: courseLink.course.price ? `${courseLink.course.price}` : 'Free',
originalPrice: courseLink.course.best_value ? `${courseLink.course.best_value}` : `${courseLink.course.price}`,
image: courseLink.course.thumbnail_img || '/images/default-course.jpg',
bestValue: courseLink.course.best_value
})) || []
} : null;
// Countdown timer // Countdown timer
useEffect(() => { useEffect(() => {
if (!webinar || webinar.status !== 'upcoming') return; if (!webinar || webinar.status !== 'upcoming' || !webinarData?.data.session_datetime) return;
const targetDate = new Date(`${webinar.date}T${webinar.time}:00`); const targetDate = new Date(webinarData.data.session_datetime);
const updateCountdown = () => { const updateCountdown = () => {
const now = new Date().getTime(); const now = new Date().getTime();
@@ -110,7 +198,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
const interval = setInterval(updateCountdown, 1000); const interval = setInterval(updateCountdown, 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [webinar?.date, webinar?.time, webinar?.status]); }, [webinar, webinarData?.data.session_datetime]);
// Close share menu when clicking outside // Close share menu when clicking outside
useEffect(() => { useEffect(() => {
@@ -129,7 +217,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
e.preventDefault(); e.preventDefault();
setIsSubmittingRegistration(true); setIsSubmittingRegistration(true);
// Simulate API call // TODO: Integrate with your registration API endpoint
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
setIsSubmittingRegistration(false); setIsSubmittingRegistration(false);
@@ -163,7 +251,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
if (!webinar) return { text: 'Loading...', onClick: () => {}, disabled: true }; if (!webinar) return { text: 'Loading...', onClick: () => {}, disabled: true };
const now = new Date(); const now = new Date();
const webinarDate = new Date(`${webinar.date}T${webinar.time}:00`); const webinarDate = webinarData?.data.session_datetime ? new Date(webinarData.data.session_datetime) : new Date();
const tenMinutesBefore = new Date(webinarDate.getTime() - 10 * 60 * 1000); const tenMinutesBefore = new Date(webinarDate.getTime() - 10 * 60 * 1000);
switch (webinar.status) { switch (webinar.status) {
@@ -177,7 +265,10 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
if (now >= tenMinutesBefore) { if (now >= tenMinutesBefore) {
return { return {
text: 'Launch in Zoom', text: 'Launch in Zoom',
onClick: () => window.open(webinar.zoomUrl, '_blank'), onClick: () => {
// You'll need to get the Zoom URL from a separate endpoint
toast.info('Zoom link would open here');
},
disabled: false disabled: false
}; };
} else { } else {
@@ -256,7 +347,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
}; };
// Show loading state while webinar data is loading // Show loading state while webinar data is loading
if (!webinar) { if (isLoading) {
return ( return (
<div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: '#FFFFFF' }}> <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: '#FFFFFF' }}>
<div className="text-center"> <div className="text-center">
@@ -267,7 +358,26 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
); );
} }
// Show error state
if (error || !webinar) {
return (
<div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: '#FFFFFF' }}>
<div className="text-center">
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
<h2 className="text-h2 mb-2">Webinar Not Found</h2>
<p className="text-body text-gray-600 mb-6">The webinar you're looking for doesn't exist or has been removed.</p>
<PrimaryCTAButton
text="Browse Webinars"
onClick={() => navigateTo('/webinars')}
className="cta-text-black"
/>
</div>
</div>
);
}
const ctaProps = getCTAProps(); const ctaProps = getCTAProps();
const sessionDateTime = webinarData?.data.session_datetime ? new Date(webinarData.data.session_datetime) : null;
return ( return (
<div className="min-h-screen" style={{ backgroundColor: '#FFFFFF' }}> <div className="min-h-screen" style={{ backgroundColor: '#FFFFFF' }}>
@@ -298,7 +408,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
</p> </p>
{/* Countdown Timer for Upcoming Webinars */} {/* Countdown Timer for Upcoming Webinars */}
{webinar.status === 'upcoming' && ( {webinar.status === 'upcoming' && sessionDateTime && (
<div className="mb-8"> <div className="mb-8">
<div className="grid grid-cols-4 gap-4 max-w-md mb-4"> <div className="grid grid-cols-4 gap-4 max-w-md mb-4">
{[ {[
@@ -318,12 +428,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
))} ))}
</div> </div>
<div className="text-small-white opacity-80"> <div className="text-small-white opacity-80">
Starts {new Date(`${webinar.date}T${webinar.time}:00`).toLocaleDateString('en-US', { Starts {webinar.date} at {webinar.time}
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})} at {webinar.time} {webinar.timezone}
</div> </div>
</div> </div>
)} )}
@@ -337,11 +442,11 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<span className="text-small font-medium">LIVE NOW</span> <span className="text-small font-medium">LIVE NOW</span>
</div> </div>
<div className="text-small-white"> <div className="text-small-white">
{webinar.attendees} people watching Max capacity: {webinar.maxAttendees} attendees
</div> </div>
</div> </div>
<div className="text-body-lg-white"> <div className="text-body-lg-white">
Session in progress Ends at {webinar.endTime} {webinar.timezone} Session in progress
</div> </div>
</div> </div>
)} )}
@@ -356,7 +461,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
)} )}
</div> </div>
<div className="text-body-lg-white"> <div className="text-body-lg-white">
Session held on {new Date(`${webinar.date}T${webinar.time}:00`).toLocaleDateString('en-US', { Session held on {sessionDateTime?.toLocaleDateString('en-US', {
month: 'long', month: 'long',
day: 'numeric', day: 'numeric',
year: 'numeric' year: 'numeric'
@@ -425,7 +530,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<div className="flex items-center gap-2 text-muted"> <div className="flex items-center gap-2 text-muted">
<Users className="w-4 h-4" /> <Users className="w-4 h-4" />
<span className="text-small"> <span className="text-small">
{webinar.attendees} {webinar.status === 'live' ? 'watching' : 'registered'} {webinar.attendees} {webinar.status === 'live' ? 'watching' : 'capacity'}
</span> </span>
</div> </div>
</div> </div>
@@ -445,6 +550,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
{webinar.abstract} {webinar.abstract}
</p> </p>
{webinar.keyTakeaways.length > 0 && (
<div> <div>
<h3 className="text-h3 mb-4">What You'll Learn</h3> <h3 className="text-h3 mb-4">What You'll Learn</h3>
<ul className="space-y-3"> <ul className="space-y-3">
@@ -456,10 +562,12 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
))} ))}
</ul> </ul>
</div> </div>
)}
</div> </div>
</section> </section>
{/* Agenda Timeline */} {/* Agenda Timeline */}
{webinar.agenda.length > 0 && (
<section> <section>
<h2 className="text-h2 mb-6">Session Agenda</h2> <h2 className="text-h2 mb-6">Session Agenda</h2>
<div className="space-y-6"> <div className="space-y-6">
@@ -484,6 +592,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
))} ))}
</div> </div>
</section> </section>
)}
{/* Host & Panelists */} {/* Host & Panelists */}
<section> <section>
@@ -503,6 +612,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<h3 className="text-h4 mb-1">{webinar.host.name}</h3> <h3 className="text-h4 mb-1">{webinar.host.name}</h3>
<p className="text-small text-[#04045B] font-medium">HOST</p> <p className="text-small text-[#04045B] font-medium">HOST</p>
</div> </div>
{webinar.host.linkedin !== '#' && (
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -511,6 +621,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
> >
<ExternalLink className="w-4 h-4" /> <ExternalLink className="w-4 h-4" />
</Button> </Button>
)}
</div> </div>
<p className="text-small text-muted mb-1">{webinar.host.title}</p> <p className="text-small text-muted mb-1">{webinar.host.title}</p>
<p className="text-small text-muted">{webinar.host.company}</p> <p className="text-small text-muted">{webinar.host.company}</p>
@@ -535,6 +646,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<h3 className="text-h4 mb-1">{panelist.name}</h3> <h3 className="text-h4 mb-1">{panelist.name}</h3>
<p className="text-small text-[#F8C301] font-medium">SPEAKER</p> <p className="text-small text-[#F8C301] font-medium">SPEAKER</p>
</div> </div>
{panelist.linkedin !== '#' && (
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -543,6 +655,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
> >
<ExternalLink className="w-4 h-4" /> <ExternalLink className="w-4 h-4" />
</Button> </Button>
)}
</div> </div>
<p className="text-small text-muted mb-1">{panelist.title}</p> <p className="text-small text-muted mb-1">{panelist.title}</p>
<p className="text-small text-muted">{panelist.company}</p> <p className="text-small text-muted">{panelist.company}</p>
@@ -604,7 +717,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<div className="text-center mb-6"> <div className="text-center mb-6">
<div className="text-h3 mb-2">{webinar.price}</div> <div className="text-h3 mb-2">{webinar.price}</div>
<div className="text-small text-muted"> <div className="text-small text-muted">
{webinar.maxAttendees - parseInt(webinar.attendees.replace(/\D/g, '')) || 0} spots remaining {webinar.maxAttendees} spots available
</div> </div>
</div> </div>
@@ -612,15 +725,8 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Calendar className="w-5 h-5 text-[#04045B]" /> <Calendar className="w-5 h-5 text-[#04045B]" />
<div> <div>
<div className="text-body font-medium"> <div className="text-body font-medium">{webinar.date}</div>
{new Date(`${webinar.date}T${webinar.time}:00`).toLocaleDateString('en-US', { <div className="text-small text-muted">{webinar.time}</div>
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</div>
<div className="text-small text-muted">{webinar.time} {webinar.timezone}</div>
</div> </div>
</div> </div>
@@ -636,7 +742,7 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Users className="w-5 h-5 text-[#04045B]" /> <Users className="w-5 h-5 text-[#04045B]" />
<div className="text-body">{webinar.attendees} registered</div> <div className="text-body">Capacity: {webinar.attendees} attendees</div>
</div> </div>
</div> </div>
@@ -863,9 +969,9 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
{/* Add to Cart Button - Outline Blue */} {/* Add to Cart Button - Outline Blue */}
<Button <Button
variant="outline" variant="outline"
onClick={(e) => { onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation(); e.stopPropagation();
// Add to cart functionality would go here toast.info('Add to cart functionality would go here');
}} }}
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-2 h-11 rounded-lg transition-all duration-200 font-medium"
style={{ style={{
@@ -877,11 +983,11 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
fontWeight: '500', fontWeight: '500',
borderWidth: '2px' borderWidth: '2px'
}} }}
onMouseEnter={(e) => { onMouseEnter={(e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.style.backgroundColor = '#04045B'; e.currentTarget.style.backgroundColor = '#04045B';
e.currentTarget.style.color = 'white'; e.currentTarget.style.color = 'white';
}} }}
onMouseLeave={(e) => { onMouseLeave={(e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.style.backgroundColor = 'transparent'; e.currentTarget.style.backgroundColor = 'transparent';
e.currentTarget.style.color = '#04045B'; e.currentTarget.style.color = '#04045B';
}} }}
@@ -901,10 +1007,10 @@ export default function WebinarDetail({ params }: WebinarDetailProps) {
fontWeight: '500', fontWeight: '500',
border: 'none' border: 'none'
}} }}
onMouseEnter={(e) => { onMouseEnter={(e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.style.backgroundColor = '#030359'; e.currentTarget.style.backgroundColor = '#030359';
}} }}
onMouseLeave={(e) => { onMouseLeave={(e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.style.backgroundColor = '#04045B'; e.currentTarget.style.backgroundColor = '#04045B';
}} }}
> >

View File

@@ -193,7 +193,7 @@ export function Webinars() {
const WebinarCard = ({ webinar }: { webinar: WebinarItem }) => { const WebinarCard = ({ webinar }: { webinar: WebinarItem }) => {
const handleCardClick = () => { const handleCardClick = () => {
if (webinar.webinar_status !== 'cancelled') { if (webinar.webinar_status !== 'cancelled') {
navigateTo(`/webinar/${webinar.id}`); navigateTo(`/webinars/${webinar.id}`);
} }
}; };

View File

@@ -111,7 +111,7 @@ export const aboutUsApi = createApi({
// ✅ GET About Us // ✅ GET About Us
getAboutUs: builder.query<AboutUsData, void>({ getAboutUs: builder.query<AboutUsData, void>({
query: () => ({ query: () => ({
url: "/admin/about-us", url: "/guest/about-us",
method: "GET", method: "GET",
}), }),

View File

@@ -104,7 +104,7 @@ export const blogApi = createApi({
if (tag_id && tag_id !== "all") params.tag_id = tag_id; if (tag_id && tag_id !== "all") params.tag_id = tag_id;
return { return {
url: "/admin/blogs/list", url: "/guest/blogs/list",
method: "GET", method: "GET",
params, params,
}; };
@@ -114,7 +114,7 @@ export const blogApi = createApi({
getBlogByID: builder.query<BlogItem, string>({ getBlogByID: builder.query<BlogItem, string>({
query: (id) => ({ query: (id) => ({
url: `/admin/blogs/list/${id}`, url: `/guest/blogs/list/${id}`,
method: "GET", method: "GET",
}), }),
transformResponse: (response: BlogByIdResponse) => response.data, transformResponse: (response: BlogByIdResponse) => response.data,

View File

@@ -10,7 +10,7 @@ export const contactUsApi = createApi({
// GET Lead Categories // GET Lead Categories
getLeadCategories: builder.query({ getLeadCategories: builder.query({
query: ({ limit = 10, offset = 0, status = "active" }) => ({ query: ({ limit = 10, offset = 0, status = "active" }) => ({
url: "admin/prepopulate/lead-categories/list", url: "guest/prepopulate/lead-categories/list",
params: { params: {
limit, limit,
offset, offset,
@@ -23,7 +23,7 @@ export const contactUsApi = createApi({
// CREATE Lead // CREATE Lead
createLead: builder.mutation({ createLead: builder.mutation({
query: (body) => ({ query: (body) => ({
url: "admin/leads/create", url: "guest/leads/create",
method: "POST", method: "POST",
body, body,
}), }),

View File

@@ -321,8 +321,8 @@ export const courseApi = createApi({
const queryString = searchParams.toString(); const queryString = searchParams.toString();
return queryString return queryString
? `admin/course/public/list?${queryString}` ? `guest/course/public/list?${queryString}`
: `admin/course/public/list`; : `guest/course/public/list`;
}, },
providesTags: (result) => providesTags: (result) =>
@@ -348,15 +348,15 @@ export const courseApi = createApi({
} }
const queryString = searchParams.toString(); const queryString = searchParams.toString();
return queryString return queryString
? `admin/prepopulate/course-categories/list?${queryString}` ? `guest/prepopulate/course-categories/list?${queryString}`
: `admin/prepopulate/course-categories/list`; : `guest/prepopulate/course-categories/list`;
}, },
providesTags: ["CourseCategories"], providesTags: ["CourseCategories"],
}), }),
// GET Course By Id // GET Course By Id
getcoursebyid: builder.query<CourseDetailResponse, string>({ getcoursebyid: builder.query<CourseDetailResponse, string>({
query: (course_id) => `admin/course/${course_id}`, query: (course_id) => `guest/course/${course_id}`,
providesTags: (_result, _error, course_id) => [{ type: "Course", id: course_id }], providesTags: (_result, _error, course_id) => [{ type: "Course", id: course_id }],
}), }),
@@ -368,4 +368,5 @@ export const {
useGetCoursesQuery, useGetCoursesQuery,
useGetCourseCategoriesQuery, useGetCourseCategoriesQuery,
useGetcoursebyidQuery, useGetcoursebyidQuery,
} = courseApi; } = courseApi;

View File

@@ -10,7 +10,7 @@ export const faqApi = createApi({
// GET FAQs LIST // GET FAQs LIST
getFaqs: builder.query({ getFaqs: builder.query({
query: ({ limit = 10, offset = 0, search_term, content_status, content_category_xid }) => ({ query: ({ limit = 10, offset = 0, search_term, content_status, content_category_xid }) => ({
url: "admin/faq/list", url: "guest/faq/list",
params: { params: {
limit, limit,
offset, offset,
@@ -25,7 +25,7 @@ export const faqApi = createApi({
// GET category TAGS LIST // GET category TAGS LIST
getFaqCategories: builder.query({ getFaqCategories: builder.query({
query: ({ limit = 10, offset = 0, search_query }) => ({ query: ({ limit = 10, offset = 0, search_query }) => ({
url: "admin/prepopulate/content-categories/list", url: "guest/prepopulate/content-categories/list",
params: { params: {
limit, limit,
offset, offset,

View File

@@ -103,7 +103,7 @@ export const homepageApi = createApi({
{ landing_page_type: "home" | "services" | "about_us" } { landing_page_type: "home" | "services" | "about_us" }
>({ >({
query: ({ landing_page_type }) => ({ query: ({ landing_page_type }) => ({
url: "/admin/home-page/list", url: "/guest/home-page/list",
params: { landing_page_type }, params: { landing_page_type },
}), }),
@@ -114,7 +114,7 @@ export const homepageApi = createApi({
getFeaturedBlogs: builder.query({ getFeaturedBlogs: builder.query({
query: ({ limit = 3 }) => ({ query: ({ limit = 3 }) => ({
url: `/admin/blogs/featured?limit=${limit}`, url: `/guest/blogs/featured?limit=${limit}`,
method: 'GET', method: 'GET',
}), }),
transformResponse: (response: any) => { transformResponse: (response: any) => {

View File

@@ -14,7 +14,15 @@ export interface KautilyaPageResponse {
subtext: string; subtext: string;
cta_text: string; cta_text: string;
cta_destination: string; cta_destination: string;
images: Array<{ // ✅ FIX ADDED
id: string;
hero_section_xid: string;
image_url: string;
alt_text: string;
display_order: number;
}>;
}; };
our_story: { our_story: {
id: string; id: string;
tag: string; tag: string;
@@ -22,6 +30,7 @@ export interface KautilyaPageResponse {
content: string; content: string;
image_url: string; image_url: string;
}; };
why_choose_us: { why_choose_us: {
id: string; id: string;
tag: string; tag: string;
@@ -40,6 +49,7 @@ export interface KautilyaPageResponse {
}>; }>;
}>; }>;
}; };
facility_features: { facility_features: {
id: string; id: string;
title: string; title: string;
@@ -58,6 +68,7 @@ export interface KautilyaPageResponse {
}>; }>;
}>; }>;
}; };
visual_tour: { visual_tour: {
id: string; id: string;
title: string; title: string;
@@ -75,6 +86,7 @@ export interface KautilyaPageResponse {
}>; }>;
}>; }>;
}; };
daily_experience: { daily_experience: {
id: string; id: string;
title: string; title: string;
@@ -88,6 +100,7 @@ export interface KautilyaPageResponse {
display_order: number; display_order: number;
}>; }>;
}; };
cta_section: { cta_section: {
id: string; id: string;
background_image_url: string; background_image_url: string;
@@ -97,6 +110,7 @@ export interface KautilyaPageResponse {
description: string; description: string;
}; };
}; };
errors: any; errors: any;
correlation_id: string; correlation_id: string;
} }
@@ -111,7 +125,7 @@ export const learningFacilityApi = createApi({
{ } { }
>({ >({
query: ({ }) => ({ query: ({ }) => ({
url: "/admin/kautilya-page/get", url: "/guest/kautilya-page/get",
}), }),
transformResponse: (response: KautilyaPageResponse) => response.data, transformResponse: (response: KautilyaPageResponse) => response.data,
providesTags: [{ type: "KautilyaPage", id: "LIST" }], providesTags: [{ type: "KautilyaPage", id: "LIST" }],

View File

@@ -10,7 +10,7 @@ export const sercicesApi = createApi({
getServiceList: builder.query<any, { service_type: string }>({ getServiceList: builder.query<any, { service_type: string }>({
query: ({ service_type }) => ({ query: ({ service_type }) => ({
url: `/admin/service-page/list`, url: `/guest/service-page/list`,
params: { service_type }, params: { service_type },
}), }),
}), }),

View File

@@ -50,6 +50,92 @@ export interface WebinarListParams {
sortBy?: "most_popular" | "newest" | "oldest" | "title" | "duration"; sortBy?: "most_popular" | "newest" | "oldest" | "title" | "duration";
} }
export interface WebinarDetailResponse {
success: boolean;
status: number;
message: string;
data: {
id: string;
session_title: string;
description: string | null;
session_datetime: string;
duration_minutes: number;
timezone_xid: string;
webinar_category_id: string;
max_attendee: number;
passcode: string;
require_registration: boolean;
recurring_webinar: boolean;
webinar_status: "scheduled" | "live" | "ended" | "cancelled";
type: string;
media: {
id: string;
file_name: string;
file_type: string;
file_extension: string;
} | null;
about_this_session: {
description: string;
points: Array<{
id: string;
about_id: string;
point: string;
sort_order: number;
}>;
} | null;
agenda_items: Array<{
id: string;
webinar_xid: string;
start_time: string;
end_time: string;
title: string;
description: string;
}>;
speakers: Array<{
id: string;
webinar_xid: string;
name: string;
designation: string;
company: string;
bio: string;
image_url: string;
is_host: boolean;
}>;
faqs: any;
course_links: Array<{
id: string;
webinar_xid: string;
course_xid: string;
display_order: number;
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: string;
updated_at: string;
total_duration: number | null;
no_of_modules: number;
media_id: string | null;
media_file_type: string | null;
media_file_extension: string | null;
media_file_name: string | null;
};
}>;
};
errors: any;
correlation_id: string;
}
/* ================= API ================= */ /* ================= API ================= */
export const webinarApi = createApi({ export const webinarApi = createApi({
@@ -115,14 +201,18 @@ export const webinarApi = createApi({
params.append("sort_by", sortBy); params.append("sort_by", sortBy);
} }
return `/admin/webinars/list?${params.toString()}`; return `/guest/webinars/list?${params.toString()}`;
}, },
providesTags: ["Webinar"], providesTags: ["Webinar"],
}), }),
getWebinarById: builder.query<WebinarDetailResponse, string>({
query: (webinarId) => `/guest/webinars/${webinarId}`,
providesTags: (result, error, id) => [{ type: "Webinar", id }],
}),
}), }),
}); });
/* ================= EXPORT HOOK ================= */ /* ================= EXPORT HOOK ================= */
export const { useWebinarListQuery } = webinarApi; export const { useWebinarListQuery, useGetWebinarByIdQuery } = webinarApi;