diff --git a/client/src/components/courses/CourseDetailPage.tsx b/client/src/components/courses/CourseDetailPage.tsx index db07789..b8d5c33 100644 --- a/client/src/components/courses/CourseDetailPage.tsx +++ b/client/src/components/courses/CourseDetailPage.tsx @@ -35,10 +35,14 @@ import { updateChapterProgress, generateCertificateTest, getUserTests, + rateCourse, + getCourseRatings, } from "../../utils/api"; import type { Course } from "../../types"; import { isAuthenticated } from "../../utils/auth"; import TestInstructionsModal from "./TestInstructionsModal"; +import StarRating from "../ui/StarRating"; +import { ToastContainer, ToastType } from "../ui/Toast"; const CourseDetailPage: React.FC = () => { const { id } = useParams<{ id: string }>(); @@ -66,6 +70,16 @@ const CourseDetailPage: React.FC = () => { nextAvailableAt: string; } | null>(null); const [cooldownTimer, setCooldownTimer] = useState(""); + const [rating, setRating] = useState(0); + const [averageRating, setAverageRating] = useState(0); + const [ratingCount, setRatingCount] = useState(0); + const [toasts, setToasts] = useState>([]); useEffect(() => { checkAuth(); @@ -82,6 +96,22 @@ const CourseDetailPage: React.FC = () => { } }, [isAuth, id, course]); + // Fetch user's existing rating when authenticated + useEffect(() => { + if (isAuth === true && id) { + console.log("⭐ Fetching user's existing rating..."); + fetchUserRating(); + } + }, [isAuth, id]); + + // Update rating states when course data is fetched + useEffect(() => { + if (course) { + setAverageRating(course.average_rating || 0); + setRatingCount(course.rating_count || 0); + } + }, [course]); + // Cooldown timer effect useEffect(() => { if (!testCooldown?.isActive) { @@ -120,6 +150,16 @@ const CourseDetailPage: React.FC = () => { return () => clearInterval(interval); }, [testCooldown]); + // Toast helper functions + const addToast = (toast: Omit) => { + const id = Date.now().toString(); + setToasts((prev) => [...prev, { ...toast, id }]); + }; + + const removeToast = (id: string) => { + setToasts((prev) => prev.filter((toast) => toast.id !== id)); + }; + // Check for navigation state (removed - no longer needed) useEffect(() => { // Navigation state handling removed as we now use separate results page @@ -567,6 +607,59 @@ const CourseDetailPage: React.FC = () => { navigate(`/courses/${course.id}/test/${testId}/results`); }; + const handleRatingSubmit = async (rating: number) => { + if (!course) return; + + // Validate that at least one star is selected + if (rating === 0) { + addToast({ + type: "error", + title: "Please select a rating", + message: "You must select at least one star before submitting.", + }); + return; + } + + try { + await rateCourse(course.id, rating); + setRating(rating); + addToast({ + type: "success", + title: "Thank you for your feedback!", + message: "Your rating has been submitted successfully.", + }); + + // Fetch updated ratings from server to get accurate average + await fetchUserRating(); + } catch (error) { + console.error("Error submitting rating:", error); + addToast({ + type: "error", + title: "Failed to submit rating", + message: "Please try again later.", + }); + } + }; + + const fetchUserRating = async () => { + try { + if (!id) return; + const response = await getCourseRatings(id); + + // Update all rating-related states from server response + setAverageRating(response.averageRating); + setRatingCount(response.totalRatings); + + if (response.hasRated && response.userRating !== null) { + setRating(response.userRating); + console.log("✅ User's existing rating loaded:", response.userRating); + } else { + console.log("â„šī¸ User hasn't rated this course yet"); + } + } catch (error) { + console.error("Error fetching ratings:", error); + } + }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString("en-US", { @@ -659,8 +752,14 @@ const CourseDetailPage: React.FC = () => { +
+ { }} /> + + {averageRating.toFixed(1)} ({ratingCount} ratings) + +
{/* Social Share Buttons */} -
+
Share on: + )} +
+
+
+ Rate this course: + +
+ - )} +
+ + + )} {/* Test Results Section */} @@ -1368,7 +1481,10 @@ const CourseDetailPage: React.FC = () => { questions={currentTest.questions} /> )} - + + {/* Toast Notifications */} + + ); }; diff --git a/client/src/components/courses/CoursesPage.tsx b/client/src/components/courses/CoursesPage.tsx index 6908620..48127c6 100644 --- a/client/src/components/courses/CoursesPage.tsx +++ b/client/src/components/courses/CoursesPage.tsx @@ -14,6 +14,7 @@ import { Loader2, } from "lucide-react"; import SEO from "../seo/SEO"; +import StarRating from "../ui/StarRating"; import { getCourses, toggleCourseBookmark, @@ -330,6 +331,26 @@ const CoursesPage: React.FC = () => {

{course.description}

+ + {/* Rating Display */} +
+ { }} + /> + + {course.average_rating == 0 ? "" : course.average_rating.toFixed(1)} + + + ({course.rating_count}{" "} + {course.rating_count <= 1 + ? "rating" + : "ratings"} + ) + +
{isAuth && (