) => {
+ console.error('Image failed to load:', member.image);
+ setImageError(true);
+ };
+
return (
{/* Breadcrumb Section */}
@@ -120,6 +141,11 @@ const TeamMemberDetail: React.FC = ({ memberId, memberDat
{member.name}
+ {isTrainee && (
+
+ Trainee/Fellow
+
+ )}
{member.designation || member.position} {member.experience && `with ${member.experience} of experience`} at CMC Vellore
@@ -134,17 +160,29 @@ const TeamMemberDetail: React.FC = ({ memberId, memberDat
{/* Sidebar */}
- {/* Profile Image */}
-
-

{
- const target = e.target as HTMLImageElement;
- target.src = '/images/default-avatar.jpg';
- }}
- />
+ {/* Profile Image or Avatar */}
+
+ {shouldShowImage ? (
+

+ ) : (
+
+ )}
{/* Profile Info */}
@@ -160,7 +198,7 @@ const TeamMemberDetail: React.FC
= ({ memberId, memberDat
- {member.description || `${member.name} is a dedicated faculty member at CMC Vellore.`}
+ {member.description || `${member.name} is a dedicated ${isTrainee ? 'trainee' : 'faculty member'} at CMC Vellore.`}
diff --git a/src/components/home/PatientTestimonialSlider.tsx b/src/components/home/PatientTestimonialSlider.tsx
new file mode 100644
index 0000000..d7ecdb0
--- /dev/null
+++ b/src/components/home/PatientTestimonialSlider.tsx
@@ -0,0 +1,164 @@
+'use client'
+import React, { useState, useEffect } from 'react';
+import testimonialService, { Testimonial } from '@/services/testimonialService';
+
+export default function PatientTestimonialCarousel() {
+ const [testimonials, setTestimonials] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ // Fetch testimonials on component mount
+ useEffect(() => {
+ fetchTestimonials();
+ }, []);
+
+ const fetchTestimonials = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const data = await testimonialService.getActiveTestimonials();
+ // Only show first 3 testimonials
+ setTestimonials(data.slice(0, 3));
+ } catch (err) {
+ console.error('Error fetching testimonials:', err);
+ setError('Failed to load testimonials. Please try again later.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Loading state
+ if (loading) {
+ return (
+
+
+
+
Loading testimonials...
+
+
+ );
+ }
+
+ // Error state
+ if (error) {
+ return (
+
+
+
+ Patient Testimonials
+
+
+
+
{error}
+
+
+
+ );
+ }
+
+ // Empty state
+ if (testimonials.length === 0) {
+ return (
+
+
+
+ Patient Testimonials
+
+
+
+
No testimonials available.
+
+
+ );
+ }
+
+ return (
+
+ {/* Section Header */}
+
+
+ Patient Testimonials
+
+
+
+ {/* Testimonial Cards Grid - Fixed to show only 3 */}
+
+ {testimonials.map((testimonial) => (
+
window.location.href = `/testimonials/${testimonial.id}`}
+ >
+ {/* Card */}
+
+ {/* Image/Story Content */}
+
+
+
+
+
+ {testimonial.story}
+
+
+
+
+
+ {/* Card Content */}
+
+ {/* Name and Age - As Heading */}
+
+ {testimonial.name}{testimonial.age ? `, ${testimonial.age}` : ''}
+
+
+ {/* Category - As Date */}
+
+ {testimonial.category || 'Patient Story'}
+
+
+
+
+ {/* Title Outside Card */}
+
+
+ {testimonial.title || 'A Journey of Hope and Recovery'}
+
+
+
+ ))}
+
+
+ {/* View More Button */}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/home/TraumaTestimonials.tsx b/src/components/home/TraumaTestimonials.tsx
new file mode 100644
index 0000000..cb68aed
--- /dev/null
+++ b/src/components/home/TraumaTestimonials.tsx
@@ -0,0 +1,268 @@
+// Updated TraumaTestimonials component - No left panel, with breadcrumb
+
+'use client'
+import React, { useState, useEffect } from 'react';
+import Link from 'next/link';
+import { ChevronRight } from 'lucide-react';
+import testimonialService, { Testimonial } from '../../services/testimonialService';
+
+interface TraumaTestimonialsProps {
+ testimonialId?: string;
+}
+
+export default function TraumaTestimonials({ testimonialId }: TraumaTestimonialsProps) {
+ const [activeIndex, setActiveIndex] = useState(0);
+ const [testimonials, setTestimonials] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetchTestimonials();
+ }, []);
+
+ // Set active index when testimonialId changes
+ useEffect(() => {
+ if (testimonialId && testimonials.length > 0) {
+ const index = testimonials.findIndex(t => t.id.toString() === testimonialId);
+ if (index !== -1) {
+ setActiveIndex(index);
+ }
+ }
+ }, [testimonialId, testimonials]);
+
+ const fetchTestimonials = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const data = await testimonialService.getActiveTestimonials();
+ setTestimonials(data);
+ } catch (err) {
+ console.error('Error fetching testimonials:', err);
+ setError('Failed to load testimonials. Please try again later.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const nextTestimonial = () => {
+ if (activeIndex < testimonials.length - 1) {
+ const nextIndex = activeIndex + 1;
+ setActiveIndex(nextIndex);
+ // Update URL without page reload
+ window.history.pushState({}, '', `/testimonials/${testimonials[nextIndex].id}`);
+ }
+ };
+
+ const prevTestimonial = () => {
+ if (activeIndex > 0) {
+ const prevIndex = activeIndex - 1;
+ setActiveIndex(prevIndex);
+ // Update URL without page reload
+ window.history.pushState({}, '', `/testimonials/${testimonials[prevIndex].id}`);
+ }
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error || testimonials.length === 0) {
+ return (
+
+
{error || 'No testimonials available.'}
+
+ );
+ }
+
+ const current = testimonials[activeIndex];
+
+ return (
+
+ {/* Breadcrumb Section */}
+
+
+
+
+
+
+ {/* Top Section */}
+
+
+
+
+
+ Patient Stories
+
+
Stories of hope and recovery
+
+
+ Donate Now
+
+
+
+
+
+ {/* Main Content - Single Column */}
+
+ {/* Category Badge */}
+
+ {current.category}
+
+
+ {/* Title */}
+
+ {current.name}, {current.age}
+
+
+
+ {current.title}
+
+
+ {/* Content Sections */}
+
+
+
+
+ {current.story}
+
+
+
+
+
+
+ {current.outcome}
+
+
+
+
+
+
+
+ COMMUNITY IMPACT
+
+
+
+ {current.impact}
+
+
+
+
+ {/* Navigation */}
+
+
+
+
+ {activeIndex + 1} of {testimonials.length}
+
+
+
+
+
+ {/* Back to All Stories Link */}
+
+
+
+ Back to All Stories
+
+
+
+
+ {/* Bottom CTA */}
+
+
+
+
+
+ Support Emergency Trauma Care
+
+
+ Your donation ensures trauma victims receive immediate, life-saving treatment.
+
+
+
+ Donate Now
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/services/Breadcrumb.tsx b/src/components/services/Breadcrumb.tsx
new file mode 100644
index 0000000..b889d4a
--- /dev/null
+++ b/src/components/services/Breadcrumb.tsx
@@ -0,0 +1,74 @@
+
+'use client'
+import React from 'react';
+import Link from 'next/link';
+import { ChevronRight } from 'lucide-react';
+
+interface BreadcrumbItem {
+ label: string;
+ href?: string;
+ isActive?: boolean;
+}
+
+interface BreadcrumbProps {
+ items: BreadcrumbItem[];
+ title: string;
+ description?: string;
+ className?: string;
+}
+
+const Breadcrumb: React.FC = ({
+ items,
+ title,
+ description,
+ className = ""
+}) => {
+ return (
+
+
+ {/* Breadcrumb Navigation */}
+
+
+ {/* Page Header */}
+
+
+
+ {title}
+
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+
+ );
+};
+
+export default Breadcrumb;
\ No newline at end of file
diff --git a/src/components/testimonials/TestimonialsListing.tsx b/src/components/testimonials/TestimonialsListing.tsx
new file mode 100644
index 0000000..10a3560
--- /dev/null
+++ b/src/components/testimonials/TestimonialsListing.tsx
@@ -0,0 +1,276 @@
+'use client'
+import React, { useEffect, useState } from 'react';
+import Link from 'next/link';
+import { ChevronRight } from 'lucide-react';
+import testimonialService, { Testimonial } from '@/services/testimonialService';
+import TraumaStats from './TraumaStats';
+
+interface TestimonialsListingProps {
+ title?: string;
+ onTestimonialClick?: (testimonial: Testimonial) => void;
+}
+
+const TestimonialsListing: React.FC = ({
+ title = "Patient Stories",
+ onTestimonialClick
+}) => {
+ const [testimonials, setTestimonials] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const loadTestimonials = async () => {
+ try {
+ setLoading(true);
+ const data = await testimonialService.getActiveTestimonials();
+ setTestimonials(data);
+ setError(null);
+ } catch (err) {
+ console.error('Failed to load testimonials:', err);
+ setError('Failed to load testimonials. Please try again later.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadTestimonials();
+ }, []);
+
+ // Group testimonials by category
+ const traumaCases = testimonials.filter(t => t.category === 'Trauma' || t.category === 'Road Accident');
+ const surgicalCases = testimonials.filter(t => t.category === 'Surgery' || t.category === 'Surgical');
+ const otherCases = testimonials.filter(t => !['Trauma', 'Road Accident', 'Surgery', 'Surgical'].includes(t.category || ''));
+
+ const handleTestimonialClick = (testimonial: Testimonial) => {
+ if (onTestimonialClick) {
+ onTestimonialClick(testimonial);
+ } else {
+ window.location.href = `/testimonials/${testimonial.id}`;
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+
Loading testimonials...
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
{error}
+
+
+
+ );
+ }
+
+ const TestimonialCard: React.FC<{ testimonial: Testimonial }> = ({ testimonial }) => {
+ return (
+ handleTestimonialClick(testimonial)}
+ >
+
+
+
+
+
+ {testimonial.story}
+
+
+
+
+
+
+
+ {testimonial.name}{testimonial.age ? `, ${testimonial.age}` : ''}
+
+
+ {testimonial.category || 'Patient Story'}
+
+ {testimonial.title && (
+
+ {testimonial.title}
+
+ )}
+
+
+ );
+ };
+
+ return (
+
+ {/* Breadcrumb Section */}
+
+
+
+
+
+
+
+ {title}
+
+
+
+ Real stories of hope, recovery, and resilience from patients treated at Christian Medical College, Vellore. Each story represents the dedication of our medical team and the strength of our patients.
+
+
+
+
+
+ {/* Stats Section */}
+
+
+ {/* Trauma Cases Section */}
+ {traumaCases.length > 0 && (
+
+
+
+
+ Trauma & Emergency Cases
+
+
+ Stories of survival and recovery from critical trauma and emergency situations
+
+
+
+ {traumaCases.map((testimonial) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* Surgical Cases Section */}
+ {surgicalCases.length > 0 && (
+
+
+
+
+ Surgical Success Stories
+
+
+ Remarkable journeys through complex surgical procedures and recovery
+
+
+
+ {surgicalCases.map((testimonial) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* Other Cases Section */}
+ {otherCases.length > 0 && (
+
+
+
+
+ More Patient Stories
+
+
+ Additional testimonials from patients across various medical specialties
+
+
+
+ {otherCases.map((testimonial) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* Show message if no testimonials */}
+ {testimonials.length === 0 && !loading && (
+
+
+
+ No Testimonials Available
+
+
+ Patient testimonials are currently being updated. Please check back later.
+
+
+
+
+ )}
+
+ {/* Bottom CTA Section */}
+
+
+
+
+
+ Support Emergency Trauma Care
+
+
+ Your donation ensures trauma victims receive immediate, life-saving treatment.
+
+
+
+ Donate Now
+
+
+
+
+
+ );
+};
+
+export default TestimonialsListing;
\ No newline at end of file
diff --git a/src/components/testimonials/TraumaStats.tsx b/src/components/testimonials/TraumaStats.tsx
new file mode 100644
index 0000000..b03238d
--- /dev/null
+++ b/src/components/testimonials/TraumaStats.tsx
@@ -0,0 +1,32 @@
+'use client'
+import React from 'react';
+
+export default function TraumaStats() {
+ const stats = [
+ { label: "Road Accident Victims Treated Annually", value: "Over 2,500" },
+ { label: "Trauma Bed Capacity", value: "112" },
+ { label: "Operation Theatres (Trauma)", value: "6" },
+ { label: "24x7 Consultant Availability", value: "Yes" },
+ ];
+
+ return (
+
+
+ {stats.map((stat, idx) => (
+
+
+ {stat.value}
+
+
+ {stat.label}
+
+
+ ))}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 110c4bf..376adb3 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -18,6 +18,7 @@ export interface Event {
phone: string;
email: string;
isActive: boolean;
+ bookSeatLink?: string; // NEW FIELD
professors?: Professor[];
}
diff --git a/src/services/milestoneService.ts b/src/services/milestoneService.ts
new file mode 100644
index 0000000..43278c6
--- /dev/null
+++ b/src/services/milestoneService.ts
@@ -0,0 +1,71 @@
+// services/milestoneService.ts
+const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
+
+export interface Milestone {
+ id: number;
+ title: string;
+ description: string;
+ displayOrder: number;
+ isActive: boolean;
+ milestoneDate: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+/**
+ * Fetch all active milestones from the API
+ * Uses the public endpoint that doesn't require authentication
+ */
+export async function getActiveMilestones(): Promise {
+ try {
+ const response = await fetch(`${API_BASE_URL}/api/milestones/public`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store', // Disable caching for fresh data
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error fetching milestones:', error);
+ // Return empty array as fallback
+ return [];
+ }
+}
+
+/**
+ * Fetch all milestones (requires authentication)
+ */
+export async function getAllMilestones(token?: string): Promise {
+ try {
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const response = await fetch(`${API_BASE_URL}/api/milestones`, {
+ method: 'GET',
+ headers,
+ cache: 'no-store',
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error fetching all milestones:', error);
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/src/services/testimonialService.ts b/src/services/testimonialService.ts
new file mode 100644
index 0000000..a07397e
--- /dev/null
+++ b/src/services/testimonialService.ts
@@ -0,0 +1,174 @@
+// src/services/testimonialService.ts
+
+export interface Testimonial {
+ id: number;
+ name: string;
+ age: number;
+ title: string;
+ story: string;
+ outcome: string;
+ impact: string;
+ category: string;
+ isActive: boolean;
+ createdAt?: Date;
+ updatedAt?: Date;
+}
+
+class TestimonialService {
+ private baseUrl: string;
+
+ constructor() {
+ // Use environment variable or default to localhost
+ this.baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
+ }
+
+ /**
+ * Get all active testimonials (for public website)
+ */
+ async getActiveTestimonials(): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}/api/testimonials/public`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store', // Always fetch fresh data
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error fetching active testimonials:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Get all testimonials (for admin)
+ */
+ async getAllTestimonials(): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}/api/testimonials`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store',
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error fetching all testimonials:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Get testimonial by ID
+ */
+ async getTestimonialById(id: number): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}/api/testimonials/${id}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store',
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error(`Error fetching testimonial ${id}:`, error);
+ throw error;
+ }
+ }
+
+ /**
+ * Create new testimonial (admin only)
+ */
+ async createTestimonial(testimonial: Omit): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}/api/testimonials`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(testimonial),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error creating testimonial:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Update testimonial (admin only)
+ */
+ async updateTestimonial(id: number, testimonial: Omit): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}/api/testimonials/${id}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(testimonial),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error(`Error updating testimonial ${id}:`, error);
+ throw error;
+ }
+ }
+
+ /**
+ * Delete testimonial (admin only)
+ */
+ async deleteTestimonial(id: number): Promise {
+ try {
+ const response = await fetch(`${this.baseUrl}/api/testimonials/${id}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ } catch (error) {
+ console.error(`Error deleting testimonial ${id}:`, error);
+ throw error;
+ }
+ }
+}
+
+// Export singleton instance
+const testimonialService = new TestimonialService();
+export default testimonialService;
\ No newline at end of file