feat: AI quiz generation per lesson + hide edit buttons for non-authors
- Generate unique quiz for each lesson using OpenRouter API - Parse lesson content (TipTap JSON) and send to AI for question generation - Cache quiz in database to avoid regeneration - Hide Edit/Delete buttons if current user is not course author - Add backendUser to auth context for proper authorization checks - Show certificate button prominently when course is completed Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -45,13 +45,14 @@ type CourseData = {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
status: string;
|
||||
authorId: string;
|
||||
chapters: Chapter[];
|
||||
};
|
||||
|
||||
export default function CoursePage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const { loading: authLoading } = useAuth();
|
||||
const { loading: authLoading, backendUser } = useAuth();
|
||||
const id = params?.id as string;
|
||||
const [course, setCourse] = useState<CourseData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -248,6 +249,9 @@ export default function CoursePage() {
|
||||
const activeLessonMeta = selectedLessonId
|
||||
? flatLessons.find((l) => l.id === selectedLessonId)
|
||||
: null;
|
||||
|
||||
const isAuthor = course && backendUser && course.authorId === backendUser.id;
|
||||
const courseCompleted = completedCount >= totalLessons && totalLessons > 0;
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-4rem)] -m-6 flex-col">
|
||||
@ -272,37 +276,51 @@ export default function CoursePage() {
|
||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
||||
<span className="text-xs font-medium">{progressPercent}% пройдено</span>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" asChild>
|
||||
<Link href={`/dashboard/courses/${course.id}/edit`}>
|
||||
<Edit className="mr-1.5 h-3.5 w-3.5" />
|
||||
Редактировать
|
||||
</Link>
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
|
||||
{/* Certificate button - show when course completed */}
|
||||
{courseCompleted && (
|
||||
<Button size="sm" variant="default" onClick={handleGetCertificate} disabled={generatingCertificate}>
|
||||
<GraduationCap className="mr-1.5 h-3.5 w-3.5" />
|
||||
{generatingCertificate ? 'Генерация...' : 'Получить сертификат'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Edit/Delete - only for author */}
|
||||
{isAuthor && (
|
||||
<>
|
||||
<Button size="sm" variant="outline" asChild>
|
||||
<Link href={`/dashboard/courses/${course.id}/edit`}>
|
||||
<Edit className="mr-1.5 h-3.5 w-3.5" />
|
||||
Редактировать
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Удалить курс?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Курс «{course.title}» будет удалён безвозвратно.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Отмена</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => { e.preventDefault(); handleDelete(); }}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
disabled={deleting}
|
||||
>
|
||||
{deleting ? 'Удаление...' : 'Удалить'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Удалить курс?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Курс «{course.title}» будет удалён безвозвратно.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Отмена</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => { e.preventDefault(); handleDelete(); }}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
disabled={deleting}
|
||||
>
|
||||
{deleting ? 'Удаление...' : 'Удалить'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -520,16 +538,10 @@ export default function CoursePage() {
|
||||
Следующий урок
|
||||
<ChevronRight className="ml-1.5 h-4 w-4" />
|
||||
</Button>
|
||||
) : completedCount >= totalLessons ? (
|
||||
<Button size="sm" onClick={handleGetCertificate} disabled={generatingCertificate}>
|
||||
<GraduationCap className="mr-1.5 h-4 w-4" />
|
||||
{generatingCertificate ? 'Генерация...' : 'Получить сертификат'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" variant="outline" disabled>
|
||||
<GraduationCap className="mr-1.5 h-4 w-4" />
|
||||
Завершить курс
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{courseCompleted ? 'Курс пройден!' : 'Последний урок'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user