'use client' import * as React from 'react' import { useState } from 'react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Loader2, CheckCircle, AlertCircle, RefreshCw, Database, FolderOpen, Play } from 'lucide-react' import Link from 'next/link' import { Header } from '@/components/Header' export default function ScanPage() { const [scanning, setScanning] = useState(false) const [coursesRoot, setCoursesRoot] = useState('./My_Courses') const [result, setResult] = useState<{ coursesCreated: number coursesUpdated: number coursesDeleted: number modulesCreated: number modulesUpdated: number lessonsCreated: number lessonsUpdated: number errors: string[] duration: number } | null>(null) const [logs, setLogs] = useState([]) const addLog = (message: string) => { setLogs(prev => [...prev, `[${new Date().toLocaleTimeString()}] ${message}`]) } React.useEffect(() => { fetch('/api/settings') .then(res => res.json()) .then(data => { if (data.coursesRoot) setCoursesRoot(data.coursesRoot) }) .catch(() => {}) }, []) const handleScan = async () => { setScanning(true) setResult(null) setLogs([]) addLog('Starting scan...') try { const response = await fetch('/api/scan', { method: 'POST' }) const data = await response.json() if (!response.ok) { throw new Error(data.error || 'Scan failed') } setResult(data) addLog(`Scan completed in ${data.duration}ms`) addLog( `Courses: ${data.coursesCreated} created, ${data.coursesUpdated} updated, ${data.coursesDeleted} deleted` ) addLog(`Modules: ${data.modulesCreated} created, ${data.modulesUpdated} updated`) addLog(`Lessons: ${data.lessonsCreated} created, ${data.lessonsUpdated} updated`) if (data.errors.length > 0) { data.errors.forEach((err: string) => addLog(`ERROR: ${err}`)) } } catch (error) { addLog(`ERROR: ${error instanceof Error ? error.message : 'Unknown error'}`) } finally { setScanning(false) } } return (

Scan Courses

Scans your My_Courses/ directory for new content. Courses are detected from the folder tree below.

Start Scan Recursively scans for courses, modules, and lessons. Updates existing records, adds new ones, and keeps renamed display titles.

Quick scan skips video metadata for speed. For full scan with metadata, use the Scan page.

{result && ( {result.errors.length > 0 ? ( ) : ( )} Scan Results

{result.coursesCreated + result.coursesUpdated}

Courses

{result.modulesCreated + result.modulesUpdated}

Modules

{result.lessonsCreated + result.lessonsUpdated}

Lessons

Completed in {result.duration}ms
)} {logs.length > 0 && ( Scan Logs
{logs.map((log, i) => (
{log}
))}
)} Expected Folder Structure
{coursesRoot || './My_Courses'}/
├── Course Name 1/
│   ├── 01 - Introduction/
│   │   ├── 01 - Introduction.mp4
│   │   ├── 01 - Introduction.en.vtt
│   │   ├── 01 - Introduction.es.vtt
│   │   ├── 02 - Overview.pdf
│   │   └── 03 - Notes.txt
│   ├── 02 - Advanced/
│   │   ├── 01 - Deep Dive.mp4
│   │   └── 01 - Deep Dive.vtt
│   └── cover.jpg   ← optional thumbnail
├── Course Name 2/
│   ├── Module A/
│   │   ├── lesson1.mp4
│   │   ├── lesson1.srt
│   │   └── notes.md
│   └── Module B/
│       └── lesson1.json
└── Course Name 3/
    └── Module 1/
        ├── video1.mp4
        ├── diagram.pdf
        └── README.txt
            

How the scanner reads it: course folder → module folder → lesson files.

Matching rule: subtitle files can either share the exact video base name, like lesson.mp4 + lesson.vtt, or use language suffixes for multiple tracks, like lesson.en.vtt + lesson.es.vtt.

Subtitle formats: .vtt is preferred for browser playback; .srt files are detected and listed as best-effort tracks.

Sorting: numeric prefixes like 01, 02, 10 keep lessons in learning order.

) }