import { useState, useEffect } from "react";
import { Badge } from "@/components/ui/badge";
import { Layers, CheckCheck, RefreshCw, SendHorizonal, Lock, Zap, Info, Tag } from "lucide-react";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { Progress } from "@/components/ui/progress";
import ResponsiveModal from "@/components/generic/ResponsiveModal";
import { Button } from "@/components/ui/button";
import { useNavigate } from "react-router-dom";
import api from "@/utils/api.util";
import { useClientTiers } from "@/contexts/ClientTiersProvider";
import { resolveTierBadge } from "@/utils/tierBadge.util";
function TierBadge({ tier }) {
const { tierMap } = useClientTiers();
const { rank, label, cls } = resolveTierBadge(tier ?? 'free', tierMap);
return (
{rank > 0 ? : }
{label}
);
}
const ReadUnit = ({ title = "Read Units", units = [], groupId, taskListId, taskId }) => {
const navigate = useNavigate();
const [selected, setSelected] = useState(null);
const [details, setDetails] = useState({});
const [locked, setLocked] = useState({});
const [lockedInfo, setLockedInfo] = useState({});
const [unavailable, setUnavailable] = useState({});
const [fetching, setFetching] = useState({});
useEffect(() => {
units.forEach(async (unit) => {
if (!unit.reference_id) return;
setFetching((prev) => ({ ...prev, [unit.reference_id]: true }));
try {
const res = await api.get(`/client/courses/unit/uuid/${unit.reference_id}`);
const d = res.data?.data;
if (d) setDetails((prev) => ({ ...prev, [unit.reference_id]: d }));
} catch (err) {
if (err?.response?.status === 403) {
setLocked((prev) => ({ ...prev, [unit.reference_id]: true }));
const course = err.response?.data?.course;
if (course) setLockedInfo((prev) => ({ ...prev, [unit.reference_id]: course }));
} else {
setUnavailable((prev) => ({ ...prev, [unit.reference_id]: true }));
}
} finally {
setFetching((prev) => ({ ...prev, [unit.reference_id]: false }));
}
});
}, []);
const getProgress = (unit) => unit.completed ? 100 : (unit.progress ?? 0);
if (!units.length) {
return (
{title}
0
No units available.
);
}
const hasLocked = Object.values(locked).some(Boolean);
return (
<>
{title}
{units.length}
{hasLocked && (
Subscription Required
To complete this activity, subscribe to one of our available tier plans.
navigate('/subscriptions')}>
View Plans
)}
{units.map((unit) => {
const info = details[unit.reference_id];
const courseInfo = lockedInfo[unit.reference_id];
const progress = getProgress(unit);
const isLocked = locked[unit.reference_id];
const isUnavailable = unavailable[unit.reference_id];
const isFetching = fetching[unit.reference_id];
// ── Locked card ───────────────────────────────────
// Skipped when already completed — a completion recorded
// while the unit was accessible still counts even if it
// re-locks later (reading-progress rows are never revoked
// on a tier change), so it must keep showing as Completed.
if (isLocked && !unit.completed) {
return (
navigate('/subscriptions')}
className="bg-card rounded-2xl border border-dashed p-4 flex flex-col gap-3 hover:bg-muted/60 hover:shadow-sm transition-all cursor-pointer group w-96 shrink-0 opacity-80"
>
{courseInfo?.title && (
from {courseInfo.title}
)}
{unit.title}
Subscribe to {courseInfo?.title ?? 'this course'} to unlock this unit.
{ e.stopPropagation(); navigate('/subscriptions'); }}>
Upgrade to unlock
);
}
// ── Unavailable card ──────────────────────────────
if (isUnavailable) {
return (
Unit
Unavailable
{unit.title}
This unit is no longer available.
);
}
// ── Normal card ───────────────────────────────────
return (
!isFetching && setSelected(unit)}
className={`bg-card rounded-2xl border p-4 flex flex-col gap-3 transition-all w-96 shrink-0 ${
isFetching
? 'opacity-60 cursor-wait'
: 'hover:bg-muted/60 hover:shadow-sm cursor-pointer group'
}`}
>
{/* Card type row */}
Unit
{info?.course?.subscription
?
: isFetching && Loading…
}
{info?.course?.title && (
from {info.course.title}
)}
{
if (!info) return;
e.stopPropagation();
if (info.course?.course_id) {
navigate(`/course/${info.course.course_id}/unit`, {
state: taskId ? { taskCtx: { has_task: true, groupId, taskListId, taskId } } : {},
});
return;
}
// Standalone unit (junction revamp — no parent course) —
// the course-scoped route can't resolve, fall back to
// the standalone unit reader instead.
navigate(`/units/${unit.reference_id}/read`, {
state: taskId ? { taskCtx: { has_task: true, groupId, taskListId, taskId } } : {},
});
}}
className="text-base font-semibold leading-snug line-clamp-2 transition-colors text-blue-600 dark:text-blue-400 hover:underline cursor-pointer"
>
{unit.title}
{info?.description ?? ''}
{progress >= 100 ? (
Completed
) : progress > 0 ? (
In Progress
) : (
Not Started
)}
{progress > 0 && {progress}% }
= 100 ? "[&>div]:bg-green-500" : ""}`}
/>
);
})}
!v && setSelected(null)}
title={selected?.title}
description="Unit Info"
footer={
<>
setSelected(null)}>Cancel
{
const info = details[selected?.reference_id];
if (!info) return;
if (info.course?.course_id) {
navigate(`/course/${info.course.course_id}/unit`, {
state: taskId ? { taskCtx: { has_task: true, groupId, taskListId, taskId } } : {},
});
return;
}
navigate(`/units/${selected.reference_id}/read`, {
state: taskId ? { taskCtx: { has_task: true, groupId, taskListId, taskId } } : {},
});
}}
disabled={
getProgress(selected ?? {}) >= 100 ||
locked[selected?.reference_id] ||
!details[selected?.reference_id]
}
>
Proceed
>
}
>
{selected && (() => {
const info = details[selected.reference_id];
const progress = getProgress(selected);
const done = progress >= 100;
return (
{done && (
Automatically Turned-in
)}
{info?.course?.title && (
Course:
{info.course.title}
)}
About this unit
{info?.description ?? '—'}
);
})()}
>
);
};
export default ReadUnit;