feat: add certificates, groups, support system, and moderation
Backend changes: - Add Certificate generation service with beautiful HTML templates - Add CourseGroup, GroupMember, GroupMessage models for group collaboration - Add Homework and HomeworkSubmission models with AI + teacher grading - Add SupportTicket and TicketMessage models for help desk - Add Moderation API for admin/moderator course approval workflow - All new modules: CertificatesModule, GroupsModule, SupportModule, ModerationModule Frontend changes: - Add certificate download button when course completed - Update course page to load enrollment progress from backend - Integrate lesson completion with backend API Database schema now supports: - Course groups with chat functionality - Homework assignments with dual AI/human grading - Support ticket system with admin responses - Full moderation workflow (PENDING_REVIEW -> PUBLISHED/REJECTED) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -66,6 +66,7 @@ export default function CoursePage() {
|
||||
const [enrollmentProgress, setEnrollmentProgress] = useState<any>(null);
|
||||
const [showQuiz, setShowQuiz] = useState(false);
|
||||
const [quizQuestions, setQuizQuestions] = useState<any[]>([]);
|
||||
const [generatingCertificate, setGeneratingCertificate] = useState(false);
|
||||
|
||||
// Flat list of all lessons
|
||||
const flatLessons = useMemo(() => {
|
||||
@ -188,6 +189,19 @@ export default function CoursePage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleGetCertificate = async () => {
|
||||
if (!id || generatingCertificate) return;
|
||||
setGeneratingCertificate(true);
|
||||
try {
|
||||
const { certificateUrl } = await api.getCertificate(id);
|
||||
window.open(certificateUrl, '_blank');
|
||||
} catch {
|
||||
// silent
|
||||
} finally {
|
||||
setGeneratingCertificate(false);
|
||||
}
|
||||
};
|
||||
|
||||
const goToNextLesson = () => {
|
||||
if (currentLessonIndex < flatLessons.length - 1) {
|
||||
markComplete();
|
||||
@ -506,8 +520,13 @@ 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={completedCount < totalLessons}>
|
||||
<Button size="sm" variant="outline" disabled>
|
||||
<GraduationCap className="mr-1.5 h-4 w-4" />
|
||||
Завершить курс
|
||||
</Button>
|
||||
|
||||
@ -294,6 +294,11 @@ class ApiClient {
|
||||
return this.request<{ data: any[]; meta: any }>(`/enrollment/${courseId}/reviews${params}`);
|
||||
}
|
||||
|
||||
// Certificates
|
||||
async getCertificate(courseId: string) {
|
||||
return this.request<{ certificateUrl: string; html: string }>(`/certificates/${courseId}`);
|
||||
}
|
||||
|
||||
// Search
|
||||
async searchCourses(query: string, filters?: { category?: string; difficulty?: string }) {
|
||||
const searchParams = new URLSearchParams({ q: query });
|
||||
|
||||
Reference in New Issue
Block a user