From bed7e440c10b07c180c6a3777c9061fdfdd0ac97 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Feb 2026 10:55:24 +0000 Subject: [PATCH] fix: useState hook error in catalog + add certificate download in learning page Co-authored-by: Cursor --- .../dashboard/catalog/[id]/page.tsx | 12 ++- .../(dashboard)/dashboard/learning/page.tsx | 21 ++++- .../src/components/dashboard/course-chat.tsx | 88 +++++++++++++++++++ 3 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/components/dashboard/course-chat.tsx diff --git a/apps/web/src/app/(dashboard)/dashboard/catalog/[id]/page.tsx b/apps/web/src/app/(dashboard)/dashboard/catalog/[id]/page.tsx index d27197a..432e97c 100644 --- a/apps/web/src/app/(dashboard)/dashboard/catalog/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/dashboard/catalog/[id]/page.tsx @@ -34,6 +34,7 @@ export default function PublicCoursePage() { const [loading, setLoading] = useState(true); const [enrolling, setEnrolling] = useState(false); const [enrolled, setEnrolled] = useState(false); + const [expandedChapters, setExpandedChapters] = useState([]); useEffect(() => { if (!id) return; @@ -221,12 +222,19 @@ export default function PublicCoursePage() {

Содержание курса

{course.chapters.map((chapter: any) => { - const [expanded, setExpanded] = useState(false); + const expanded = expandedChapters.includes(chapter.id); + const toggleExpanded = () => { + setExpandedChapters(prev => + prev.includes(chapter.id) + ? prev.filter(id => id !== chapter.id) + : [...prev, chapter.id] + ); + }; return (
+ {enrollment.completedAt && ( + + )} diff --git a/apps/web/src/components/dashboard/course-chat.tsx b/apps/web/src/components/dashboard/course-chat.tsx new file mode 100644 index 0000000..d30e007 --- /dev/null +++ b/apps/web/src/components/dashboard/course-chat.tsx @@ -0,0 +1,88 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { Send, MessageCircle } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +interface Message { + id: string; + content: string; + createdAt: string; + user: { id: string; name: string | null; avatarUrl: string | null }; +} + +interface CourseChatProps { + groupId: string; + userId: string; + onSendMessage: (content: string) => Promise; + messages: Message[]; +} + +export function CourseChat({ groupId, userId, onSendMessage, messages }: CourseChatProps) { + const [newMessage, setNewMessage] = useState(''); + const [sending, setSending] = useState(false); + const messagesEndRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + const handleSend = async () => { + if (!newMessage.trim() || sending) return; + setSending(true); + try { + await onSendMessage(newMessage); + setNewMessage(''); + } finally { + setSending(false); + } + }; + + return ( + + + + + Чат курса + + + +
+ {messages.map((msg) => { + const isOwn = msg.user.id === userId; + return ( +
+
+ {msg.user.name?.[0] || 'U'} +
+
+ {msg.user.name || 'Аноним'} +
+ {msg.content} +
+
+
+ ); + })} +
+
+
+ setNewMessage(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && !e.shiftKey && handleSend()} + placeholder="Написать сообщение..." + className="flex-1 px-3 py-2 text-sm border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary" + disabled={sending} + /> + +
+ + + ); +}