feat: persist learner progress and comments
All checks were successful
CI / Validate frontend and API (push) Successful in 47s
CI / Build and publish Docker images (push) Successful in 39s

This commit is contained in:
Cauê Faleiros
2026-08-31 16:33:08 -03:00
parent 7842a757cd
commit 2dfca33758
10 changed files with 225 additions and 491 deletions

View File

@@ -19,7 +19,7 @@ import {
} from 'lucide-react';
import { Course, Comment, Lesson, Attachment } from '../types';
import { useAuth } from '../context/AuthContext';
import { getComments, saveComment, saveProgress, incrementViews } from '../services/db';
import { getComments, getCompletedLessonIds, saveComment, saveLessonCompletion, incrementViews } from '../services/db';
import { LoginModal } from './LoginModal';
import { MaterialCard } from './MaterialCard';
@@ -32,7 +32,6 @@ export const CoursePlayerModal: React.FC<CoursePlayerModalProps> = ({ course, on
const { user } = useAuth();
const [comments, setComments] = useState<Comment[]>([]);
const [newComment, setNewComment] = useState('');
const [currentProgress, setCurrentProgress] = useState(0);
const [currentLesson, setCurrentLesson] = useState<Lesson | null>(null);
const [completedLessonIds, setCompletedLessonIds] = useState<string[]>([]);
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
@@ -41,28 +40,18 @@ export const CoursePlayerModal: React.FC<CoursePlayerModalProps> = ({ course, on
const videoRef = useRef<HTMLVideoElement>(null);
// Load course data & completed lessons from storage
// Load course data and this learner's saved state from PostgreSQL.
useEffect(() => {
if (course) {
incrementViews(course.id);
getComments(course.id).then(setComments);
setCurrentProgress(course.progress || 0);
// Load completed lessons from localStorage
const storageKey = `course_${course.id}_completed_lessons`;
const savedCompleted = localStorage.getItem(storageKey);
if (savedCompleted) {
try {
setCompletedLessonIds(JSON.parse(savedCompleted));
} catch (e) {
setCompletedLessonIds([]);
}
} else if (course.progress && course.progress > 0 && course.lessons && course.lessons.length > 0) {
// Initial demo completion based on progress
const count = Math.ceil((course.progress / 100) * course.lessons.length);
const initialCompleted = course.lessons.slice(0, count).map(l => l.id);
setCompletedLessonIds(initialCompleted);
localStorage.setItem(storageKey, JSON.stringify(initialCompleted));
setCompletedLessonIds([]);
if (user) {
getComments(course.id).then(setComments).catch(() => setComments([]));
getCompletedLessonIds(course.id).then((completed) => {
setCompletedLessonIds(completed);
}).catch(() => setCompletedLessonIds([]));
} else {
setComments([]);
}
// Select first lesson by default
@@ -70,28 +59,28 @@ export const CoursePlayerModal: React.FC<CoursePlayerModalProps> = ({ course, on
setCurrentLesson(course.lessons[0]);
}
}
}, [course]);
}, [course, user]);
// Persist completed lessons and recalculate progress
const updateCompletedLessons = (newCompleted: string[]) => {
setCompletedLessonIds(newCompleted);
if (course) {
localStorage.setItem(`course_${course.id}_completed_lessons`, JSON.stringify(newCompleted));
if (course.lessons && course.lessons.length > 0) {
const percent = Math.round((newCompleted.length / course.lessons.length) * 100);
setCurrentProgress(percent);
saveProgress(course.id, percent);
}
}
};
const toggleLessonCompleted = (lessonId: string) => {
if (!user) {
setShowLoginPrompt(true);
return;
}
if (completedLessonIds.includes(lessonId)) {
const updated = completedLessonIds.filter(id => id !== lessonId);
updateCompletedLessons(updated);
saveLessonCompletion(lessonId, false).catch(() => updateCompletedLessons(completedLessonIds));
} else {
const updated = [...completedLessonIds, lessonId];
updateCompletedLessons(updated);
saveLessonCompletion(lessonId, true).catch(() => updateCompletedLessons(completedLessonIds));
}
};
@@ -135,18 +124,19 @@ export const CoursePlayerModal: React.FC<CoursePlayerModalProps> = ({ course, on
e.preventDefault();
if (!newComment.trim() || !course) return;
const comment: Comment = {
id: Date.now().toString(),
const comment = {
courseId: course.id,
lessonId: currentLesson?.id,
user: user?.name || 'Aluno Convidado',
text: newComment,
date: new Date().toLocaleDateString('pt-BR')
};
await saveComment(comment);
setComments([comment, ...comments]);
setNewComment('');
try {
const savedComment = await saveComment(comment);
setComments([savedComment, ...comments]);
setNewComment('');
} catch {
// The server remains the source of truth; leave the text intact for a retry.
}
};
const changeLesson = (lesson: Lesson) => {