import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { House, Timer, ChevronLeft, ChevronRight, LockIcon, Tag, Tags, Check, ShoppingCart } from "lucide-react"; import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import ResponsiveModal from "@/components/generic/ResponsiveModal"; import { useClientCourses } from "@/contexts/ClientCoursesContext"; import { cn } from "@/lib/utils"; import { Fragment } from "react"; import { PageMeta } from "@/contexts/MetadataContext"; import { useDateFormat } from "@/hooks/useDateFormat"; import api from "@/utils/api.util"; import { resolveTierBadge } from "@/utils/tierBadge.util"; import { Building2 } from "lucide-react"; import { useClientAdvertisements } from "@/contexts/ClientAdvertisementContext"; import { Banner, BannerSkeleton } from "@/components/generic/Blocks/Client/Advertisements/Banner"; import { GitBranch } from "lucide-react"; // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDuration(seconds = 0) { if (!seconds) return null; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); if (h && m) return `${h}h ${m}m`; if (h) return `${h}h`; return `${m}m`; } const ITEMS_PER_PAGE = 10; // ─── Course Card ────────────────────────────────────────────────────────────── const CourseCard = ({ course, tierMap, onViewDetails }) => { const slug = course.subscription ?? "free"; const locked = course.is_locked; const duration = formatDuration(course.duration_seconds); const { rank, label, cls } = resolveTierBadge(slug, tierMap); return (
onViewDetails(course)} >
{locked ? : } {label} {course.level && !locked && ( {course.level.charAt(0).toUpperCase() + course.level.slice(1)} )} {(course.categories ?? []).map((cat) => ( {cat.name} ))} {locked && ( Locked )}

{course.title}

{course.description && (

{course.description}

)}
{duration ?? "—"}
{locked && ( Upgrade to unlock )}
); }; // ─── Skeleton Card ──────────────────────────────────────────────────────────── const CourseCardSkeleton = () => (
); // ─── Pagination ─────────────────────────────────────────────────────────────── const Pagination = ({ currentPage, totalPages, totalItems, itemsPerPage, onPageChange }) => { const start = (currentPage - 1) * itemsPerPage + 1; const end = Math.min(currentPage * itemsPerPage, totalItems); const getPages = () => { const pages = []; if (totalPages <= 5) { for (let i = 1; i <= totalPages; i++) pages.push(i); } else { pages.push(1); if (currentPage > 3) pages.push("..."); for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) pages.push(i); if (currentPage < totalPages - 2) pages.push("..."); pages.push(totalPages); } return pages; }; return (
Showing {start}–{end} of{" "} {totalItems} courses
{getPages().map((page, i) => page === "..." ? ( ··· ) : ( ) )}
); }; // ─── Main Page ──────────────────────────────────────────────────────────────── const CoursesList = () => { const navigate = useNavigate(); const { courses, coursesLoading, getCourses } = useClientCourses(); const { fmtCurrency } = useDateFormat(); const { advertisements, loading: adLoading, getActiveAdvertisement, handleAdCtaClick } = useClientAdvertisements(); const [tierCategories, setTierCategories] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [search, setSearch] = useState(""); const [subFilter, setSubFilter] = useState("All"); const [categoryFilter, setCategoryFilter] = useState("All"); const [modalOpen, setModalOpen] = useState(false); const [selectedCourse, setSelectedCourse] = useState(null); const [allCategories, setAllCategories] = useState([]); useEffect(() => { getCourses(); api.get("/client/tiers/categories") .then(({ data }) => setTierCategories(data.data ?? [])) .catch(() => { }); api.get("/client/courses/categories") .then(({ data }) => setAllCategories(data.data ?? [])) .catch(() => { }); getActiveAdvertisement("course_list.banner"); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const bannerAd = advertisements["course_list.banner"] ?? null; // slug → category info map const tierMap = useMemo(() => { const m = {}; tierCategories.forEach((c) => { m[c.slug] = c; }); return m; }, [tierCategories]); const filtered = useMemo(() => courses .filter((c) => { const matchSearch = c.title.toLowerCase().includes(search.toLowerCase()) || (c.description ?? "").toLowerCase().includes(search.toLowerCase()); const matchSub = subFilter === "All" || c.subscription === subFilter; const matchCategory = categoryFilter === "All" || (c.categories ?? []).some((cat) => String(cat.id) === categoryFilter); return matchSearch && matchSub && matchCategory; }) .sort((a, b) => (a.order_index ?? 0) - (b.order_index ?? 0)), [courses, search, subFilter, categoryFilter] ); const totalPages = Math.max(1, Math.ceil(filtered.length / ITEMS_PER_PAGE)); const paginated = filtered.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE); const handleViewDetails = (course) => { if (course.is_locked) { setSelectedCourse(course); setModalOpen(true); } else { navigate(`/course/${course.course_id}`); } }; const items = [ { label: "Home", icon: , to: `/dashboard` }, { label: "Courses" }, ]; // Upsell modal tier panel const upsellTier = selectedCourse ? tierMap[selectedCourse.subscription] : null; return (
{/* Search & Filters */}
{ setSearch(e.target.value); setCurrentPage(1); }} />
{allCategories.length > 0 && ( )}
{/* Advertisement Banner */} {adLoading["course_list.banner"] ? ( ) : ( )} {categoryFilter !== "All" && (
Tags: {allCategories .filter((cat) => String(cat.id) === categoryFilter) .map((cat) => ( { setCategoryFilter("All"); setCurrentPage(1); }} > {cat.name} ))}
)} {/* Course Grid */} {coursesLoading ? (
{Array.from({ length: 8 }).map((_, i) => )}
) : paginated.length === 0 ? (

No courses found

) : (
{paginated.map((course) => ( ))}
)} {!coursesLoading && filtered.length > ITEMS_PER_PAGE && ( )}
{/* Upsell Modal */} {selectedCourse?.product?.is_active && ( )} } >
{upsellTier && !upsellTier.is_default && (() => { const { cls, panel } = resolveTierBadge(selectedCourse?.subscription ?? "", tierMap); return (
{upsellTier.name}
  • Access to {upsellTier.name} content
  • Certificates & achievements

Upgrade to a {upsellTier.name} plan to unlock this course.

); })()}
); }; export default CoursesList;