'use client' import * as React from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { ArrowLeft, RotateCcw, Share2, Settings, BrainCircuit, Loader2, Star, Tags, FolderPlus, PencilLine, Trash2, Bomb, CheckCircle2, AlertCircle, X, } from 'lucide-react' import { Button } from '@/components/ui/button' import { Progress } from '@/components/ui/progress' import { Badge } from '@/components/ui/badge' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' type CourseActionsProps = { course: { id: string slug: string name: string path: string favorited?: boolean } modules: any[] } export function CourseActions({ course, modules }: CourseActionsProps) { const router = useRouter() const [menuOpen, setMenuOpen] = React.useState(false) const [loading, setLoading] = React.useState(false) const [status, setStatus] = React.useState<{ kind: 'success' | 'error'; text: string } | null>(null) const [quickAction, setQuickAction] = React.useState<'tag' | 'category' | null>(null) const [quickActionValue, setQuickActionValue] = React.useState('') const [quickActionError, setQuickActionError] = React.useState(null) const [quizStatus, setQuizStatus] = React.useState<'idle' | 'running' | 'success' | 'error'>('idle') const [quizMessage, setQuizMessage] = React.useState(null) const [quizResults, setQuizResults] = React.useState<{ ok: number; skipped: number } | null>(null) const refreshLibrary = async () => { router.refresh() } const patchCourse = async (payload: Record) => { const response = await fetch(`/api/courses/${course.slug}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!response.ok) { const data = await response.json().catch(() => ({})) throw new Error(data?.error || 'Failed to update course') } await refreshLibrary() } const handleToggleFavorite = async () => { try { await patchCourse({ favorited: !course.favorited }) } catch (error) { console.error('Toggle favorite failed:', error) setStatus({ kind: 'error', text: 'Sorry, the pin did not stick. The favorite goblin dropped it.' }) } } const startQuickAction = (mode: 'tag' | 'category') => { setQuickAction(mode) setQuickActionValue('') setQuickActionError(null) } const submitQuickAction = async () => { if (!quickAction) return const trimmed = quickActionValue.trim().replace(/\s+/g, ' ') if (!trimmed) { setQuickActionError(quickAction === 'tag' ? 'Type a tag first.' : 'Type a category first.') return } try { await patchCourse({ tagId: quickAction === 'category' ? `category:${trimmed}` : trimmed }) setQuickAction(null) setQuickActionValue('') setQuickActionError(null) } catch (error) { console.error(`${quickAction} add failed:`, error) setQuickActionError(quickAction === 'tag' ? 'Tag did not stick. Try again.' : 'Category did not stick. Try again.') } } const [clearQuizModule, setClearQuizModule] = React.useState('') const [clearQuizMode, setClearQuizMode] = React.useState(false) const clearQuiz = async () => { if (!clearQuizModule) { setStatus({ kind: 'error', text: 'Pick a module to clear first.' }) return } const confirmed = window.confirm(`Remove quiz cache and quiz lesson for module "${clearQuizModule}"? This cannot be undone.`) if (!confirmed) return setQuizStatus('running') setQuizMessage('Clearing quiz...') setClearQuizMode(false) setClearQuizModule('') try { const response = await fetch(`/api/courses/${course.slug}/quiz`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'clear', module: clearQuizModule }), }) const data = await response.json() if (!response.ok) { throw new Error(data?.error || 'Failed to clear quiz') } await refreshLibrary() router.refresh() setQuizStatus('success') setQuizMessage(`✅ Cleared quiz for ${clearQuizModule}`) setQuizResults(null) } catch (error) { console.error('Clear quiz failed:', error) setQuizStatus('error') setQuizMessage(error instanceof Error ? error.message : 'Failed to clear quiz.') } } const handleRegenerateQuiz = async () => { setQuizStatus('running') setQuizMessage('Regenerating quizzes with QuizAPI...') try { const response = await fetch(`/api/courses/${course.slug}/quiz`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'regenerate', source: 'quizapi', difficulty: 'medium' }), }) const data = await response.json() if (!response.ok) { throw new Error(data?.error || 'Failed to regenerate quizzes') } const okCount = data.results?.filter((item: { status: string }) => item.status === 'ok').length ?? 0 const skippedCount = data.results?.filter((item: { status: string }) => item.status === 'skipped').length ?? 0 setQuizResults({ ok: okCount, skipped: skippedCount }) await refreshLibrary() router.refresh() setQuizStatus('success') setQuizMessage(`✅ Regenerated ${okCount} module quiz${okCount !== 1 ? 's' : ''}${skippedCount ? ` (${skippedCount} skipped)` : ''}`) } catch (error) { console.error('Regenerate quiz failed:', error) setQuizStatus('error') setQuizMessage(error instanceof Error ? error.message : 'Quiz regeneration failed.') } } const handleRename = async () => { const nextName = window.prompt('Rename course', course.name) if (nextName === null) return const trimmed = nextName.trim() if (!trimmed || trimmed === course.name) return try { const response = await fetch(`/api/courses/${course.slug}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ displayName: trimmed }), }) if (!response.ok) { throw new Error('Failed to rename course') } await refreshLibrary() } catch (error) { console.error('Rename course failed:', error) setStatus({ kind: 'error', text: 'Sorry, the rename did not stick. The little gremlin in the API tripped.' }) } } const handleDelete = async (scope: 'library' | 'disk') => { const isDiskDelete = scope === 'disk' const confirmationMessage = isDiskDelete ? `Delete "${course.name}" from disk?\n\nThis permanently removes the course folder and all library records. This cannot be undone.` : `Delete "${course.name}" from the library?\n\nThis hides the course from the app but keeps its files on disk.` const confirmed = window.confirm(confirmationMessage) if (!confirmed) return try { const response = await fetch(`/api/courses/${course.slug}?scope=${scope}`, { method: 'DELETE', }) if (!response.ok) { throw new Error(`Failed to delete course (${scope})`) } await refreshLibrary() // Navigate back to library after deletion router.push('/') } catch (error) { console.error('Delete course failed:', error) setStatus({ kind: 'error', text: isDiskDelete ? 'Sorry, the disk delete did not stick. The server bumped into the filesystem goblin.' : 'Sorry, the library delete did not stick. The server sneezed on the request.' }) } } return (
Back to Library
Support on Ko-fi {course.favorited ? 'Unpin favorite' : 'Pin favorite'} { event.preventDefault(); startQuickAction('tag') }} className="gap-2"> Add tag { event.preventDefault(); startQuickAction('category') }} className="gap-2"> Add category {quickAction && (
event.stopPropagation()}> setQuickActionValue(event.target.value)} onKeyDown={(event) => { event.stopPropagation() if (event.key === 'Enter') submitQuickAction() if (event.key === 'Escape') setQuickAction(null) }} placeholder={quickAction === 'tag' ? 'e.g. linux' : 'e.g. Cloud'} className="mb-2 h-8 w-full rounded-md border border-input bg-background px-2 text-sm outline-none focus:ring-2 focus:ring-primary/40" autoFocus /> {quickActionError &&

{quickActionError}

}
)} { event.preventDefault(); setClearQuizMode(value => !value) }} disabled={quizStatus === 'running'} className="gap-2"> {quizStatus === 'running' ? : } {clearQuizMode ? 'Cancel' : 'Clear Quiz'} {clearQuizMode && (
event.stopPropagation()}>
)} {quizStatus === 'running' ? : } {quizStatus === 'running' ? 'Regenerating...' : 'Regenerate Quiz'} Rename handleDelete('library')} className="gap-2 text-destructive focus:text-destructive"> Delete from library handleDelete('disk')} className="gap-2 text-destructive focus:text-destructive"> Delete from disk
{status ? {status.text} : null}
{(quizStatus === 'success' || quizStatus === 'error') && quizMessage && (
{quizMessage} {quizResults && ( {quizResults.ok} generated {quizResults.skipped > 0 && ·} {quizResults.skipped > 0 && {quizResults.skipped} skipped} )}
)} {loading || quizStatus === 'running' ? : null}
) }