Compare commits
2 Commits
e70d869b76
...
6afb2a7cfb
Author | SHA1 | Date |
---|---|---|
|
6afb2a7cfb | 6 months ago |
|
e3ffa28c35 | 6 months ago |
@ -1,4 +1,21 @@ |
|||||||
/** @type {import('next').NextConfig} */ |
/** @type {import('next').NextConfig} */ |
||||||
const nextConfig = {}; |
const nextConfig = { |
||||||
|
images: { |
||||||
|
remotePatterns: [ |
||||||
|
{ |
||||||
|
protocol: 'https', |
||||||
|
hostname: '**', |
||||||
|
port: '', |
||||||
|
pathname: '**', |
||||||
|
search: '', |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
webpack: (config) => { |
||||||
|
config.resolve.alias.canvas = false; |
||||||
|
|
||||||
|
return config; |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
export default nextConfig; |
export default nextConfig; |
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@ |
|||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" |
||||||
|
import { Button } from "@/components/ui/button" |
||||||
|
import { Card, CardContent } from "@/components/ui/card" |
||||||
|
import { CourseData } from "@/helpers/apiSchema/course.schema" |
||||||
|
|
||||||
|
|
||||||
|
interface CourseDetailHeroSectionProps { |
||||||
|
courseData: CourseData; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const CourseDetailHeroSection: React.FC<CourseDetailHeroSectionProps> = ({courseData}) => { |
||||||
|
return( |
||||||
|
<div className="bg-purple-700 text-white"> |
||||||
|
<div className="container mx-auto px-4 py-12"> |
||||||
|
<div className="grid md:grid-cols-2 gap-8 items-center"> |
||||||
|
<div className="space-y-4"> |
||||||
|
<h1 className="text-3xl font-bold">{courseData.title}</h1> |
||||||
|
<p className="text-blue-100">{courseData.description}</p> |
||||||
|
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4"> |
||||||
|
<Avatar className="h-12 w-12"> |
||||||
|
<AvatarImage src={courseData.instructor.avatar} className='aspect-1/1'/> |
||||||
|
<AvatarFallback>SJ</AvatarFallback> |
||||||
|
</Avatar> |
||||||
|
<div> |
||||||
|
<div className="font-semibold">{courseData.instructor.name}</div> |
||||||
|
<div className="text-sm text-blue-100">{courseData.instructor.title}</div> |
||||||
|
<div>Published at {courseData?.lastUpdated}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{/* course image */} |
||||||
|
<Card className="bg-white"> |
||||||
|
<CardContent className="p-6 space-y-4"> |
||||||
|
<img |
||||||
|
src="https://placehold.co/600x400" |
||||||
|
alt="Course Preview" |
||||||
|
className="w-full rounded-lg" |
||||||
|
/> |
||||||
|
<Button className="w-full text-lg h-12 bg-purple-700"> |
||||||
|
Enroll Now |
||||||
|
</Button> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CourseDetailHeroSection |
@ -0,0 +1,56 @@ |
|||||||
|
import { Card, CardContent } from "@/components/ui/card" |
||||||
|
import { CourseData } from "@/helpers/apiSchema/course.schema" |
||||||
|
import { BookOpen, Clock, MessageSquare, Users } from "lucide-react" |
||||||
|
|
||||||
|
|
||||||
|
const CourseDetailStats :React.FC<{courseData : CourseData}> = ({courseData}) => { |
||||||
|
return( |
||||||
|
<> |
||||||
|
<div className="mx-auto container py-8"> |
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> |
||||||
|
<Card> |
||||||
|
<CardContent className="p-4 flex items-center space-x-4"> |
||||||
|
<Clock className="h-8 w-8 text-blue-500" /> |
||||||
|
<div> |
||||||
|
<div className="text-sm text-gray-500">Duration</div> |
||||||
|
<div className="font-semibold">{courseData.duration}</div> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
|
||||||
|
<Card> |
||||||
|
<CardContent className="p-4 flex items-center space-x-4"> |
||||||
|
<Users className="h-8 w-8 text-green-500" /> |
||||||
|
<div> |
||||||
|
<div className="text-sm text-gray-500">Enrolled</div> |
||||||
|
<div className="font-semibold">{courseData.enrolledStudents} students</div> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
|
||||||
|
<Card> |
||||||
|
<CardContent className="p-4 flex items-center space-x-4"> |
||||||
|
<MessageSquare className="h-8 w-8 text-purple-500" /> |
||||||
|
<div> |
||||||
|
<div className="text-sm text-gray-500">Reviews</div> |
||||||
|
<div className="font-semibold">{courseData.rating} ({courseData.totalReviews})</div> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
|
||||||
|
<Card> |
||||||
|
<CardContent className="p-4 flex items-center space-x-4"> |
||||||
|
<BookOpen className="h-8 w-8 text-orange-500" /> |
||||||
|
<div> |
||||||
|
<div className="text-sm text-gray-500">Completion</div> |
||||||
|
<div className="font-semibold">{courseData.completionRate}%</div> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CourseDetailStats |
@ -0,0 +1,58 @@ |
|||||||
|
'use client'; |
||||||
|
import React from 'react'; |
||||||
|
import { Card, CardContent } from "@/components/ui/card"; |
||||||
|
import { Progress } from "@/components/ui/progress"; |
||||||
|
import CourseDetailHeroSection from './CourseDetailHeroSection'; |
||||||
|
import CourseDetailStats from './CourseDetailStats'; |
||||||
|
import CourseMedia from './CourseMedia'; |
||||||
|
import CourseProgress from './CourseProgress'; |
||||||
|
import TryPDF from './TryPDF'; |
||||||
|
|
||||||
|
const CourseIndividualContentWrapper : React.FC = () => { |
||||||
|
// Dummy course data
|
||||||
|
const courseData = { |
||||||
|
title: "Advanced JavaScript Programming", |
||||||
|
instructor: { |
||||||
|
name: "Dr. Sarah Johnson", |
||||||
|
title: "Senior Software Engineer", |
||||||
|
avatar: "https://placehold.co/600x400" |
||||||
|
}, |
||||||
|
description: "Master modern JavaScript concepts including ES6+, async programming, and advanced frameworks. This comprehensive course covers everything from basic concepts to advanced topics in JavaScript development.", |
||||||
|
category: "Programming", |
||||||
|
duration: "12 weeks", |
||||||
|
enrolledStudents: 1234, |
||||||
|
rating: 4.8, |
||||||
|
totalReviews: 456, |
||||||
|
completionRate: 85, |
||||||
|
lastUpdated: "2024-01-10", |
||||||
|
chapters: [ |
||||||
|
{ title: "Introduction to ES6+", duration: "2.5 hours", isCompleted: true }, |
||||||
|
{ title: "Async Programming", duration: "3 hours", isCompleted: true }, |
||||||
|
{ title: "Advanced DOM Manipulation", duration: "2 hours", isCompleted: false }, |
||||||
|
{ title: "Modern Frameworks Overview", duration: "4 hours", isCompleted: false } |
||||||
|
], |
||||||
|
requirements: [ |
||||||
|
"Basic understanding of JavaScript", |
||||||
|
"Familiarity with web development concepts", |
||||||
|
"Node.js installed on your computer" |
||||||
|
] |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="min-h-screen bg-gray-50"> |
||||||
|
{/* Hero Section */} |
||||||
|
<CourseDetailHeroSection courseData={courseData}/> |
||||||
|
|
||||||
|
{/* Course Stats */} |
||||||
|
<CourseDetailStats courseData={courseData}/> |
||||||
|
|
||||||
|
<CourseProgress courseData={courseData}/> |
||||||
|
|
||||||
|
{/* Course Content */} |
||||||
|
<CourseMedia courseData={courseData}/> |
||||||
|
|
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default CourseIndividualContentWrapper; |
@ -0,0 +1,39 @@ |
|||||||
|
import { Card, CardContent } from "@/components/ui/card" |
||||||
|
import { Progress } from "@/components/ui/progress" |
||||||
|
import { CourseData } from "@/helpers/apiSchema/course.schema" |
||||||
|
import PDFFlipBook from "./CoursePDFHolder" |
||||||
|
import CustomPDFViewer from "./TryPDF" |
||||||
|
import DiscussionSection from "@/components/elements/DiscussionForm" |
||||||
|
|
||||||
|
const CourseMedia : React.FC<{courseData : CourseData}> = ({courseData}) => { |
||||||
|
return( |
||||||
|
<div className="container mx-auto py-8"> |
||||||
|
<div className="grid md:grid-cols-3 gap-8"> |
||||||
|
{/* Main Content */} |
||||||
|
<div className="md:col-span-2 space-y-8"> |
||||||
|
<Card> |
||||||
|
<CardContent className="p-6"> |
||||||
|
<h2 className="text-xl font-semibold mb-4">Course Content</h2> |
||||||
|
<CustomPDFViewer /> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
{/* Sidebar */} |
||||||
|
<div className="space-y-6"> |
||||||
|
<Card> |
||||||
|
<CardContent className="p-6"> |
||||||
|
<h3 className="text-lg font-semibold mb-4">Discussions</h3> |
||||||
|
<DiscussionSection /> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
|
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CourseMedia |
@ -0,0 +1,149 @@ |
|||||||
|
'use client' |
||||||
|
import React, { forwardRef, useState, useEffect } from 'react'; |
||||||
|
import HTMLFlipBook from 'react-pageflip'; |
||||||
|
import { pdfjs, Document, Page as ReactPdfPage } from 'react-pdf'; |
||||||
|
|
||||||
|
// Configure PDF.js worker globally
|
||||||
|
pdfjs.GlobalWorkerOptions.workerSrc = new URL( |
||||||
|
'pdfjs-dist/build/pdf.worker.min.mjs', |
||||||
|
import.meta.url, |
||||||
|
).toString(); |
||||||
|
|
||||||
|
interface PageProps { |
||||||
|
pageNumber: number; |
||||||
|
width: number; |
||||||
|
} |
||||||
|
|
||||||
|
interface PDFFlipBookProps { |
||||||
|
pdfUrl: string; |
||||||
|
width?: number; |
||||||
|
height?: number; |
||||||
|
numPages?: number; |
||||||
|
} |
||||||
|
|
||||||
|
const DEFAULT_WIDTH = 300; |
||||||
|
const DEFAULT_HEIGHT = 424; |
||||||
|
|
||||||
|
const Page = forwardRef<HTMLDivElement, PageProps>(({ pageNumber, width }, ref) => { |
||||||
|
const [pageError, setPageError] = useState<string | null>(null); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div ref={ref} style={{ background: 'white' }}> |
||||||
|
<ReactPdfPage |
||||||
|
pageNumber={pageNumber} |
||||||
|
width={width} |
||||||
|
loading={ |
||||||
|
<div className="flex items-center justify-center p-4"> |
||||||
|
<div>Loading page {pageNumber}...</div> |
||||||
|
</div> |
||||||
|
} |
||||||
|
error={ |
||||||
|
pageError && ( |
||||||
|
<div className="text-red-500 p-4"> |
||||||
|
Error loading page {pageNumber}: {pageError} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
onLoadError={(error) => setPageError(error.message)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
Page.displayName = 'Page'; |
||||||
|
|
||||||
|
const PDFFlipBook: React.FC<PDFFlipBookProps> = ({ |
||||||
|
pdfUrl, |
||||||
|
width = DEFAULT_WIDTH, |
||||||
|
height = DEFAULT_HEIGHT, |
||||||
|
numPages = 3, |
||||||
|
}) => { |
||||||
|
const [totalPages, setTotalPages] = useState<number>(0); |
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(true); |
||||||
|
const [error, setError] = useState<string | null>(null); |
||||||
|
|
||||||
|
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { |
||||||
|
console.log('PDF loaded successfully with', numPages, 'pages'); |
||||||
|
setTotalPages(numPages); |
||||||
|
setIsLoading(false); |
||||||
|
}; |
||||||
|
|
||||||
|
const onDocumentLoadError = (error: Error) => { |
||||||
|
console.error('Error loading PDF:', error); |
||||||
|
setError(error.message); |
||||||
|
setIsLoading(false); |
||||||
|
}; |
||||||
|
|
||||||
|
const LoadingComponent = () => ( |
||||||
|
<div className="flex items-center justify-center p-4"> |
||||||
|
<div className="text-center"> |
||||||
|
<div className="text-lg font-medium">Loading PDF...</div> |
||||||
|
<div className="text-sm text-gray-500">Please wait</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
|
||||||
|
const ErrorComponent = ({ message }: { message: string }) => ( |
||||||
|
<div className="flex items-center justify-center p-4"> |
||||||
|
<div className="text-center text-red-500"> |
||||||
|
<div className="text-lg font-medium">Error loading PDF</div> |
||||||
|
<div className="text-sm">{message}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
|
||||||
|
if (error) { |
||||||
|
return <ErrorComponent message={error} />; |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Document |
||||||
|
file={pdfUrl} |
||||||
|
onLoadSuccess={onDocumentLoadSuccess} |
||||||
|
onLoadError={onDocumentLoadError} |
||||||
|
loading={<LoadingComponent />} |
||||||
|
error={<ErrorComponent message="Failed to load PDF" />} |
||||||
|
options={{ |
||||||
|
cMapUrl: 'cmaps/', |
||||||
|
cMapPacked: true, |
||||||
|
standardFontDataUrl: 'standard_fonts/', |
||||||
|
}} |
||||||
|
> |
||||||
|
{isLoading ? ( |
||||||
|
<LoadingComponent /> |
||||||
|
) : ( |
||||||
|
<HTMLFlipBook |
||||||
|
style={{}} |
||||||
|
width={width} |
||||||
|
height={height} |
||||||
|
size="fixed" |
||||||
|
minWidth={0} |
||||||
|
maxWidth={0} |
||||||
|
minHeight={0} |
||||||
|
maxHeight={0} |
||||||
|
drawShadow={true} |
||||||
|
flippingTime={1000} |
||||||
|
usePortrait={true} |
||||||
|
startZIndex={0} |
||||||
|
autoSize={true} |
||||||
|
maxShadowOpacity={1} |
||||||
|
showCover={false} |
||||||
|
mobileScrollSupport={true} |
||||||
|
className="pdf-flipbook" |
||||||
|
startPage={0} |
||||||
|
swipeDistance={0} |
||||||
|
showPageCorners={true} |
||||||
|
disableFlipByClick={false} |
||||||
|
clickEventForward={true} |
||||||
|
useMouseEvents={true} |
||||||
|
> |
||||||
|
{Array.from(new Array(Math.min(totalPages, numPages)), (_, index) => ( |
||||||
|
<Page key={index + 1} pageNumber={index + 1} width={width} /> |
||||||
|
))} |
||||||
|
</HTMLFlipBook> |
||||||
|
)} |
||||||
|
</Document> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default PDFFlipBook; |
@ -0,0 +1,22 @@ |
|||||||
|
import CommonContainer from "@/components/elements/CommonContainer" |
||||||
|
import { Card, CardContent } from "@/components/ui/card" |
||||||
|
import { Progress } from "@/components/ui/progress" |
||||||
|
import { CourseData } from "@/helpers/apiSchema/course.schema" |
||||||
|
|
||||||
|
const CourseProgress :React.FC<{courseData : CourseData}> = ({courseData}) => { |
||||||
|
return( |
||||||
|
<CommonContainer className="!px-0"> |
||||||
|
<Card> |
||||||
|
<CardContent className="py-6"> |
||||||
|
<h3 className="text-lg font-semibold mb-4">Your Progress</h3> |
||||||
|
<Progress value={courseData.completionRate} className="mb-2 " /> |
||||||
|
<p className="text-sm text-gray-500 text-center"> |
||||||
|
{courseData.completionRate}% Complete |
||||||
|
</p> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
</CommonContainer> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CourseProgress |
@ -0,0 +1,97 @@ |
|||||||
|
import { Button } from '@/components/ui/button'; |
||||||
|
import React, { useEffect, useState } from 'react'; |
||||||
|
|
||||||
|
export default function CustomPDFViewer() { |
||||||
|
const [currentPage, setCurrentPage] = useState(1); |
||||||
|
const [totalPages, setTotalPages] = useState(0); |
||||||
|
const [loading, setLoading] = useState(true); |
||||||
|
const [error, setError] = useState<string | null>(null); |
||||||
|
const [pageUrl, setPageUrl] = useState(''); |
||||||
|
|
||||||
|
// Mock function to fetch PDF page as image URL
|
||||||
|
// In a real implementation, you would use a backend service to convert PDF pages to images
|
||||||
|
const fetchPageImage = async (pageNumber : number) => { |
||||||
|
setLoading(true); |
||||||
|
try { |
||||||
|
// Simulating API call delay
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500)); |
||||||
|
// Using placeholder image for demonstration
|
||||||
|
setPageUrl(`https://placehold.co/600x400?text=Page ${pageNumber}`); |
||||||
|
setTotalPages(5); // Mock total pages
|
||||||
|
setLoading(false); |
||||||
|
} catch (err) { |
||||||
|
setError('Failed to load PDF page'); |
||||||
|
setLoading(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
fetchPageImage(currentPage); |
||||||
|
}, [currentPage]); |
||||||
|
|
||||||
|
const goToPreviousPage = () => { |
||||||
|
if (currentPage > 1) { |
||||||
|
setCurrentPage(prev => prev - 1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const goToNextPage = () => { |
||||||
|
if (currentPage < totalPages) { |
||||||
|
setCurrentPage(prev => prev + 1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex flex-col items-center w-full max-w-3xl mx-auto p-4"> |
||||||
|
{/* Page Display */} |
||||||
|
<div className="relative w-full h-auto bg-gray-100 rounded-lg shadow-lg mb-4"> |
||||||
|
{loading ? ( |
||||||
|
<div className=" inset-0 flex items-center justify-center min-h-96"> |
||||||
|
<div className="text-lg text-gray-600">Loading page {currentPage}...</div> |
||||||
|
</div> |
||||||
|
) : error ? ( |
||||||
|
<div className="absolute inset-0 flex items-center justify-center"> |
||||||
|
<div className="text-lg text-red-600">{error}</div> |
||||||
|
</div> |
||||||
|
) : ( |
||||||
|
<img
|
||||||
|
src={pageUrl}
|
||||||
|
alt={`Page ${currentPage}`} |
||||||
|
className="w-full h-auto object-contain rounded-lg" |
||||||
|
/> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
|
||||||
|
{/* Navigation Controls */} |
||||||
|
<div className="flex items-center gap-4 mt-6"> |
||||||
|
<Button |
||||||
|
onClick={goToPreviousPage} |
||||||
|
disabled={currentPage <= 1 || loading} |
||||||
|
className={`px-4 py-2 rounded-lg ${ |
||||||
|
currentPage <= 1 || loading |
||||||
|
? 'bg-purple-700/50 cursor-not-allowed' |
||||||
|
: 'bg-purple-700 hover:bg-purple-500 text-white' |
||||||
|
} transition-colors`}
|
||||||
|
> |
||||||
|
Previous |
||||||
|
</Button> |
||||||
|
|
||||||
|
<div className="text-lg font-medium"> |
||||||
|
Page {currentPage} of {totalPages} |
||||||
|
</div> |
||||||
|
|
||||||
|
<Button |
||||||
|
onClick={goToNextPage} |
||||||
|
disabled={currentPage >= totalPages || loading} |
||||||
|
className={`px-4 py-2 rounded-lg ${ |
||||||
|
currentPage >= totalPages || loading |
||||||
|
? 'bg-purple-700/50 cursor-not-allowed' |
||||||
|
: 'bg-purple-700 hover:bg-purple-500 text-white' |
||||||
|
} transition-colors`}
|
||||||
|
> |
||||||
|
Next |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
Binary file not shown.
@ -0,0 +1,18 @@ |
|||||||
|
'use client' |
||||||
|
|
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||||
|
import CommonView from "@/views/CommonView" |
||||||
|
import CourseIndividualContentWrapper from "./_partials/CourseIndividualContentWrapper" |
||||||
|
|
||||||
|
const CourseDetailPage : React.FC = () => { |
||||||
|
return( |
||||||
|
<AppContextProvider> |
||||||
|
<CommonView> |
||||||
|
|
||||||
|
<CourseIndividualContentWrapper /> |
||||||
|
</CommonView> |
||||||
|
</AppContextProvider> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CourseDetailPage |
@ -0,0 +1,98 @@ |
|||||||
|
// src/components/DiscussionList.tsx
|
||||||
|
"use client"; |
||||||
|
|
||||||
|
import React, { useState } from "react"; |
||||||
|
import { useSearchParams } from "next/navigation"; |
||||||
|
import Image from "next/image"; |
||||||
|
import { Card, CardContent } from "@/components/ui/card"; |
||||||
|
|
||||||
|
const SearchFeature: React.FC = () => { |
||||||
|
const [searchQuery, setSearchQuery] = useState(""); |
||||||
|
const searchParams = useSearchParams(); |
||||||
|
|
||||||
|
const discussions = [ |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
title: "Introduction to Computer Science", |
||||||
|
description: |
||||||
|
"Learn the basics of programming and computer science concepts", |
||||||
|
publishedDate: "2025-01-10", |
||||||
|
enrolledStudent: 156, |
||||||
|
activeDiscussionCount: 23, |
||||||
|
publishedBy: "Prof. Smith", |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 2, |
||||||
|
title: "Advanced Mathematics", |
||||||
|
description: "Covering calculus, linear algebra and statistics", |
||||||
|
publishedDate: "2025-01-09", |
||||||
|
enrolledStudent: 89, |
||||||
|
activeDiscussionCount: 15, |
||||||
|
publishedBy: "Dr. Johnson", |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 3, |
||||||
|
title: "Digital Marketing Fundamentals", |
||||||
|
description: "Understanding modern marketing strategies", |
||||||
|
publishedDate: "2025-01-08", |
||||||
|
enrolledStudent: 234, |
||||||
|
activeDiscussionCount: 45, |
||||||
|
publishedBy: "Prof. Williams", |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="container mx-auto p-6 space-y-6"> |
||||||
|
{/* Discussion Cards */} |
||||||
|
<div className="space-y-4"> |
||||||
|
{discussions.map((discussion) => ( |
||||||
|
<Card |
||||||
|
key={discussion.id} |
||||||
|
className="hover:shadow-lg transition-shadow" |
||||||
|
> |
||||||
|
<CardContent className="p-6 flex gap-4"> |
||||||
|
<div > |
||||||
|
<Image src="https://placehold.jp/400x250.png"alt="placeholder" width={400} height={250} className="w-[400] h-auto rounded-2xl"/> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<div className="flex items-start justify-between"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<h3 className="text-xl font-semibold text-purple-700"> |
||||||
|
{discussion.title} |
||||||
|
</h3> |
||||||
|
<p className="text-gray-600">{discussion.description}</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="mt-4 flex flex-wrap gap-4 text-sm text-gray-500"> |
||||||
|
<span className="flex items-center"> |
||||||
|
<span className="font-medium">Published:</span> |
||||||
|
<span className="ml-1">{discussion.publishedDate}</span> |
||||||
|
</span> |
||||||
|
<span className="flex items-center"> |
||||||
|
<span className="font-medium">Enrolled:</span> |
||||||
|
<span className="ml-1"> |
||||||
|
{discussion.enrolledStudent} students |
||||||
|
</span> |
||||||
|
</span> |
||||||
|
<span className="flex items-center"> |
||||||
|
<span className="font-medium">Active Discussions:</span> |
||||||
|
<span className="ml-1"> |
||||||
|
{discussion.activeDiscussionCount} |
||||||
|
</span> |
||||||
|
</span> |
||||||
|
<span className="flex items-center"> |
||||||
|
<span className="font-medium">Published by:</span> |
||||||
|
<span className="ml-1">{discussion.publishedBy}</span> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</CardContent> |
||||||
|
</Card> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default SearchFeature; |
@ -0,0 +1,16 @@ |
|||||||
|
'use client' |
||||||
|
import AppContextProvider from "@/helpers/context/AppContextProvider" |
||||||
|
import SearchFeature from "./_partials/SearchFeature" |
||||||
|
import CommonView from "@/views/CommonView" |
||||||
|
|
||||||
|
const SearchFeatureIndexPage : React.FC = () => { |
||||||
|
return( |
||||||
|
<AppContextProvider> |
||||||
|
<CommonView> |
||||||
|
<SearchFeature /> |
||||||
|
</CommonView> |
||||||
|
</AppContextProvider> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default SearchFeatureIndexPage |
@ -0,0 +1,24 @@ |
|||||||
|
export interface CourseData { |
||||||
|
title: string; |
||||||
|
instructor: { |
||||||
|
name: string; |
||||||
|
title: string; |
||||||
|
avatar: string; |
||||||
|
}; |
||||||
|
description: string; |
||||||
|
category: string; |
||||||
|
duration: string; |
||||||
|
enrolledStudents: number; |
||||||
|
rating: number; |
||||||
|
totalReviews: number; |
||||||
|
completionRate: number; |
||||||
|
lastUpdated: string; |
||||||
|
chapters: Chapter[]; |
||||||
|
requirements: string[]; |
||||||
|
} |
||||||
|
|
||||||
|
interface Chapter { |
||||||
|
title: string; |
||||||
|
duration: string; |
||||||
|
isCompleted: boolean; |
||||||
|
} |
Loading…
Reference in new issue