first commit

This commit is contained in:
2025-10-09 20:05:39 +05:30
commit d4fcb658e3
69 changed files with 13582 additions and 0 deletions

View File

@ -0,0 +1,402 @@
// components/CourseDetail.tsx
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import {
ChevronRight,
Clock,
Users,
Calendar,
User,
ExternalLink
} from 'lucide-react';
import { educationService, CourseApplicationData } from '../../services/educationService';
import { fileUploadService } from '../../services/fileUploadService';
interface CourseDetailProps {
courseId?: string;
}
const CourseDetail: React.FC<CourseDetailProps> = ({ courseId }) => {
const [mounted, setMounted] = useState(false);
const [course, setCourse] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
// Application form data
const [formData, setFormData] = useState({
fullName: '',
email: '',
phone: '',
qualification: '',
experience: '',
coverLetter: '',
resume: null as File | null
});
useEffect(() => {
setMounted(true);
// Get course ID from URL params if not provided as prop
const urlParams = new URLSearchParams(window.location.search);
const id = courseId || urlParams.get('id');
if (id) {
loadCourseDetail(parseInt(id));
} else {
setError('Course ID not provided');
setLoading(false);
}
}, [courseId]);
const loadCourseDetail = async (id: number) => {
try {
setLoading(true);
setError(null);
const courseData = await educationService.getCourseById(id);
if (courseData) {
setCourse(courseData);
} else {
setError('Course not found');
}
} catch (err) {
setError('Failed to load course details');
console.error('Error loading course:', err);
} finally {
setLoading(false);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setFormData(prev => ({
...prev,
resume: e.target.files![0]
}));
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!course) {
alert('Course not found.');
return;
}
try {
setSubmitting(true);
setError(null);
let resumeUrl = '';
// Upload resume if provided
if (formData.resume) {
try {
const uploadResponse = await fileUploadService.uploadFile(formData.resume);
resumeUrl = uploadResponse.url;
} catch (uploadError) {
console.warn('Resume upload failed, submitting application without resume:', uploadError);
resumeUrl = '';
}
}
// Prepare application data
const applicationData: CourseApplicationData = {
courseId: parseInt(course.id),
fullName: formData.fullName,
email: formData.email,
phone: formData.phone,
qualification: formData.qualification,
experience: formData.experience || undefined,
coverLetter: formData.coverLetter || undefined,
resumeUrl: resumeUrl || undefined
};
// Submit application
const success = await educationService.submitApplication(applicationData);
if (success) {
setSubmitSuccess(true);
// Reset form
setFormData({
fullName: '',
email: '',
phone: '',
qualification: '',
experience: '',
coverLetter: '',
resume: null
});
// Clear file input
const fileInput = document.getElementById('resume') as HTMLInputElement;
if (fileInput) {
fileInput.value = '';
}
} else {
setError('Failed to submit application. Please try again.');
}
} catch (err) {
console.error('Error submitting application:', err);
setError('Failed to submit application. Please try again.');
} finally {
setSubmitting(false);
}
};
if (!mounted) {
return null;
}
if (loading) {
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-blue-900 mx-auto mb-4"></div>
<p className="text-gray-600">Loading course details...</p>
</div>
</div>
);
}
if (error || !course) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<p className="text-red-600 text-xl mb-4"> {error || 'Course not found'}</p>
<Link href="/education-training" className="text-blue-600 hover:underline">
Back to Education & Training
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-white">
{/* Header Section with Background Pattern */}
<section
className="py-8 relative overflow-hidden"
style={{ backgroundColor: '#f4f4f4' }}
>
<div className="max-w-7xl mx-auto px-4 relative z-10">
{/* Breadcrumb */}
<nav className="flex items-center space-x-2 text-sm mb-6">
<Link
href="/"
className="hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
Home
</Link>
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
<Link
href="/education-training"
className="hover:opacity-70 transition-opacity duration-200"
style={{ color: '#012068' }}
>
Education & Training
</Link>
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
<span className="font-medium" style={{ color: '#e64838' }}>
Course Details
</span>
</nav>
{/* Course Title and Meta */}
<div className="mb-8">
<div className="flex items-center mb-4">
<h1 className="text-4xl font-bold" style={{ color: '#012068' }}>
{course.title}
</h1>
</div>
<div className="flex flex-wrap items-center gap-6 text-lg mb-6" style={{ color: '#012068' }}>
<div className="flex items-center">
<Clock className="w-5 h-5 mr-2" />
<span>Duration: {course.duration}</span>
</div>
<div className="flex items-center">
<Users className="w-5 h-5 mr-2" />
<span>No of seats: {course.seats}</span>
</div>
</div>
</div>
</div>
</section>
{/* Course Info Bar */}
<section className="py-6 border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex flex-wrap items-center gap-6 text-sm">
<div className="flex items-center">
<User className="w-4 h-4 mr-2" style={{ color: '#012068' }} />
<span style={{ color: '#666' }}>Instructor: </span>
<span className="font-medium" style={{ color: '#012068' }}>
{course.instructor}
</span>
</div>
{course.startDate && (
<div className="flex items-center">
<Calendar className="w-4 h-4 mr-2" style={{ color: '#012068' }} />
<span style={{ color: '#666' }}>Start Date: </span>
<span className="font-medium" style={{ color: '#012068' }}>
{new Date(course.startDate).toLocaleDateString()}
</span>
</div>
)}
</div>
<div className="flex gap-3">
<button
className="px-6 py-2 text-sm font-medium rounded border-2 hover:opacity-70 transition-opacity duration-300"
style={{
borderColor: '#012068',
color: '#012068'
}}
>
Download Brochure
</button>
<button
className="px-6 py-2 mr-2 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300"
style={{
backgroundColor: '#012068',
color: 'white'
}}
onClick={() => {
document.getElementById('applicationForm')?.scrollIntoView({ behavior: 'smooth' });
}}
>
Apply Now
</button>
</div>
</div>
</div>
</section>
{/* Main Content - Full Width */}
<section className="py-8">
<div className="max-w-7xl mx-auto px-4">
{/* Left Column - Course Information */}
<div>
<div className="bg-white rounded-lg border border-gray-200 p-8 space-y-12">
{/* Overview Section */}
<div>
<h2 className="text-2xl font-bold mb-6" style={{ color: '#012068' }}>
Overview
</h2>
<div className="prose max-w-none">
<p className="text-base leading-relaxed mb-6" style={{ color: '#666' }}>
{course.description}
</p>
{course.objectives && course.objectives.length > 0 && (
<>
<h3 className="text-xl font-semibold mb-4" style={{ color: '#012068' }}>
Learning Objectives
</h3>
<p className="text-sm mb-4" style={{ color: '#666' }}>
The main objectives of the program can be summarised as follows:
</p>
<ul className="space-y-3">
{course.objectives.map((objective: string, index: number) => (
<li key={index} className="flex items-start">
<span className="text-sm font-medium mr-3" style={{ color: '#012068' }}>
</span>
<span className="text-sm leading-relaxed" style={{ color: '#666' }}>
{objective}
</span>
</li>
))}
</ul>
</>
)}
</div>
</div>
{/* Eligibility Criteria Section */}
{course.eligibility && course.eligibility.length > 0 && (
<div>
<h2 className="text-2xl font-bold mb-6" style={{ color: '#012068' }}>
Eligibility Criteria
</h2>
<div className="overflow-x-auto">
<table className="w-full border border-gray-200 rounded-lg">
<thead>
<tr style={{ backgroundColor: '#f8f9fa' }}>
<th
className="px-6 py-4 text-left text-sm font-semibold border-b border-gray-200"
style={{ color: '#012068' }}
>
Sr. No.
</th>
<th
className="px-6 py-4 text-left text-sm font-semibold border-b border-gray-200"
style={{ color: '#012068' }}
>
Eligibility Requirements
</th>
</tr>
</thead>
<tbody>
{course.eligibility.map((criteria: string, index: number) => (
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<td className="px-6 py-4 text-sm border-b border-gray-200" style={{ color: '#012068' }}>
{index + 1}
</td>
<td className="px-6 py-4 text-sm border-b border-gray-200" style={{ color: '#666' }}>
{criteria}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* How to Apply Section */}
<div>
<h2 className="text-2xl font-bold mb-6" style={{ color: '#012068' }}>
How to Apply
</h2>
<div className="mb-6">
<p className="text-sm mb-2" style={{ color: '#666' }}>
Use the application form on the right to apply for this course, or visit our admissions website:
</p>
<a
href="https://admissions.cmcvellore.ac.in/"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-sm font-medium hover:opacity-70 transition-opacity duration-200"
style={{ color: '#e64838' }}
>
https://admissions.cmcvellore.ac.in/
<ExternalLink className="w-4 h-4 ml-1" />
</a>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
);
};
export default CourseDetail;