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 */}
{/* 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 ? (
) : (
{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;