diff --git a/frontend/src/pages/ManagedCourses.jsx b/frontend/src/pages/ManagedCourses.jsx
index 88a8d7f..874fa2a 100644
--- a/frontend/src/pages/ManagedCourses.jsx
+++ b/frontend/src/pages/ManagedCourses.jsx
@@ -3,6 +3,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import InstructorLayout from '../components/layout/InstructorLayout';
import useCourseStore from '../store/courseStore';
import useEnrollmentStore from '../store/enrollmentStore';
+import useToastStore from '../store/toastStore';
import { getDeptStyle } from '../utils/departmentStyles';
import Button from '../components/common/Button';
import {
@@ -339,7 +340,12 @@ const ManagedCourses = () => {
const handleDelete = async (courseId, courseTitle) => {
if (!window.confirm(`Are you sure you want to delete "${courseTitle}"? This action cannot be undone.`)) return;
setDeletingId(courseId);
- try { await deleteCourse(courseId); } catch (error) { alert('Failed to delete course: ' + (error.message || 'Unknown error')); }
+ try {
+ await deleteCourse(courseId);
+ useToastStore.getState().success('Course deleted', `"${courseTitle}" has been deleted.`);
+ } catch (error) {
+ useToastStore.getState().error('Delete failed', error.message || 'Failed to delete course. Please try again.');
+ }
finally { setDeletingId(null); }
};
diff --git a/frontend/src/pages/MyCourses.jsx b/frontend/src/pages/MyCourses.jsx
index 7ff010d..a856ce0 100644
--- a/frontend/src/pages/MyCourses.jsx
+++ b/frontend/src/pages/MyCourses.jsx
@@ -108,7 +108,7 @@ const MyCourses = () => {
diff --git a/frontend/src/pages/MyEnrollments.jsx b/frontend/src/pages/MyEnrollments.jsx
index 2c5a8f3..dc21185 100644
--- a/frontend/src/pages/MyEnrollments.jsx
+++ b/frontend/src/pages/MyEnrollments.jsx
@@ -1,8 +1,10 @@
import React, { useEffect, useState } from 'react';
import StudentLayout from '../components/layout/StudentLayout';
import useEnrollmentStore from '../store/enrollmentStore';
+import useToastStore from '../store/toastStore';
import api from '../utils/api';
import { Search } from 'lucide-react';
+import Button from '../components/common/Button';
const DEPARTMENTS = ['All', 'Computer Science', 'Mathematics', 'Science', 'Business'];
@@ -23,19 +25,23 @@ const MyEnrollments = () => {
const { myEnrollments, fetchMyEnrollments, requestEnrollment } = useEnrollmentStore();
const [availableCourses, setAvailableCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
- const [isRequesting, setIsRequesting] = useState(false);
+ const [loadError, setLoadError] = useState(false);
+ const [requestingId, setRequestingId] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [activeDept, setActiveDept] = useState('All');
useEffect(() => {
const loadData = async () => {
setIsLoading(true);
+ setLoadError(false);
await fetchMyEnrollments();
try {
const { data } = await api.get('/api/courses');
setAvailableCourses(data.courses || []);
} catch (error) {
console.error("Failed to fetch courses", error);
+ setLoadError(true);
+ useToastStore.getState().error('Failed to load courses', 'Please refresh the page to try again.');
}
setIsLoading(false);
};
@@ -43,14 +49,15 @@ const MyEnrollments = () => {
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleRequest = async (courseId) => {
- setIsRequesting(true);
+ setRequestingId(courseId);
try {
await requestEnrollment(courseId);
await fetchMyEnrollments();
+ useToastStore.getState().success('Request sent', 'Your enrollment request has been submitted.');
} catch (error) {
- alert(error.message || "Failed to request enrollment");
+ useToastStore.getState().error('Request failed', error.message || 'Failed to request enrollment');
} finally {
- setIsRequesting(false);
+ setRequestingId(null);
}
};
@@ -125,14 +132,21 @@ const MyEnrollments = () => {
+ {loadError && !isLoading && (
+
+ Couldn't load the full course list. Some courses may be missing β try refreshing the page.
+
+ )}
+
{isLoading ? (
-
+
) : (
{filteredCourses.map((course) => {
- const enrollment = myEnrollments.find(e => e.course._id === course._id || e.course === course._id);
+ const enrollment = myEnrollments.find(e => (e.course._id || e.course) === course._id);
const gradient = getDeptGradient(course.department);
return (
@@ -162,7 +176,7 @@ const MyEnrollments = () => {
{course.description}
-

+
{course.instructor?.name || 'Instructor'}
Course Creator
@@ -174,13 +188,16 @@ const MyEnrollments = () => {
{enrollment ? getStatusBadge(enrollment.status) :
Not enrolled}
{(!enrollment || enrollment.status === 'denied') && (
-
+
)}
diff --git a/frontend/src/pages/RoleSelectPage.jsx b/frontend/src/pages/RoleSelectPage.jsx
index bae49b8..1e04d35 100644
--- a/frontend/src/pages/RoleSelectPage.jsx
+++ b/frontend/src/pages/RoleSelectPage.jsx
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useAuthStore from '../store/authStore';
+import useToastStore from '../store/toastStore';
import { GraduationCap, Presentation, Check } from 'lucide-react';
const RoleSelectPage = () => {
@@ -31,7 +32,7 @@ const RoleSelectPage = () => {
const handleContinue = async () => {
if (selectedRoles.length === 0) {
- alert('Please select at least one role.');
+ useToastStore.getState().info('Select a role', 'Please select at least one role.');
return;
}
@@ -56,11 +57,11 @@ const RoleSelectPage = () => {
} else if (currentRole === 'instructor') {
navigate('/instructor', { replace: true });
} else {
- alert('Your account already has a role assigned. Logging out...');
+ useToastStore.getState().error('Role already set', 'Your account already has a role assigned. Logging out...');
navigate('/login', { replace: true });
}
} else {
- alert('Failed to set role. Please try again.');
+ useToastStore.getState().error('Something went wrong', 'Failed to set role. Please try again.');
}
setIsSubmitting(false);
}
diff --git a/frontend/src/pages/StudentStatusOverview.jsx b/frontend/src/pages/StudentStatusOverview.jsx
index 0b9e123..24db079 100644
--- a/frontend/src/pages/StudentStatusOverview.jsx
+++ b/frontend/src/pages/StudentStatusOverview.jsx
@@ -7,6 +7,7 @@ import {
CheckCircle, Search, Flame,
} from 'lucide-react';
import useCourseStore from '../store/courseStore';
+import useToastStore from '../store/toastStore';
import api from '../utils/api';
// ββ helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -235,7 +236,7 @@ const TopPerformers = ({ leaderboard, loading }) => {
'bg-orange-50/40 border-orange-200/40 dark:bg-orange-500/5 dark:border-orange-500/15'
}`}>
{RANK_MEDAL[i]}
-
+
{s.name || 'Student'}
Lv.{s.level || levelFromXP(s.xp)} Β· {(s.xp || 0).toLocaleString()} XP
@@ -253,9 +254,9 @@ const TopPerformers = ({ leaderboard, loading }) => {
// ββ avatar helper ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-const AvatarDisplay = ({ avatar, size = 'w-10 h-10' }) => {
+const AvatarDisplay = ({ avatar, size = 'w-10 h-10', name }) => {
if (avatar && (avatar.startsWith('http') || avatar.startsWith('/'))) {
- return

;
+ return

;
}
return (
@@ -336,6 +337,7 @@ const StudentStatusOverview = () => {
setAnalytics(res.data);
} catch (e) {
console.error('Failed to fetch analytics', e);
+ useToastStore.getState().error('Failed to load analytics', 'Please try selecting the course again.');
} finally {
setIsLoading(false);
}
@@ -349,6 +351,7 @@ const StudentStatusOverview = () => {
setStudents(res.data?.students || []);
} catch (e) {
console.error('Failed to fetch students', e);
+ useToastStore.getState().error('Failed to load students', 'Please try selecting the course again.');
} finally {
setStudentsLoading(false);
}
@@ -369,6 +372,7 @@ const StudentStatusOverview = () => {
setLeaderboard(res.data || []);
} catch (e) {
console.error('Failed to fetch leaderboard', e);
+ useToastStore.getState().error('Failed to load leaderboard', 'Please try again in a moment.');
} finally {
setLeaderboardLoading(false);
}
@@ -735,7 +739,7 @@ const StudentStatusOverview = () => {
const iss = issueConfig(item.issue);
return (
-
+
@@ -871,7 +875,7 @@ const StudentStatusOverview = () => {
{/* Name + avatar */}
-
+
{s.student?.name || 'Unknown'}
{s.student?.email || 'β'}
@@ -1050,7 +1054,7 @@ const StudentStatusOverview = () => {
}
{/* Avatar */}
-
+
{/* Name + XP bar */}
diff --git a/frontend/src/pages/admin/InstructorsTab.jsx b/frontend/src/pages/admin/InstructorsTab.jsx
index d9db154..a047458 100644
--- a/frontend/src/pages/admin/InstructorsTab.jsx
+++ b/frontend/src/pages/admin/InstructorsTab.jsx
@@ -107,7 +107,7 @@ const InstructorsTab = () => {
{inst.avatar ? (
- 
+ 
) : (
{inst.name?.[0]?.toUpperCase() ?? '?'}
| |