'use client'; import * as React from 'react'; import { useRouter } from 'next/navigation'; import { useSession } from 'next-auth/react'; import { usePlaylists } from '@/hooks/usePlaylists'; import { Card, CardContent, CardHeader, CardTitle, } from '@/components/ui/card'; import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from '@/components/ui/carousel'; import { Badge } from '@/components/ui/badge'; import { SegmentedProgressBar, WatchSegment } from '@/components/segmented-progress-bar'; import { AppSidebar } from '@/components/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { Lock } from 'lucide-react'; import Image from 'next/image'; import { toast } from 'sonner'; type VideoItem = { id: string; title: string; durationSec?: number; thumbnail?: string; locked?: boolean; instantAccess?: boolean; uploader?: { id: string; name?: string; image?: string; }; }; type VideoProgressData = { percent: number; watchedSec: number; segments: WatchSegment[]; }; export default function DashboardClient() { const router = useRouter(); const { data: session } = useSession(); const { subjects, isLoading } = usePlaylists(); const [videoProgress, setVideoProgress] = React.useState>({}); const [userUnlocks, setUserUnlocks] = React.useState>(new Set()); const [latestVideos, setLatestVideos] = React.useState([]); const [latestVideosLoading, setLatestVideosLoading] = React.useState(true); const playlistsToShow = React.useMemo(() => { const playlistMap = new Map< string, { playlist: any; courses: Map; } >(); subjects.forEach((subject: any) => { (subject.playlists || []).forEach((playlist: any) => { const entry = playlistMap.get(playlist.id); const courseInfo = { id: subject.id, title: subject.title, code: subject.code, }; if (entry) { entry.courses.set(courseInfo.id, courseInfo); } else { const coursesMap = new Map(); coursesMap.set(courseInfo.id, courseInfo); playlistMap.set(playlist.id, { playlist, courses: coursesMap }); } }); }); return Array.from(playlistMap.values()).map(({ playlist, courses }) => ({ ...playlist, coursesForDisplay: Array.from(courses.values()), })); }, [subjects]); // Fetch user's unlocks React.useEffect(() => { const fetchUserUnlocks = async () => { try { const res = await fetch('/api/user/unlocks'); if (res.ok) { const data = await res.json(); const unlockedVideoIds = new Set((data.unlocks?.map((u: any) => u.videoId) ?? []) as string[]); setUserUnlocks(unlockedVideoIds); } } catch (err) { console.error('Failed to fetch user unlocks:', err); } }; fetchUserUnlocks(); }, []); // Fetch latest videos from enrolled courses React.useEffect(() => { const fetchLatestVideos = async () => { try { setLatestVideosLoading(true); const res = await fetch('/api/videos/latest'); if (res.ok) { const data = await res.json(); setLatestVideos(data.videos ?? []); } } catch (err) { console.error('Failed to fetch latest videos:', err); } finally { setLatestVideosLoading(false); } }; fetchLatestVideos(); }, []); // Auto-sync enrollments for allowed students on first login React.useEffect(() => { const syncEnrollments = async () => { if (!session?.user) return; const userLevels = (session.user as any)?.levels; console.log('[DASHBOARD] User levels from session:', userLevels); if (!userLevels) { console.log('[DASHBOARD] No levels found in session'); return; } const levels = userLevels .split(',') .map((level: string) => level.trim()) .filter((level: string) => level.length > 0); console.log('[DASHBOARD] Parsed levels:', levels); if (levels.length === 0) { console.log('[DASHBOARD] No valid levels after parsing'); return; } try { console.log('[DASHBOARD] Calling sync-enrollments with:', { levels }); const res = await fetch('/api/enrollments/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ levels }), }); if (!res.ok) { console.error('[DASHBOARD] Failed to sync enrollments, status:', res.status); } else { const result = await res.json(); console.log('[DASHBOARD] Sync enrollments result:', result); } } catch (err) { console.error('[DASHBOARD] Error syncing enrollments:', err); } }; syncEnrollments(); }, [session]); // One request for all of this user's progress; videos never played are // simply absent and render as 0%. React.useEffect(() => { let cancelled = false; const fetchProgress = async () => { try { const res = await fetch('/api/progress/all'); if (!res.ok) return; const data = await res.json(); if (!cancelled) setVideoProgress(data.progress ?? {}); } catch (err) { console.error('Failed to fetch progress', err); } }; fetchProgress(); return () => { cancelled = true; }; }, []); const handleOpenVideo = (playlistId: string, videoId: string) => { // navigate to videoplayer page using query params (keeps your current structure) router.push(`/videoplayer?playlistId=${playlistId}&videoId=${videoId}`); }; const formatDuration = (s?: number) => { if (!s && s !== 0) return ''; const mins = Math.floor(s! / 60); const secs = Math.floor(s! % 60) .toString() .padStart(2, '0'); return `${mins}:${secs}`; }; return (
{isLoading && latestVideosLoading ? (
Loading…
) : (
{/* Latest Videos Section */} {!latestVideosLoading && latestVideos.length > 0 && (

Latest Videos

Recently uploaded from your enrolled courses

{latestVideos.map((v: any) => ( { if (v.accessible === false) { toast.error(v.lockReason ?? 'This video is locked.'); return; } if (v.playlist?.id) { handleOpenVideo(v.playlist.id, v.id); } }} >
{v.title} {v.accessible === false ? (
) : null}
{v.uploader?.image && (
{v.uploader.name
)}
{v.title} {formatDuration(v.durationSec)}
{v.uploader?.name && (

{v.uploader.name}

)} {v.playlist?.title && (

{v.playlist.title}

)}
))}
)} {/* Playlists Section */} {playlistsToShow.length === 0 && latestVideos.length === 0 ? (
No content available.
) : ( <> {playlistsToShow.length > 0 && ( <>

Playlists

Showing {playlistsToShow.length} playlist{playlistsToShow.length === 1 ? '' : 's'}

{playlistsToShow.map((pl: any) => (

{pl.title}

{pl.description}
{pl.coursesForDisplay?.length ? (
{pl.coursesForDisplay.map((course: any) => ( {course.code ?? course.title} ))}
) : null}
{pl.videos?.map((v: VideoItem) => ( { if ((v as any).locked) return; if ((v as any).instantAccess) { handleOpenVideo(pl.id, v.id); return; } if (v.id === pl.videos?.[0]?.id) { handleOpenVideo(pl.id, v.id); return; } if (userUnlocks.has(v.id)) { handleOpenVideo(pl.id, v.id); } }} >
{v.title} {(v as any).locked || (!(v as any).instantAccess && v.id !== pl.videos?.[0]?.id && !userUnlocks.has(v.id)) ? (
) : null}
{(v as any).uploader?.image && (
{(v
)}
{v.title} {formatDuration(v.durationSec)}
{(v as any).uploader?.name && (

{(v as any).uploader.name}

)}
))}
))} )} )}
)}
); }