@@ -19,11 +21,11 @@ const CommunitySection = () => {
-
{}+
+
{AuthorCount?.totalAuthors}+
Expert Contributors
-
1000+
+
{TotalCourses?.totalCourses}+
Research-Based Courses
diff --git a/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx b/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx
index b3d5b93..b017e53 100644
--- a/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx
+++ b/frontend/edu-connect/src/app/_partials/FeaturedCourses.tsx
@@ -3,75 +3,13 @@ import CourseCard from '@/components/elements/CourseCard';
import useSWR from 'swr';
import { APP_BASE_URL } from '@/utils/constants';
import { defaultFetcher } from '@/helpers/fetch.helper';
+import Link from 'next/link';
+import { routes } from '@/lib/routes';
const FeaturedCourses = () => {
- const { data : CourseList } = useSWR(APP_BASE_URL + '' , defaultFetcher)
- const courses = [
- {
- id: 1,
- title: 'Foundation course to under stand about software',
- category: 'Data & Tech',
- lessons: 23,
- duration: '1hr 30 min',
- price: 32.00,
- originalPrice: 89.00,
- instructor: {
- name : 'Micie John',
- image: 'https://dummyimage.com/300'
- },
- rating: 4.5,
- reviews: 44,
- image: 'https://dummyimage.com/300'
- },
- {
- id: 2,
- title: 'Nidhies course to under stand about softwere',
- category: 'Mechanical',
- lessons: 29,
- duration: '2hr 30 min',
- price: 32.00,
- originalPrice: 89.00,
- instructor: {
- name : 'Micie John',
- image: 'https://dummyimage.com/300'
- },
- rating: 4.5,
- reviews: 44,
- image: 'https://dummyimage.com/300'
- },
- {
- id: 3,
- title: 'Minws course to under stand about solution',
- category: 'Development',
- lessons: 25,
- duration: '1hr 40 min',
- price: 40.00,
- originalPrice: 89.00,
- instructor: {
- name : 'Micie John',
- image: 'https://dummyimage.com/300'
- },
- rating: 4.5,
- reviews: 44,
- image: 'https://dummyimage.com/300'
- },
- {
- id: 3,
- title: 'Minws course to under stand about solution',
- category: 'Development',
- lessons: 25,
- duration: '1hr 40 min',
- price: 40.00,
- originalPrice: 89.00,
- instructor: {
- name : 'Micie John',
- image: 'https://dummyimage.com/300'
- },
- rating: 4.5,
- reviews: 44,
- image: 'https://dummyimage.com/300'
- }
- ];
+ const { data : CourseList } = useSWR(APP_BASE_URL + '/api/course/listAll' , defaultFetcher)
+
+ console.log(CourseList)
return (
@@ -86,18 +24,20 @@ const FeaturedCourses = () => {
{
- courses?.map((course : Record
, index) => {
+ CourseList?.data?.length && CourseList?.data?.map((course : Record , index) => {
return(
-
+
+
+
)
})
}
diff --git a/frontend/edu-connect/src/app/_partials/HeroSection.tsx b/frontend/edu-connect/src/app/_partials/HeroSection.tsx
index 6888b7e..eb8d7bb 100644
--- a/frontend/edu-connect/src/app/_partials/HeroSection.tsx
+++ b/frontend/edu-connect/src/app/_partials/HeroSection.tsx
@@ -5,7 +5,8 @@ import Image from 'next/image';
import useSWR from "swr"
const HeroSection: React.FC = () => {
- const {data : TotalUserCount } = useSWR(APP_BASE_URL + '/api/user/count' , defaultFetcher)
+ const {data : TotalUserCount } = useSWR(APP_BASE_URL + '/api/public/stats/total-users' , defaultFetcher)
+ console.log(TotalUserCount)
return (
@@ -40,7 +41,7 @@ const HeroSection: React.FC = () => {
/>
-
{}+
+
{TotalUserCount?.totalUsers}+
Active Learners
diff --git a/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx b/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx
index bf2a3dc..b7d959f 100644
--- a/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx
+++ b/frontend/edu-connect/src/app/auth/login/_partials/LoginForm.tsx
@@ -3,7 +3,7 @@ import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/com
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
-import { EyeIcon, EyeOffIcon } from 'lucide-react';
+import { EyeIcon, EyeOffIcon, Loader } from 'lucide-react';
import Link from 'next/link';
import { routes } from '@/lib/routes';
import { useToast } from '@/hooks/use-toast';
@@ -82,8 +82,8 @@ export default function LoginForm() {
},[router])
return (
-
-
+
+
Login
@@ -127,7 +127,14 @@ export default function LoginForm() {
type="submit"
className="w-full h-11 bg-purple-600 hover:bg-purple-700 text-white font-semibold"
>
- Login
+ {
+ loading ?
+ <>
+
+ Login
+ >
+ : 'Login'
+ }
diff --git a/frontend/edu-connect/src/app/courses/page.tsx b/frontend/edu-connect/src/app/courses/page.tsx
new file mode 100644
index 0000000..a15be68
--- /dev/null
+++ b/frontend/edu-connect/src/app/courses/page.tsx
@@ -0,0 +1,46 @@
+'use client'
+import { PageHeading } from "@/components/(dashboard)/ui/title"
+import CommonContainer from "@/components/elements/CommonContainer"
+import CourseCard from "@/components/elements/CourseCard"
+import AppContextProvider from "@/helpers/context/AppContextProvider"
+import { defaultFetcher } from "@/helpers/fetch.helper"
+import { APP_BASE_URL } from "@/utils/constants"
+import CommonView from "@/views/CommonView"
+import React from "react"
+import useSWR from "swr"
+
+const AllCourseList : React.FC = () => {
+ const { data } = useSWR(APP_BASE_URL + '/api/course/listAll' , defaultFetcher)
+
+ console.log(data)
+ return(
+
+
+
+ All Courses
+
+ {
+ data?.data?.length && data?.data?.map((course) => {
+ return(
+
+ )
+ })
+ }
+
+
+
+
+ )
+}
+
+export default AllCourseList
\ No newline at end of file
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailHeroSection.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailHeroSection.tsx
index 129aa18..42b170e 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailHeroSection.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailHeroSection.tsx
@@ -2,46 +2,112 @@ 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"
+import { AuthContext } from "@/helpers/context/AuthProvider";
+import { fetchHeader } from "@/helpers/fetch.helper";
+import { useToast } from "@/hooks/use-toast";
+import { APP_BASE_URL } from "@/utils/constants";
+import { Loader } from "lucide-react";
+import Image from "next/image";
+import { useContext, useState } from "react";
interface CourseDetailHeroSectionProps {
- courseData: CourseData;
+ courseData: Record
;
+ mutate : () => void
}
-const CourseDetailHeroSection: React.FC = ({courseData}) => {
+const CourseDetailHeroSection: React.FC = ({ courseData , mutate }) => {
+
+ console.log(courseData)
+
+ const { user } = useContext(AuthContext)
+ const { toast } = useToast();
+
+ const [loading , setLoading] = useState(false)
+
+
+ const handleEnrollNow = async () : Promise => {
+ setLoading(true)
+ const formData = new FormData();
+ formData.append('course_uuid' , courseData.id)
+ try{
+ const res = await fetch(APP_BASE_URL + '' , {
+ headers : fetchHeader() ,
+ method : 'POST' ,
+ body : formData
+ })
+ const data = await res.json();
+ if(res.status == 200){
+ toast({
+ title : 'Successfully enrolled !',
+ description : data?.message ,
+ variant : 'success'
+ })
+ mutate()
+ }else{
+ toast({
+ title : 'Successfully enrolled !',
+ description : data?.message ,
+ variant : 'destructive'
+ })
+ }
+ }catch(error : any){
+ toast({
+ title : 'Successfully enrolled !',
+ description : 'something went wrong , please try again' ,
+ variant : 'destructive'
+ })
+ }finally{
+ setLoading(false)
+ }
+
+ }
+
return(
-
{courseData.title}
-
{courseData.description}
+
{courseData?.courseName}
+
{courseData?.courseDescription}
-
+
SJ
-
{courseData.instructor.name}
-
{courseData.instructor.title}
-
Published at {courseData?.lastUpdated}
+
{courseData?.instructor?.firstName}
+
{courseData?.instructor?.title}
+
Published at {courseData?.creationDate}
{/* course image */}
+

-
+
+ {
+ courseData?.selfEnrollment?.isEnrolled &&
+
+ }
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailStats.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailStats.tsx
index 21e3280..aade949 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailStats.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDetailStats.tsx
@@ -3,7 +3,7 @@ import { CourseData } from "@/helpers/apiSchema/course.schema"
import { BookOpen, Clock, MessageSquare, Users } from "lucide-react"
-const CourseDetailStats :React.FC<{courseData : CourseData}> = ({courseData}) => {
+const CourseDetailStats :React.FC<{courseData : Record
}> = ({courseData}) => {
return(
<>
@@ -13,7 +13,7 @@ const CourseDetailStats :React.FC<{courseData : CourseData}> = ({courseData}) =>
Duration
-
{courseData.duration}
+
{courseData?.duration ?? 0}
@@ -23,7 +23,7 @@ const CourseDetailStats :React.FC<{courseData : CourseData}> = ({courseData}) =>
Enrolled
-
{courseData.enrolledStudents} students
+
{courseData?.enrollmentSummary?.courseData ?? 0} students
@@ -33,7 +33,7 @@ const CourseDetailStats :React.FC<{courseData : CourseData}> = ({courseData}) =>
Reviews
-
{courseData.rating} ({courseData.totalReviews})
+
{courseData?.enrollmentSummary?.totalChats ?? 0}
@@ -42,8 +42,8 @@ const CourseDetailStats :React.FC<{courseData : CourseData}> = ({courseData}) =>
-
Completion
-
{courseData.completionRate}%
+
Discussion group member
+
{courseData?.enrollmentSummary?.usersInChat ?? 0}
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDiscussion.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDiscussion.tsx
new file mode 100644
index 0000000..3c950b5
--- /dev/null
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseDiscussion.tsx
@@ -0,0 +1,300 @@
+import React, { useState, useRef } from 'react';
+import useSWRInfinite from 'swr/infinite';
+import { Card } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
+import { Textarea } from "@/components/ui/textarea";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { MessageCircle, Reply, X } from "lucide-react";
+import { APP_BASE_URL } from '@/utils/constants';
+import { getEduConnectAccessToken } from '@/helpers/token.helper';
+
+// Types
+interface Reply {
+ id: number;
+ user: string;
+ avatar: string;
+ content: string;
+ timestamp: string;
+}
+
+interface Discussion {
+ id: number;
+ user: string;
+ avatar: string;
+ content: string;
+ likes: number;
+ replies: Reply[];
+ timestamp: string;
+}
+
+interface PageData {
+ discussions: Discussion[];
+ hasMore: boolean;
+}
+
+interface DiscussionSectionProps {
+ courseUuid: string;
+}
+
+// Key generator for SWR
+const getKey = (courseUuid: string) => (pageIndex: number, previousPageData: PageData | null) => {
+ if (previousPageData && !previousPageData.discussions.length) return null;
+ return `${APP_BASE_URL}/api/chat/get?page=${pageIndex}`;
+};
+
+// Fetcher function
+const fetcher = async (url: string, courseUuid: string): Promise
=> {
+ const formData = new FormData();
+ formData.append('course_uuid', courseUuid);
+
+ const response = await fetch(url, {
+ method: 'POST',
+ body: formData ,
+ headers : {
+ Authorization : `Bearer ${getEduConnectAccessToken()}`
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch discussions');
+ }
+
+ const data = await response.json();
+ return {
+ discussions: data.discussions.map((discussion: any) => ({
+ ...discussion,
+ timestamp: new Date(discussion.timestamp).toLocaleString()
+ })),
+ hasMore: data.hasMore
+ };
+};
+
+const EnhancedDiscussionSection: React.FC = ({ courseUuid }) => {
+ const [comment, setComment] = useState('');
+ const [replyingTo, setReplyingTo] = useState<{ commentId: number; userName: string } | null>(null);
+ const loadingRef = useRef(null);
+ const containerRef = useRef(null);
+
+ const {
+ data,
+ error,
+ size,
+ setSize,
+ isValidating,
+ mutate
+ } = useSWRInfinite(
+ getKey(courseUuid),
+ (url) => fetcher(url, courseUuid),
+ {
+ revalidateFirstPage: false,
+ revalidateOnFocus: false,
+ persistSize: true
+ }
+ );
+
+ const allDiscussions = data ? data.flatMap(page => page.discussions) : [];
+ const hasMore = data ? data[data.length - 1]?.hasMore : true;
+ const isLoadingMore = !data && !error || (size > 0 && data && typeof data[size - 1] === "undefined");
+
+ // Intersection Observer for infinite loading
+ React.useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting && hasMore && !isValidating) {
+ setSize(size + 1);
+ }
+ },
+ { threshold: 0.1 }
+ );
+
+ if (loadingRef.current) {
+ observer.observe(loadingRef.current);
+ }
+
+ return () => observer.disconnect();
+ }, [hasMore, isValidating, setSize, size]);
+
+ const handleSubmit = async () => {
+ if (comment.trim()) {
+ const formData = new FormData();
+ formData.append('course_uuid', courseUuid);
+ formData.append('content', comment);
+ if (replyingTo) {
+ formData.append('parent_id', replyingTo.commentId.toString());
+ }
+
+ try {
+ const response = await fetch('/api/chat/post', {
+ method: 'POST',
+ body: formData
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to post comment');
+ }
+
+ const newComment = await response.json();
+
+ // Optimistically update the UI
+ const updatedData = [...(data || [])];
+ if (replyingTo) {
+ // Update the discussion with the new reply
+ updatedData.forEach(page => {
+ page.discussions = page.discussions.map(discussion => {
+ if (discussion.id === replyingTo.commentId) {
+ return {
+ ...discussion,
+ replies: [...discussion.replies, {
+ ...newComment,
+ timestamp: 'Just now'
+ }]
+ };
+ }
+ return discussion;
+ });
+ });
+ } else {
+ // Add new top-level comment
+ if (updatedData[0]) {
+ updatedData[0].discussions = [{
+ ...newComment,
+ replies: [],
+ timestamp: 'Just now'
+ }, ...updatedData[0].discussions];
+ }
+ }
+
+ await mutate(updatedData, false);
+ setComment('');
+ setReplyingTo(null);
+ } catch (error) {
+ console.error('Failed to post comment:', error);
+ // You might want to show an error message to the user here
+ }
+ }
+ };
+
+ const handleReply = (commentId: number, userName: string) => {
+ setReplyingTo({ commentId, userName });
+ setComment(`@${userName} `);
+ };
+
+ const cancelReply = () => {
+ setReplyingTo(null);
+ setComment('');
+ };
+
+ if (error) {
+ return (
+
+ Error loading discussions. Please try again later.
+
+ );
+ }
+
+ return (
+
+
+
+ {allDiscussions.map((discussion) => (
+
+
+
+
+
+ {discussion.user[0]}
+
+
+
+ {discussion.user}
+ {discussion.timestamp}
+
+
{discussion.content}
+
+
+
+
+ {discussion.replies.length} replies
+
+
+
+
+
+ {discussion.replies.length > 0 && (
+
+ {discussion.replies.map((reply) => (
+
+
+
+ {reply.user[0]}
+
+
+
+ {reply.user}
+ {reply.timestamp}
+
+
{reply.content}
+
+
+ ))}
+
+ )}
+
+
+ ))}
+
+
+ {isLoadingMore && (
+
+ )}
+ {!hasMore && allDiscussions.length > 0 && (
+
No more discussions
+ )}
+
+
+
+
+
+
+
+
+ U
+
+
+ {replyingTo && (
+
+ Replying to {replyingTo.userName}
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default EnhancedDiscussionSection;
\ No newline at end of file
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx
index cd86274..e247942 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseGenerateQuestions.tsx
@@ -6,77 +6,76 @@ import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Check, X } from "lucide-react";
+import useSWR from 'swr';
+import { APP_BASE_URL } from '@/utils/constants';
+import { defaultFetcher } from '@/helpers/fetch.helper';
+import { getEduConnectAccessToken } from '@/helpers/token.helper';
+import { useToast } from '@/hooks/use-toast';
-const QuizGenerator = () => {
- // Submitted questions history
- const [submittedQuestions] = useState([
- {
- id: 1,
- question: "What is React's Virtual DOM?",
- options: [
- "A complete DOM copy",
- "A lightweight copy of the DOM",
- "A browser feature",
- "A routing system"
- ],
- selectedOption: 0, // User selected
- correctOption: 1, // Correct answer
- },
- {
- id: 2,
- question: "What hook manages side effects?",
- options: [
- "useState",
- "useReducer",
- "useEffect",
- "useContext"
- ],
- selectedOption: 2,
- correctOption: 2,
- },
- ]);
-
- // Unsubmitted questions
- const [pendingQuestions, setPendingQuestions] = useState([
- {
- id: 1,
- question: "Which of these is a state management library?",
- options: ["Redux", "Axios", "Lodash", "Moment"],
- selectedOption: null
- },
- {
- id: 2,
- question: "What does CSS stand for?",
- options: [
- "Computer Style Sheets",
- "Cascading Style Sheets",
- "Creative Style Sheets",
- "Colorful Style Sheets"
- ],
- selectedOption: null
- },
- ]);
-
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
-
- const handleOptionSelect = (value) => {
- const updatedQuestions = [...pendingQuestions];
- updatedQuestions[currentQuestionIndex].selectedOption = parseInt(value);
- setPendingQuestions(updatedQuestions);
- };
+interface Course {
+ id: number;
+ name: string;
+ description: string;
+}
+
+interface Creator {
+ id: number;
+ firstName: string;
+ lastName: string;
+ username: string;
+ pfpFilename: string;
+}
+
+interface QuizAttempt {
+ id: number;
+ quizID: number;
+ isActive: boolean;
+ creationDate: string;
+ quizAnswers: string[];
+ quizQuestion: string;
+ userAnswer: string | null;
+ quizCorrectAnswer: string;
+ isCorrect: boolean;
+ course: Course;
+ creator: Creator;
+}
+
+interface QuizGeneratorProps {
+ current_page: number;
+ course_uuid: string;
+}
+
+const QuizGenerator: React.FC = ({
+ current_page,
+ course_uuid ,
+}) => {
+ const { data: completedQuizzes, error: completedError } = useSWR(
+ `${APP_BASE_URL}/api/quiz/get/allComplete?course_uuid=${course_uuid}`,
+ defaultFetcher
+ );
- const generateQuiz = () => {
- // Reset selections and shuffle pending questions
- const shuffledQuestions = [...pendingQuestions].map(q => ({
- ...q,
- selectedOption: null
+ const { toast } = useToast()
+
+ const { data: uncompletedQuizzes, error: uncompletedError , mutate } = useSWR(
+ `${APP_BASE_URL}/api/quiz/get/personalIncomplete?course_uuid=${course_uuid}`,
+ defaultFetcher
+ );
+
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
+ const [pendingAnswers, setPendingAnswers] = useState>({});
+
+ const isLoading = !completedQuizzes || !uncompletedQuizzes;
+ const isError = completedError || uncompletedError;
+
+ const handleOptionSelect = (quizId: number, value: string) => {
+ setPendingAnswers(prev => ({
+ ...prev,
+ [quizId]: value
}));
- setPendingQuestions(shuffledQuestions);
- setCurrentQuestionIndex(0);
};
const handleNext = () => {
- if (currentQuestionIndex < pendingQuestions.length - 1) {
+ if (uncompletedQuizzes && currentQuestionIndex < uncompletedQuizzes.length - 1) {
setCurrentQuestionIndex(prev => prev + 1);
}
};
@@ -87,93 +86,150 @@ const QuizGenerator = () => {
}
};
+ if (isError) {
+ return (
+
+
+ Error loading quiz data
+
+
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+
+ Loading quiz data...
+
+
+ );
+ }
+
+ const handleGenerateQuiz = async() : Promise => {
+ try{
+ const formData = new FormData();
+
+ formData.append('course_uuid' , course_uuid);
+ formData.append('page' , (current_page + 1)?.toString())
+ const res = await fetch(APP_BASE_URL + '/api/quiz/generate' , {
+ headers : {
+ Authorization : `Bearer ${getEduConnectAccessToken()}`
+ } ,
+ body : formData,
+ method : 'POST'
+ });
+ if(res.status == 200){
+ mutate()
+ toast({
+ title : 'Sucessfully fetched !',
+ variant : 'success'
+ })
+ }
+
+ }catch(error : any){
+ toast({
+ title : 'Failed to fetch',
+ variant : 'destructive'
+ })
+ }
+ }
+
+ const currentQuiz = uncompletedQuizzes?.[currentQuestionIndex];
+
return (
-
- {/* Submitted Questions History */}
-
-
+
+
+
Quiz History
- {submittedQuestions.map((q, index) => (
-
+ {completedQuizzes?.data?.map((quiz, index) => (
+
-
{q.question}
+
+
{quiz.quizQuestion}
+
+ by {quiz.creator.firstName} {quiz.creator.lastName}
+
+
- {q.options.map((option, i) => (
+ {quiz.quizAnswers.map((answer, i) => (
- {option}
- {i === q.correctOption && (
+ {answer}
+ {answer === quiz.quizCorrectAnswer && (
)}
- {i === q.selectedOption && i !== q.correctOption && (
+ {answer === quiz.userAnswer && answer !== quiz.quizCorrectAnswer && (
)}
))}
- {index < submittedQuestions.length - 1 &&
}
+ {index < completedQuizzes?.length - 1 &&
}
))}
+ {
+ completedQuizzes?.data?.length === 0 && 'No Quiz taken yet'
+ }
- {/* Pending Questions Interface */}
-
-
-
- {pendingQuestions.length > 0 ? (
+
+ {currentQuiz ? (
- Question {currentQuestionIndex + 1} of {pendingQuestions.length}
+ Question {currentQuestionIndex + 1} of {uncompletedQuizzes?.length}
- {pendingQuestions[currentQuestionIndex].question}
+ {currentQuiz?.quizQuestion}
handleOptionSelect(currentQuiz.id, value)}
>
- {pendingQuestions[currentQuestionIndex].options.map((option, i) => (
+ {currentQuiz.quizAnswers.map((answer, i) => (
-
-
+
+
))}
-
+
+
@@ -182,7 +238,9 @@ const QuizGenerator = () => {
) : (
- No pending questions available
+
)}
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseIndividualContentWrapper.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseIndividualContentWrapper.tsx
index 5310930..70855cf 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseIndividualContentWrapper.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseIndividualContentWrapper.tsx
@@ -1,5 +1,5 @@
'use client';
-import React from 'react';
+import React, { useContext } from 'react';
import { Card, CardContent } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import CourseDetailHeroSection from './CourseDetailHeroSection';
@@ -7,49 +7,40 @@ import CourseDetailStats from './CourseDetailStats';
import CourseMedia from './CourseMedia';
import CourseProgress from './CourseProgress';
import TryPDF from './TryPDF';
+import { CourseData } from '@/helpers/apiSchema/course.schema';
+import useSWR from 'swr';
+import { APP_BASE_URL } from '@/utils/constants';
+import { defaultFetcher } from '@/helpers/fetch.helper';
+import { AuthContext, AuthProvider } from '@/helpers/context/AuthProvider';
+
+const CourseIndividualContentWrapper : React.FC<{
+ course_uuid : string
+}> = ({
+ course_uuid
+}) => {
+
+ const { user } = useContext(AuthContext)
+ const { data , mutate } = useSWR(APP_BASE_URL + `/api/course/info/${course_uuid}` , defaultFetcher)
-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 (
{/* Hero Section */}
-
+
{/* Course Stats */}
-
+
+
+ {
+ user && data?.data?.selfEnrollment?.isEnrolled && (
+
+
+ )
+ }
-
{/* Course Content */}
-
+
);
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx
index f30e86c..cd033b5 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseMedia.tsx
@@ -1,12 +1,16 @@
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"
import QuizGenerator from "./CourseGenerateQuestions"
+import EnhancedDiscussionSection from "./CourseDiscussion"
+import { useState } from "react"
-const CourseMedia : React.FC<{courseData : CourseData}> = ({courseData}) => {
+const CourseMedia : React.FC<{courseData : Record
}> = ({courseData}) => {
+
+ const [currentPage, setCurrentPage] = useState(0);
+
return(
@@ -15,7 +19,11 @@ const CourseMedia : React.FC<{courseData : CourseData}> = ({courseData}) => {
Course Content
-
+
@@ -26,14 +34,17 @@ const CourseMedia : React.FC<{courseData : CourseData}> = ({courseData}) => {
Discussions
-
+ {/* */}
+
-
+
)
}
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseProgress.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseProgress.tsx
index 6dc5f63..e36c224 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseProgress.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/CourseProgress.tsx
@@ -3,15 +3,15 @@ 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}) => {
+const CourseProgress :React.FC<{courseData : Record
}> = ({courseData}) => {
return(
Your Progress
-
+
- {courseData.completionRate}% Complete
+ {((courseData?.selfEnrollment?.maxPage) / courseData?.totalPages) * 100 }% Complete
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/_partials/TryPDF.tsx b/frontend/edu-connect/src/app/courses/show/[id]/_partials/TryPDF.tsx
index d5cd8d7..44e00a8 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/_partials/TryPDF.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/_partials/TryPDF.tsx
@@ -1,97 +1,73 @@
+import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
-import React, { useEffect, useState } from 'react';
+import { APP_BASE_URL } from '@/utils/constants';
-export default function CustomPDFViewer() {
- const [currentPage, setCurrentPage] = useState(1);
- const [totalPages, setTotalPages] = useState(0);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [pageUrl, setPageUrl] = useState('');
+const CustomPDFViewer = ({
+ pdfPages ,
+ currentPage ,
+ onChange
+} : {
+ pdfPages : Array
+ currentPage : number
+ onChange : (value : any) => void
+}) => {
+ // const [currentPage, setCurrentPage] = useState(0);
+
+ // Your PDF pages array
- // 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 totalPages = pdfPages?.length;
const goToPreviousPage = () => {
- if (currentPage > 1) {
- setCurrentPage(prev => prev - 1);
+ if (currentPage > 0) {
+ onChange((prev : number) => prev - 1);
}
};
const goToNextPage = () => {
- if (currentPage < totalPages) {
- setCurrentPage(prev => prev + 1);
+ if (currentPage < totalPages - 1) {
+ onChange((prev : number) => prev + 1);
}
};
return (
-
- {/* Page Display */}
-
- {loading ? (
-
-
Loading page {currentPage}...
-
- ) : error ? (
-
-
{error}
+ <>
+ {
+ pdfPages && (
+
+ {/* PDF Display */}
+
+
- ) : (
-

- )}
-
- {/* Navigation Controls */}
-
-
-
-
- Page {currentPage} of {totalPages}
+ {/* Navigation Controls */}
+
+
+
+ Page {currentPage + 1} of {totalPages}
+
+
+
-
-
-
-
+ )
+ }
+ >
);
-}
\ No newline at end of file
+};
+
+export default CustomPDFViewer;
\ No newline at end of file
diff --git a/frontend/edu-connect/src/app/courses/show/[id]/page.tsx b/frontend/edu-connect/src/app/courses/show/[id]/page.tsx
index b568395..96426cb 100644
--- a/frontend/edu-connect/src/app/courses/show/[id]/page.tsx
+++ b/frontend/edu-connect/src/app/courses/show/[id]/page.tsx
@@ -3,13 +3,33 @@
import AppContextProvider from "@/helpers/context/AppContextProvider"
import CommonView from "@/views/CommonView"
import CourseIndividualContentWrapper from "./_partials/CourseIndividualContentWrapper"
+import { APP_BASE_URL } from "@/utils/constants"
+import { defaultFetcher } from "@/helpers/fetch.helper"
+import useSWR from "swr"
+import { useEffect } from "react"
+import { useRouter } from "next/navigation"
+import { routes } from "@/lib/routes"
+
+
+const CourseDetailPage : React.FC<{
+ params : Record
+}> = ({params}) => {
+ const { id } = params
+ const router = useRouter()
+ const CourseDetailPage = `${APP_BASE_URL}/api/course/info/${id}`
+ const { data } = useSWR(CourseDetailPage, defaultFetcher);
+
+ console.log(data)
+
+
+ useEffect(() => {
+ if(!id) router.replace(routes.COURSE_LIST_INDEX)
+ },[])
-const CourseDetailPage : React.FC = () => {
return(
-
-
+
)
diff --git a/frontend/edu-connect/src/app/my-courses/_partials/EnrolledCourseList.tsx b/frontend/edu-connect/src/app/my-courses/_partials/EnrolledCourseList.tsx
index 35a480b..397d94f 100644
--- a/frontend/edu-connect/src/app/my-courses/_partials/EnrolledCourseList.tsx
+++ b/frontend/edu-connect/src/app/my-courses/_partials/EnrolledCourseList.tsx
@@ -5,7 +5,7 @@ import Image from "next/image"
import useSWR from "swr"
const EnrolledCourseTabContent : React.FC = () => {
- const { data } = useSWR(APP_BASE_URL + '/' , defaultFetcher);
+ const { data } = useSWR(APP_BASE_URL + '/api/course/enrolled' , defaultFetcher);
return (
<>
diff --git a/frontend/edu-connect/src/app/my-courses/_partials/MyCoursesListTabContent.tsx b/frontend/edu-connect/src/app/my-courses/_partials/MyCoursesListTabContent.tsx
index 278663f..f777952 100644
--- a/frontend/edu-connect/src/app/my-courses/_partials/MyCoursesListTabContent.tsx
+++ b/frontend/edu-connect/src/app/my-courses/_partials/MyCoursesListTabContent.tsx
@@ -1,3 +1,4 @@
+import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { defaultFetcher } from "@/helpers/fetch.helper"
import { APP_BASE_URL } from "@/utils/constants"
@@ -5,7 +6,8 @@ import Image from "next/image"
import useSWR from "swr"
const MyCoursesListTabContent = () => {
- const { data } = useSWR(APP_BASE_URL + '/' , defaultFetcher);
+ const { data } = useSWR(APP_BASE_URL + '/api/course/myCourses' , defaultFetcher);
+ console.log(data)
return (
<>
@@ -20,7 +22,7 @@ const MyCoursesListTabContent = () => {
<>
@@ -30,7 +32,7 @@ const MyCoursesListTabContent = () => {
- {course.title}
+ {course.name}
{course.description}
@@ -38,25 +40,35 @@ const MyCoursesListTabContent = () => {
- Published:
- {course.publishedDate}
-
-
- Enrolled:
-
- {course.enrolledStudent} students
+ {
+ course.publishedDate == '1'
+ ? Published
+ : course.publishedDate == '1'
+ ? Pending
+ : Rejected
+ }
+
+
+
+
- Active Discussions:
-
- {course.activeDiscussionCount}
+ Enrolled:
+
+ {course.totalEnrolled ?? 0} students
+
+
+
+
+
+ {course.activeDiscussionCount ?? 0} discussions
+
+
+
+ Published by:
+ {course?.author?.firstName ?? '-'}
-
-
- Published by:
- {course.publishedBy}
-
diff --git a/frontend/edu-connect/src/app/my-courses/_partials/myCoursesTabWrapper.tsx b/frontend/edu-connect/src/app/my-courses/_partials/myCoursesTabWrapper.tsx
index 1ad4f93..b135dd2 100644
--- a/frontend/edu-connect/src/app/my-courses/_partials/myCoursesTabWrapper.tsx
+++ b/frontend/edu-connect/src/app/my-courses/_partials/myCoursesTabWrapper.tsx
@@ -20,7 +20,7 @@ const MyCoursesWrapper = () => {
value="enrolled"
className="justify-start w-full px-3 py-2 text-left data-[state=active]:bg-gray-50 data-[state=active]:border data-[state=active]:border-purple-700/50 rounded-md"
>
- Enrolled Course List
+ Enrolled Course