@defikitdotnet/education-module-ai
Version:
AI Education Module using Agent Framework
552 lines (551 loc) • 44.4 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var jsx_runtime_1 = require("react/jsx-runtime");
var react_1 = require("react");
var router_1 = require("next/router");
var link_1 = __importDefault(require("next/link"));
var axios_1 = __importDefault(require("axios"));
var sonner_1 = require("sonner"); // Using sonner for toasts
// Shadcn UI Components for Auth and Layout
var button_1 = require("../../components/ui/button");
var card_1 = require("../../components/ui/card");
var input_1 = require("../../components/ui/input");
var label_1 = require("../../components/ui/label");
var tabs_1 = require("../../components/ui/tabs");
// Define level name mapping for UI display
var levelNames = {
beginner: "Beginner",
intermediate: "Intermediate",
advanced: "Advanced",
};
var StudentPortalPage = function () {
var router = (0, router_1.useRouter)();
var _a = (0, react_1.useState)(null), agentId = _a[0], setAgentId = _a[1];
// Auth State
var _b = (0, react_1.useState)(false), isAuthenticated = _b[0], setIsAuthenticated = _b[1];
var _c = (0, react_1.useState)(null), student = _c[0], setStudent = _c[1];
var _d = (0, react_1.useState)(true), isAuthLoading = _d[0], setIsAuthLoading = _d[1];
var _e = (0, react_1.useState)(null), authError = _e[0], setAuthError = _e[1];
// Course State
var _f = (0, react_1.useState)([]), courses = _f[0], setCourses = _f[1];
var _g = (0, react_1.useState)(false), isCourseLoading = _g[0], setIsCourseLoading = _g[1];
var _h = (0, react_1.useState)(null), courseError = _h[0], setCourseError = _h[1];
var _j = (0, react_1.useState)("Teacher"), teacherName = _j[0], setTeacherName = _j[1]; // Placeholder
// Category State
var _k = (0, react_1.useState)([]), categories = _k[0], setCategories = _k[1];
var _l = (0, react_1.useState)(false), isCategoryLoading = _l[0], setIsCategoryLoading = _l[1];
var _m = (0, react_1.useState)(null), categoryError = _m[0], setCategoryError = _m[1];
// Form State (Login/Signup)
var _o = (0, react_1.useState)(""), loginEmail = _o[0], setLoginEmail = _o[1];
var _p = (0, react_1.useState)(""), loginPassword = _p[0], setLoginPassword = _p[1];
var _q = (0, react_1.useState)(""), signupName = _q[0], setSignupName = _q[1];
var _r = (0, react_1.useState)(""), signupEmail = _r[0], setSignupEmail = _r[1];
var _s = (0, react_1.useState)(""), signupPassword = _s[0], setSignupPassword = _s[1];
var _t = (0, react_1.useState)(false), isSubmitting = _t[0], setIsSubmitting = _t[1];
// Add state for controlling the active auth tab
var _u = (0, react_1.useState)("login"), activeAuthTab = _u[0], setActiveAuthTab = _u[1];
// Add this new state for enrollments
var _v = (0, react_1.useState)([]), enrollments = _v[0], setEnrollments = _v[1];
var _w = (0, react_1.useState)(false), isEnrollmentLoading = _w[0], setIsEnrollmentLoading = _w[1];
// --- Authentication ---
var checkAuthStatus = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var response, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
setIsAuthLoading(true);
setAuthError(null);
_a.label = 1;
case 1:
_a.trys.push([1, 3, 4, 5]);
return [4 /*yield*/, axios_1.default.get("".concat(process.env.NEXT_PUBLIC_BACKEND_URL, "/api/auth/student/status"), {
withCredentials: true
})];
case 2:
response = _a.sent();
if (response.data.isAuthenticated) {
setIsAuthenticated(true);
setStudent(response.data.student);
}
else {
setIsAuthenticated(false);
setStudent(null);
}
return [3 /*break*/, 5];
case 3:
error_1 = _a.sent();
console.error("Auth status check failed:", error_1);
setIsAuthenticated(false);
setStudent(null);
return [3 /*break*/, 5];
case 4:
setIsAuthLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, []);
(0, react_1.useEffect)(function () {
// Extract teacherId from URL query
if (router.isReady) {
var queryTeacherId = router.query.teacherId;
if (typeof queryTeacherId === "string") {
setAgentId(queryTeacherId);
// Fetch teacher details based on teacherId if needed (optional)
// fetchTeacherDetails(queryTeacherId);
}
else {
// Only set error if teacherId is explicitly missing after router is ready
setAuthError("Teacher ID not found in URL. Please use the link provided by your teacher.");
setIsAuthLoading(false); // Stop loading if URL is invalid
}
}
}, [router.isReady, router.query]);
(0, react_1.useEffect)(function () {
// Check auth status once teacherId is confirmed
if (agentId) {
checkAuthStatus();
}
// If no teacherId after router ready, don't proceed with auth check
else if (router.isReady && !agentId) {
setIsAuthLoading(false); // Ensure loading stops
}
}, [agentId, checkAuthStatus, router.isReady]);
// --- Course Fetching (runs when authenticated and agentId is set) ---
var fetchEnrollments = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var apiUrl, response, error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!isAuthenticated || !agentId)
return [2 /*return*/];
setIsEnrollmentLoading(true);
_a.label = 1;
case 1:
_a.trys.push([1, 3, 4, 5]);
apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "";
return [4 /*yield*/, axios_1.default.get("".concat(apiUrl, "/api/enrollments/student"), { withCredentials: true })];
case 2:
response = _a.sent();
console.log("Enrollments response:", response.data);
setEnrollments(response.data || []);
return [3 /*break*/, 5];
case 3:
error_2 = _a.sent();
console.error("Error fetching enrollments:", error_2);
// We don't need to show error for enrollments
setEnrollments([]);
return [3 /*break*/, 5];
case 4:
setIsEnrollmentLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, [isAuthenticated, agentId]);
(0, react_1.useEffect)(function () {
if (isAuthenticated && agentId) {
fetchEnrollments();
}
}, [isAuthenticated, agentId, fetchEnrollments]);
var findEnrollmentForCourse = function (courseId) {
return enrollments.find(function (enrollment) { return enrollment.courseId === courseId; });
};
var fetchCoursesForAgent = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var apiUrl, response, fetchedCourses, coursesWithEnrollment, error_3;
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!isAuthenticated || !agentId)
return [2 /*return*/];
setIsCourseLoading(true);
setCourseError(null);
_c.label = 1;
case 1:
_c.trys.push([1, 3, 4, 5]);
apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "";
return [4 /*yield*/, axios_1.default.get("".concat(apiUrl, "/api/courses/teacher/").concat(agentId), {
withCredentials: true, // Important for sessions/cookies if used
})];
case 2:
response = _c.sent();
// Add debugging for course data
console.log("Courses response:", response.data);
fetchedCourses = [];
if (Array.isArray(response.data)) {
// If response.data is already an array
fetchedCourses = response.data;
}
else if (response.data && Array.isArray(response.data.courses)) {
// If response.data is an object with a courses property that's an array
fetchedCourses = response.data.courses;
}
else if (response.data && typeof response.data === 'object') {
// If response.data is some other object, log a warning and try to handle it
console.warn("Unexpected courses data structure:", response.data);
fetchedCourses = []; // Default to empty array
}
coursesWithEnrollment = fetchedCourses.map(function (course) {
var enrollment = findEnrollmentForCourse(course.id);
return __assign(__assign({}, course), { enrollment: enrollment });
});
console.log("Setting courses with enrollment:", coursesWithEnrollment);
setCourses(coursesWithEnrollment);
// Optionally fetch teacher name here if not included in course data
if (fetchedCourses && fetchedCourses.length > 0) {
// You might need another API endpoint to get teacher details
// const teacherDetails = await axios.get(`${apiUrl}/api/teachers/${agentId}`);
// setTeacherName(teacherDetails.data.name || "Teacher");
setTeacherName("Teacher"); // Default fallback
}
return [3 /*break*/, 5];
case 3:
error_3 = _c.sent();
console.error("Error fetching courses for teacher ".concat(agentId, ":"), error_3);
setCourseError(((_b = (_a = error_3.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) ||
"Failed to load courses for this teacher.");
setCourses([]);
return [3 /*break*/, 5];
case 4:
setIsCourseLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, [isAuthenticated, agentId, enrollments]);
// --- Category Fetching (runs when agentId is set) ---
var fetchCategories = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var apiUrl, response, fetchedCategories, error_4;
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!agentId)
return [2 /*return*/]; // Need agentId to fetch relevant categories
setIsCategoryLoading(true);
setCategoryError(null);
_c.label = 1;
case 1:
_c.trys.push([1, 3, 4, 5]);
apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "";
return [4 /*yield*/, axios_1.default.get("".concat(apiUrl, "/api/categories/teacher/").concat(agentId))];
case 2:
response = _c.sent();
// Add debugging to check the response structure
console.log("Categories response:", response.data);
fetchedCategories = response.data || [];
setCategories(fetchedCategories);
// Log the categories being set
console.log("Categories set:", fetchedCategories);
return [3 /*break*/, 5];
case 3:
error_4 = _c.sent();
console.error("Error fetching categories for agent ".concat(agentId, ":"), error_4);
setCategoryError(((_b = (_a = error_4.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || "Failed to load course categories.");
setCategories([]);
return [3 /*break*/, 5];
case 4:
setIsCategoryLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, [agentId]);
(0, react_1.useEffect)(function () {
// Fetch courses only when authenticated and agentId is available
if (isAuthenticated && agentId) {
fetchCoursesForAgent();
}
}, [isAuthenticated, agentId, fetchCoursesForAgent]);
(0, react_1.useEffect)(function () {
// Fetch categories when agentId is available (independent of auth)
if (agentId) {
fetchCategories();
}
}, [agentId, fetchCategories]);
// --- Auth Form Handlers ---
var handleLogin = function (event) { return __awaiter(void 0, void 0, void 0, function () {
var apiUrl, response, error_5;
var _a, _b, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
event.preventDefault();
if (!loginEmail || !loginPassword) {
sonner_1.toast.error("Please enter both email and password.");
return [2 /*return*/];
}
setIsSubmitting(true);
_d.label = 1;
case 1:
_d.trys.push([1, 3, 4, 5]);
apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:3009";
return [4 /*yield*/, axios_1.default.post("".concat(apiUrl, "/api/student/login"), {
email: loginEmail,
password: loginPassword,
}, { withCredentials: true })];
case 2:
response = _d.sent();
// Check if the backend reported success explicitly
if (response.data && response.data.message === "Login successful") {
sonner_1.toast.success("Login successful!");
// Remove the immediate checkAuthStatus() call
// await checkAuthStatus();
// Just reload the page. The useEffect hooks will run checkAuthStatus on reload.
router.reload();
}
else {
// Handle cases where backend might return 200 but indicate failure
throw new Error(((_a = response.data) === null || _a === void 0 ? void 0 : _a.message) || "Login failed: Invalid response from server.");
}
return [3 /*break*/, 5];
case 3:
error_5 = _d.sent();
console.error("Login failed:", error_5);
sonner_1.toast.error(((_c = (_b = error_5.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) ||
"Login failed. Please check your credentials.");
return [3 /*break*/, 5];
case 4:
setIsSubmitting(false);
setLoginPassword(""); // Clear password field
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); };
var handleSignup = function (event) { return __awaiter(void 0, void 0, void 0, function () {
var response, error_6;
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
event.preventDefault();
if (!signupName || !signupEmail || !signupPassword) {
sonner_1.toast.error("Please fill in all signup fields.");
return [2 /*return*/];
}
setIsSubmitting(true);
_c.label = 1;
case 1:
_c.trys.push([1, 3, 4, 5]);
return [4 /*yield*/, axios_1.default.post("".concat(process.env.NEXT_PUBLIC_BACKEND_URL, "/api/student/register"), {
name: signupName,
email: signupEmail,
password: signupPassword,
})];
case 2:
response = _c.sent();
sonner_1.toast.success(response.data.message || "Signup successful! Please log in.");
// Optionally switch to login tab or clear form
setSignupName("");
setSignupEmail("");
setSignupPassword("");
setActiveAuthTab("login"); // Switch to login tab after successful signup
return [3 /*break*/, 5];
case 3:
error_6 = _c.sent();
console.error("Signup failed:", error_6);
sonner_1.toast.error(((_b = (_a = error_6.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || "Signup failed. Please try again.");
return [3 /*break*/, 5];
case 4:
setIsSubmitting(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); };
var handleLogout = function () { return __awaiter(void 0, void 0, void 0, function () {
var apiUrl, response, error_7;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
console.log("Attempting to log out student...");
// First clear the localStorage for agentId when using it
localStorage.removeItem('currentAgentId');
apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:3009";
return [4 /*yield*/, axios_1.default.post("".concat(apiUrl, "/api/auth/student/logout"), {}, {
withCredentials: true // Important for cookie-based auth
})];
case 1:
response = _a.sent();
// Log the response
console.log("Logout response:", response);
// Clear local state
setIsAuthenticated(false);
setStudent(null);
setCourses([]); // Clear courses on logout
setCategories([]); // Clear categories on logout
// Clear form fields on logout
setLoginEmail("");
setLoginPassword("");
setSignupName("");
setSignupEmail("");
setSignupPassword("");
setActiveAuthTab("login"); // Reset tab
return [3 /*break*/, 3];
case 2:
error_7 = _a.sent();
console.error("Logout failed:", error_7);
sonner_1.toast.error("Logout failed. Please try again.");
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
}); };
// --- Helper function to parse categories safely ---
var parseCourseCategories = function (categoriesJson) {
if (!categoriesJson)
return [];
try {
console.log("Parsing categories JSON:", categoriesJson);
// Handle different possible formats
if (typeof categoriesJson === 'string') {
// If it's a JSON string, try to parse it
try {
var parsed = JSON.parse(categoriesJson);
console.log("Parsed categories:", parsed);
// Handle array of strings or array of objects with id property
if (Array.isArray(parsed)) {
return parsed.map(function (item) { return typeof item === 'object' && item !== null ? item.id || item : item; });
}
// Handle single string or object
else if (parsed) {
return [typeof parsed === 'object' ? parsed.id || '' : parsed];
}
}
catch (jsonError) {
// If JSON parsing fails, it might be a comma-separated string
if (categoriesJson.includes(',')) {
return categoriesJson.split(',').map(function (id) { return id.trim(); });
}
// Or a single category ID
return [categoriesJson];
}
}
// If categories is already an array (not a string that needs parsing)
else if (Array.isArray(categoriesJson)) {
return categoriesJson.map(function (item) { return typeof item === 'object' && item !== null ? item.id || item : item; });
}
return [];
}
catch (error) {
console.error("Failed to parse course categories JSON:", error);
return [];
}
};
// --- Rendering Logic ---
// Combined Loading State for initial page load
if (isAuthLoading || (!isAuthenticated && !authError && agentId === null)) {
// Show loading until agentId is determined or auth check completes
return ((0, jsx_runtime_1.jsxs)("div", { className: "min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-gray-600 dark:text-gray-400 text-lg", children: "Loading Access..." }), (0, jsx_runtime_1.jsx)("div", { className: "ml-4 w-5 h-5 border-t-2 border-blue-500 border-solid rounded-full animate-spin" })] }));
}
// Error State if Agent ID is missing or invalid
if (authError) {
// Display error if authError is set (e.g., invalid agentId)
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 p-4", children: (0, jsx_runtime_1.jsxs)(card_1.Card, { className: "w-full max-w-md bg-red-50 dark:bg-red-900/20 border-red-300 dark:border-red-700", children: [(0, jsx_runtime_1.jsx)(card_1.CardHeader, { children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { className: "text-red-700 dark:text-red-400", children: "Access Error" }) }), (0, jsx_runtime_1.jsxs)(card_1.CardContent, { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-red-600 dark:text-red-400", children: authError }), (0, jsx_runtime_1.jsx)("p", { className: "mt-4 text-sm text-gray-600 dark:text-gray-400", children: "Please check the URL or contact the teacher who shared it." })] })] }) }));
}
// --- Render Auth Forms if not authenticated ---
if (!isAuthenticated) {
return ((0, jsx_runtime_1.jsxs)("div", { className: "min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900 p-4", children: [(0, jsx_runtime_1.jsx)(sonner_1.Toaster, { position: "top-center", richColors: true }), (0, jsx_runtime_1.jsxs)(tabs_1.Tabs, { value: activeAuthTab, onValueChange: setActiveAuthTab, className: "w-full max-w-md", children: [(0, jsx_runtime_1.jsxs)(tabs_1.TabsList, { className: "grid w-full grid-cols-2", children: [(0, jsx_runtime_1.jsx)(tabs_1.TabsTrigger, { value: "login", children: "Login" }), (0, jsx_runtime_1.jsx)(tabs_1.TabsTrigger, { value: "signup", children: "Sign Up" })] }), (0, jsx_runtime_1.jsx)(tabs_1.TabsContent, { value: "login", children: (0, jsx_runtime_1.jsxs)(card_1.Card, { children: [(0, jsx_runtime_1.jsxs)(card_1.CardHeader, { children: [(0, jsx_runtime_1.jsx)(card_1.CardTitle, { children: "Student Login" }), (0, jsx_runtime_1.jsx)(card_1.CardDescription, { children: "Access your courses by logging in." })] }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleLogin, children: [(0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "login-email", children: "Email" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "login-email", type: "email", placeholder: "m@example.com", required: true, value: loginEmail, onChange: function (e) { return setLoginEmail(e.target.value); }, disabled: isSubmitting })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "login-password", children: "Password" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "login-password", type: "password", required: true, value: loginPassword, onChange: function (e) { return setLoginPassword(e.target.value); }, disabled: isSubmitting })] })] }), (0, jsx_runtime_1.jsx)(card_1.CardFooter, { children: (0, jsx_runtime_1.jsx)(button_1.Button, { type: "submit", className: "w-full", disabled: isSubmitting, children: isSubmitting ? "Logging In..." : "Login" }) })] })] }) }), (0, jsx_runtime_1.jsx)(tabs_1.TabsContent, { value: "signup", children: (0, jsx_runtime_1.jsxs)(card_1.Card, { children: [(0, jsx_runtime_1.jsxs)(card_1.CardHeader, { children: [(0, jsx_runtime_1.jsx)(card_1.CardTitle, { children: "Student Sign Up" }), (0, jsx_runtime_1.jsx)(card_1.CardDescription, { children: "Create an account to enroll in courses." })] }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSignup, children: [(0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "signup-name", children: "Name" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "signup-name", placeholder: "Your Name", required: true, value: signupName, onChange: function (e) { return setSignupName(e.target.value); }, disabled: isSubmitting })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "signup-email", children: "Email" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "signup-email", type: "email", placeholder: "m@example.com", required: true, value: signupEmail, onChange: function (e) { return setSignupEmail(e.target.value); }, disabled: isSubmitting })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "signup-password", children: "Password" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "signup-password", type: "password", required: true, value: signupPassword, onChange: function (e) { return setSignupPassword(e.target.value); }, disabled: isSubmitting })] })] }), (0, jsx_runtime_1.jsx)(card_1.CardFooter, { children: (0, jsx_runtime_1.jsx)(button_1.Button, { type: "submit", className: "w-full", disabled: isSubmitting, children: isSubmitting ? "Signing Up..." : "Sign Up" }) })] })] }) })] })] }));
}
// --- Render Authenticated Student Dashboard ---
return ((0, jsx_runtime_1.jsxs)("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100", children: [(0, jsx_runtime_1.jsx)(sonner_1.Toaster, { position: "top-center", richColors: true }), (0, jsx_runtime_1.jsx)("header", { className: "bg-white dark:bg-gray-800 shadow-sm", children: (0, jsx_runtime_1.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center h-16", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex-shrink-0 flex items-center", children: (0, jsx_runtime_1.jsxs)("span", { className: "font-bold text-xl", children: [teacherName, "'s Courses"] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center space-x-4", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-sm font-medium", children: ["Welcome, ", (student === null || student === void 0 ? void 0 : student.name) || "Student", "!"] }), (0, jsx_runtime_1.jsx)(link_1.default, { href: "/student/profile?teacherId=".concat(agentId), passHref: true, children: (0, jsx_runtime_1.jsx)(button_1.Button, { variant: "outline", size: "sm", children: "Profile" }) }), (0, jsx_runtime_1.jsx)(button_1.Button, { variant: "ghost", size: "sm", onClick: handleLogout, children: "Logout" })] })] }) }) }), (0, jsx_runtime_1.jsxs)("main", { className: "max-w-7xl mx-auto py-6 sm:px-6 lg:px-8", children: [(isCourseLoading || isCategoryLoading) && ((0, jsx_runtime_1.jsxs)("div", { className: "text-center py-16", children: [(0, jsx_runtime_1.jsx)("div", { className: "inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-4 text-gray-500 dark:text-gray-400", children: "Loading your learning journey..." })] })), !isCourseLoading && !isCategoryLoading && (courseError || categoryError) && ((0, jsx_runtime_1.jsx)("div", { className: "px-4 py-6 sm:px-0", children: (0, jsx_runtime_1.jsxs)(card_1.Card, { className: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-300 dark:border-yellow-700", children: [(0, jsx_runtime_1.jsx)(card_1.CardHeader, { children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { className: "text-yellow-700 dark:text-yellow-400", children: "Warning" }) }), (0, jsx_runtime_1.jsx)(card_1.CardContent, { children: (0, jsx_runtime_1.jsx)("p", { className: "text-yellow-600 dark:text-yellow-400", children: courseError ||
categoryError ||
"Could not load all course information." }) })] }) })), !isCourseLoading && !isCategoryLoading && !courseError && !categoryError && ((0, jsx_runtime_1.jsxs)("div", { className: "px-4 py-6 sm:px-0", children: [categories.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "space-y-12", children: categories.map(function (category) {
// Filter courses that belong to this category
var coursesInCategory = courses.filter(function (course) {
var courseCats = parseCourseCategories(course.categories);
console.log("Course ".concat(course.title, " has categories:"), courseCats);
return courseCats.some(function (catId) {
return catId === category.id ||
// Also check if the category name matches in case IDs don't match exactly
(typeof catId === 'string' &&
category.name &&
catId.toLowerCase() === category.name.toLowerCase());
});
});
// Only render categories that have courses
if (coursesInCategory.length === 0)
return null;
return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-6", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-2xl font-bold tracking-tight", children: category.name }), category.description && ((0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: category.description }))] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6", children: coursesInCategory.map(function (course) { return ((0, jsx_runtime_1.jsxs)(card_1.Card, { className: "overflow-hidden hover:shadow-lg transition-shadow duration-300 h-full flex flex-col", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative pb-[56.25%] bg-gray-100 dark:bg-gray-800 overflow-hidden", children: [course.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: course.thumbnail, alt: "".concat(course.title, " thumbnail"), className: "absolute inset-0 w-full h-full object-cover", onError: function (e) {
e.target.src = "/placeholder-course.jpg";
} })) : ((0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-200 dark:bg-gray-700", children: (0, jsx_runtime_1.jsx)("span", { className: "text-gray-400 dark:text-gray-500 text-sm", children: "No image" }) })), (0, jsx_runtime_1.jsx)("div", { className: "absolute top-2 left-2 bg-blue-600 text-white text-xs px-2 py-1 rounded", children: levelNames[course.level] || course.level })] }), (0, jsx_runtime_1.jsx)(card_1.CardHeader, { className: "p-4 pb-0", children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { className: "text-lg", children: course.title }) }), (0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "p-4 pt-2 flex-grow", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400 line-clamp-3", children: course.description || "No description available." }), course.enrollment && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-xs mb-1", children: [(0, jsx_runtime_1.jsx)("span", { children: "Progress" }), (0, jsx_runtime_1.jsxs)("span", { children: [Math.round(course.enrollment.progress), "%"] })] }), (0, jsx_runtime_1.jsx)("div", { className: "w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2", children: (0, jsx_runtime_1.jsx)("div", { className: "bg-blue-600 h-2 rounded-full", style: { width: "".concat(course.enrollment.progress, "%") } }) })] }))] }), (0, jsx_runtime_1.jsx)(card_1.CardFooter, { className: "px-4 pb-4 pt-0 mt-auto", children: (0, jsx_runtime_1.jsx)(button_1.Button, { className: "w-full", onClick: function () { return router.push("/student/course/".concat(course.id, "?teacherId=").concat(agentId)); }, children: course.enrollment ? 'Continue Learning' : 'View Course' }) })] }, course.id)); }) })] }, category.id));
}) })), (function () {
// Find courses that don't belong to any of the displayed categories
var uncategorizedCourses = courses.filter(function (course) {
var courseCats = parseCourseCategories(course.categories);
// A course is considered uncategorized if:
// 1. It has no categories at all, OR
// 2. None of its categories match any category in our categories list
if (courseCats.length === 0)
return true;
// Check if any of this course's categories match any category we have
return !categories.some(function (category) {
return courseCats.some(function (catId) {
return catId === category.id ||
(typeof catId === 'string' &&
category.name &&
catId.toLowerCase() === category.name.toLowerCase());
});
});
});
if (uncategorizedCourses.length === 0)
return null;
return ((0, jsx_runtime_1.jsxs)("section", { className: "mt-12", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-6", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-2xl font-bold tracking-tight", children: "Other Courses" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: "Courses that don't belong to the displayed categories" })] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6", children: uncategorizedCourses.map(function (course) { return ((0, jsx_runtime_1.jsxs)(card_1.Card, { className: "overflow-hidden hover:shadow-lg transition-shadow duration-300 h-full flex flex-col", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative pb-[56.25%] bg-gray-100 dark:bg-gray-800 overflow-hidden", children: [course.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: course.thumbnail, alt: "".concat(course.title, " thumbnail"), className: "absolute inset-0 w-full h-full object-cover", onError: function (e) {
e.target.src = "/placeholder-course.jpg";
} })) : ((0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-200 dark:bg-gray-700", children: (0, jsx_runtime_1.jsx)("span", { className: "text-gray-400 dark:text-gray-500 text-sm", children: "No image" }) })), (0, jsx_runtime_1.jsx)("div", { className: "absolute top-2 left-2 bg-blue-600 text-white text-xs px-2 py-1 rounded", children: levelNames[course.level] || course.level })] }), (0, jsx_runtime_1.jsx)(card_1.CardHeader, { className: "p-4 pb-0", children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { className: "text-lg", children: course.title }) }), (0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "p-4 pt-2 flex-grow", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400 line-clamp-3", children: course.description || "No description available." }), course.enrollment && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-xs mb-1", children: [(0, jsx_runtime_1.jsx)("span", { children: "Progress" }), (0, jsx_runtime_1.jsxs)("span", { children: [Math.round(course.enrollment.progress), "%"] })] }), (0, jsx_runtime_1.jsx)("div", { className: "w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2", children: (0, jsx_runtime_1.jsx)("div", { className: "bg-blue-600 h-2 rounded-full", style: { width: "".concat(course.enrollment.progress, "%") } }) })] }))] }), (0, jsx_runtime_1.jsx)(card_1.CardFooter, { className: "px-4 pb-4 pt-0 mt-auto", children: (0, jsx_runtime_1.jsx)(button_1.Button, { className: "w-full", onClick: function () { return router.push("/student/course/".concat(course.id, "?teacherId=").concat(agentId)); }, children: course.enrollment ? 'Continue Learning' : 'View Course' }) })] }, course.id)); }) })] }));
})(), courses.length === 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "text-center py-16", children: [(0, jsx_runtime_1.jsx)("svg", { className: "mx-auto h-12 w-12 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" }) }), (0, jsx_runtime_1.jsx)("h3", { className: "mt-2 text-sm font-medium text-gray-900 dark:text-gray-100", children: "No courses available" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: "There are no courses available from this teacher yet." })] }))] }))] })] }));
};
exports.default = StudentPortalPage;