diff --git a/frontend/src/pages/Academy/Courses/CourseDetail.jsx b/frontend/src/pages/Academy/Courses/CourseDetail.jsx index e19f383..0b011f6 100644 --- a/frontend/src/pages/Academy/Courses/CourseDetail.jsx +++ b/frontend/src/pages/Academy/Courses/CourseDetail.jsx @@ -1,7 +1,13 @@ /* global process */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { FiArrowLeft } from 'react-icons/fi'; +import { + FiArrowLeft, + FiCheck, + FiChevronDown, + FiPlay, + FiStar, +} from 'react-icons/fi'; // ------------------------------ // STRIPE IMPORTS @@ -14,8 +20,6 @@ import { loadStripe } from "@stripe/stripe-js"; // ------------------------------ import CheckoutForm from "../../../components/Shared/CheckoutForm"; -import { FiClock, FiDollarSign } from 'react-icons/fi'; - // ------------------------------ // STRIPE INITIALIZATION // ------------------------------ @@ -191,18 +195,39 @@ function CourseDetail() { // ------------------------------ // HELPERS // ------------------------------ - const _getCoursePrice = (c) => { - if (!c) return 'Free'; - if (c.isLiteVersion) return 'Free (Lite)'; - if (c.priceAmount && c.priceAmount > 0) { - return `$${c.priceAmount}`; + const getShortDescription = (value) => { + const text = String(value || '').trim(); + if (!text) return 'Build your freelance career with practical, real-world guidance.'; + const firstSentence = text.split(/(?<=[.!?])\s+/)[0] || text; + return firstSentence.length > 140 ? `${firstSentence.slice(0, 137).trimEnd()}...` : firstSentence; + }; + + const formatDate = (value) => { + if (!value) return '00/0000'; + try { + return new Date(value).toLocaleDateString(); + } catch { + return '00/0000'; } - return 'Free'; }; - const _formatDuration = (duration) => { - if (!duration) return 'N/A'; - return duration; + const formatMinutes = (value) => { + const minutes = Number(value || 0); + if (!Number.isFinite(minutes) || minutes <= 0) return null; + const hours = Math.floor(minutes / 60); + const mins = Math.round(minutes % 60); + return `${hours}h ${String(mins).padStart(2, '0')}m`; + }; + + const getTotalLectureCount = (modules = []) => ( + modules.reduce((total, moduleItem) => total + (moduleItem?.learningPoints?.length || 0), 0) + ); + + const handleModuleKeyDown = (event, moduleKey) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + toggleModule(moduleKey); + } }; // ------------------------------ @@ -240,130 +265,241 @@ function CourseDetail() { // ------------------------------ // MAIN RENDER // ------------------------------ - const _publishedDate = course?.createdAt - ? new Date(course.createdAt).toLocaleDateString() - : null; + const modules = Array.isArray(course?.modules) ? course.modules : []; + const totalLectures = getTotalLectureCount(modules); + const totalLength = formatMinutes(course?.estimatedMinutes) || course?.duration || '00h 00m'; + const priceAmount = Number(course?.priceAmount ?? course?.pricing?.amount ?? 0); + const isFree = Boolean(course?.isFree) || course?.isLiteVersion || priceAmount === 0; + const hasSubscription = course?.subscription?.isSubscriptionCourse === true || course?.subscription?.isSubscriptionCourse === 'true'; + const tierLabel = course?.subscription?.tier || 'Standard'; + const shortDescription = getShortDescription(course?.description); return ( -
+
- - {/* COURSE HEADER */} -
-
-
- {course.thumbnail ? ( - {course.title} - ) : ( -
Image
- )} + {/* HERO */} +
+
+

+ Courses > {course.category || 'General'} +

+

{course.title}

+

{shortDescription}

+
+

Instructor: {course?.instructor?.name || 'UniFreelancer Academy'}

+

Last updated: {formatDate(course?.updatedAt || course?.createdAt)}

+

Language/Subtitles: English

+
+
+ {course.thumbnail ? ( + {course.title} + ) : ( +
+ Course Image +
+ )} +
+
-
-
- {course.isLiteVersion && ( - Lite Version - )} - - {course.difficulty || "Beginner"} - - - {course.category || "General"} + {/* INFO BAR */} +
+
+ Tier Level + {tierLabel} +
+
+ Level + {course.difficulty || 'Beginner'} + Recommended Experience +
+
+ Ratings +
+ + + + + +
+ No ratings yet +
+ {(isFree || hasSubscription) && ( +
+
+ + + Included with {tierLabel} subscription
+
+ )} + {!isFree && ( +
+ +
+ )} +
+ + {/* LEARNING + OVERVIEW */} +
+
+
+

What you'll learn

+
+ {(course.learningPoints?.length ? course.learningPoints : [ + 'Build a signature freelance brand clients trust.', + 'Define your unique positioning and messaging.', + 'Create content that drives inbound leads.', + 'Develop a repeatable client acquisition system.', + 'Strengthen your portfolio and proposal strategy.', + 'Deliver work that turns into referrals.', + ]).map((point, index) => ( +
+ + {point} +
+ ))} +
+
-

{course.title}

+
+

Overview

+

+ {course.description || 'No description available for this course yet.'} +

+
+
-
-
- {course.duration || "N/A"} +
+
+ {course.thumbnail ? ( + {`${course.title} + ) : ( +
+ Preview Video
-
- {" "} - {course.isFree ? "Free" : `$${course.priceAmount}`} + )} +
+
+
-
- - {/* COURSE OVERVIEW */} -
-

Course Overview

-

{course.description}

-
+
+ + {/* COURSE CONTENT */} +
+

Course Content

+

+ {`${modules.length} sections | ${totalLectures} lectures | ${totalLength} total length`} +

+ +
+ {modules.length === 0 ? ( +
Course content will be available soon.
+ ) : ( + modules.map((moduleItem, index) => { + const moduleKey = moduleItem._id || index; + const embedUrl = toYouTubeEmbedUrl(moduleItem.videoUrl); + const moduleLectures = moduleItem?.learningPoints?.length || 0; + const moduleLength = formatMinutes(moduleItem?.estimatedMinutes) || moduleItem?.duration || 'Length'; + + return ( +
+
toggleModule(moduleKey)} + onKeyDown={(event) => handleModuleKeyDown(event, moduleKey)} + className="flex items-center gap-4 px-6 py-4 hover:bg-light-secondary/40 transition-colors cursor-pointer" + > +
+ {moduleItem.thumbnail ? ( + + ) : ( + 'Module' + )} +
+
+

{moduleItem.title || `Module ${index + 1}`}

+

{`${moduleLectures} lectures | ${moduleLength}`}

+
+ +
- {/* COURSE MODULES */} - {course.modules?.length > 0 && ( -
-

Course Modules

- - {course.modules.map((module, index) => { - const key = module._id || index; - const embedUrl = toYouTubeEmbedUrl(module.videoUrl); - - return ( -
-
toggleModule(key)} - > -

{module.title}

- {expandedModules[key] ? "−" : "+"} + {expandedModules[moduleKey] && ( +
+

+ {moduleItem.description || 'Module description will be available soon.'} +

+ + {embedUrl && ( +
+ + + {openVideos[moduleKey] && ( +