diff --git a/backend/controllers/courseController.ts b/backend/controllers/courseController.ts index 0bbdca4..facb19f 100644 --- a/backend/controllers/courseController.ts +++ b/backend/controllers/courseController.ts @@ -52,7 +52,9 @@ export const getCourseById = async ( if (id) { // Find course by ID and populate related fields - const course = await Course.findById(id).populate(["speakers"]).exec(); + const course = await Course.findById(id) + .populate(["speakers", "handouts"]) + .exec(); if (!course) { res.status(404).json({ @@ -118,6 +120,7 @@ export const createCourse = async ( productInfo, shortUrl, draft, + registrationLimit, } = req.body; // Validate required fields @@ -175,6 +178,7 @@ export const createCourse = async ( productInfo, shortUrl, draft, + registrationLimit, }); const savedCourseResponse = await newCourseResponse.save(); @@ -402,9 +406,15 @@ export const getCourseUsers = async ( return; } - // Check if the course exists and populate the students field + // Check if the course exists and populate the students field with their roles console.log("Looking up course in database..."); - const course = await Course.findById(courseId).populate("students"); + const course = await Course.findById(courseId).populate({ + path: "students", + populate: { + path: "role", + model: "UserType", + }, + }); console.log("Found course:", course ? "Yes" : "No"); if (!course) { console.log("Course not found in database"); diff --git a/backend/controllers/handoutController.ts b/backend/controllers/handoutController.ts index ad309fb..8d53898 100644 --- a/backend/controllers/handoutController.ts +++ b/backend/controllers/handoutController.ts @@ -1,10 +1,14 @@ import { Request, Response } from "express"; import Handout from "../models/handoutModel"; +import Course from "../models/courseModel"; // @desc Get all handouts or filter handouts by courseId // @route GET /api/handouts // @access Public -export const getHandouts = async (req: Request, res: Response): Promise => { +export const getHandouts = async ( + req: Request, + res: Response +): Promise => { try { const { courseId } = req.query; @@ -39,7 +43,10 @@ export const getHandouts = async (req: Request, res: Response): Promise => // @desc Create a new handout // @route POST /api/handouts // @access Public -export const createHandout = async (req: Request, res: Response): Promise => { +export const createHandout = async ( + req: Request, + res: Response +): Promise => { try { const { courseId, fileUrl, fileType } = req.body; @@ -82,7 +89,10 @@ export const createHandout = async (req: Request, res: Response): Promise // @desc Update a handout // @route PUT /api/handouts/:id // @access Public -export const updateHandout = async (req: Request, res: Response): Promise => { +export const updateHandout = async ( + req: Request, + res: Response +): Promise => { try { const { id } = req.params; const { fileUrl, fileType } = req.body; @@ -124,9 +134,13 @@ export const updateHandout = async (req: Request, res: Response): Promise // @desc Delete a handout // @route DELETE /api/handouts/:id // @access Public -export const deleteHandout = async (req: Request, res: Response): Promise => { +export const deleteHandout = async ( + req: Request, + res: Response +): Promise => { try { const { id } = req.params; + const { courseId } = req.body; const deletedHandout = await Handout.findByIdAndDelete(id); if (!deletedHandout) { @@ -137,6 +151,13 @@ export const deleteHandout = async (req: Request, res: Response): Promise return; } + // Remove reference from Course.handouts + if (courseId) { + await Course.findByIdAndUpdate(courseId, { + $pull: { handouts: id }, + }); + } + res.status(200).json({ success: true, data: deletedHandout, @@ -155,4 +176,4 @@ export const deleteHandout = async (req: Request, res: Response): Promise }); } } -}; \ No newline at end of file +}; diff --git a/backend/controllers/surveyController.ts b/backend/controllers/surveyController.ts index e9b4bc5..6a0d535 100644 --- a/backend/controllers/surveyController.ts +++ b/backend/controllers/surveyController.ts @@ -5,126 +5,139 @@ import Survey, { ISurvey } from "../models/surveyModel"; // @route GET /api/surveys // @access Public export const getSurvey = async (req: Request, res: Response): Promise => { - try { - // Use `findOneAndUpdate` to find the survey and create one if it doesn't exist - const survey = await Survey.findOneAndUpdate( - {}, // Find the first document (survey) - { $setOnInsert: { questions: [] } }, // If no document is found, insert a new one with empty questions - { new: true, upsert: true } // Return the updated/new document, and if not found, create it - ).populate({ - path: "questions", - model: "Question", - }); - - res.status(200).json(survey); - } catch (error) { - if (error instanceof Error) { - res.status(500).json({ message: error.message }); - } else { - res.status(500).json({ message: "An unknown error occurred" }); - } - } + try { + // Use `findOneAndUpdate` to find the survey and create one if it doesn't exist + const survey = await Survey.findOneAndUpdate( + {}, // Find the first document (survey) + { $setOnInsert: { questions: [] } }, // If no document is found, insert a new one with empty questions + { new: true, upsert: true } // Return the updated/new document, and if not found, create it + ).populate({ + path: "questions", + model: "Question", + }); + + res.status(200).json(survey); + } catch (error) { + if (error instanceof Error) { + res.status(500).json({ message: error.message }); + } else { + res.status(500).json({ message: "An unknown error occurred" }); + } + } }; // @desc Create a new survey (only if it doesn't already exist) // @route POST /api/surveys // @access Public -// export const createSurvey = async (req: Request, res: Response): Promise => { -// try { -// // Check if a survey already exists -// const existingSurvey = await Survey.findOne(); - -// if (existingSurvey) { -// res.status(200).json({ -// survey: existingSurvey, -// message: "Survey already exists. You can update it instead.", -// }); -// return; -// } - -// const { questions } = req.body; - -// if (!questions) { -// res.status(400).json({ message: "Questions are required" }); -// return; -// } - -// // Create a new survey -// const survey = new Survey({ -// questions, -// }); -// await survey.save(); - -// res.status(201).json({ -// survey, -// message: "Survey created successfully", -// }); -// } catch (error) { -// if (error instanceof Error) { -// res.status(500).json({ message: error.message }); -// } else { -// res.status(500).json({ message: "An unknown error occurred" }); -// } -// } -// }; +export const createSurvey = async ( + req: Request, + res: Response +): Promise => { + try { + // Check if a survey already exists + const existingSurvey = await Survey.findOne(); + + if (existingSurvey) { + res.status(200).json({ + survey: existingSurvey, + message: "Survey already exists. You can update it instead.", + }); + return; + } + + const { questions } = req.body; + + if (!questions) { + res.status(400).json({ message: "Questions are required" }); + return; + } + + // Create a new survey + const survey = new Survey({ + questions, + }); + await survey.save(); + + res.status(201).json({ + survey, + message: "Survey created successfully", + }); + } catch (error) { + if (error instanceof Error) { + res.status(500).json({ message: error.message }); + } else { + res.status(500).json({ message: "An unknown error occurred" }); + } + } +}; // @desc Update the existing survey // @route PUT /api/surveys // @access Public -export const updateSurvey = async (req: Request, res: Response): Promise => { - try { - const { questions } = req.body; - - // Find the single existing survey (since there is only one survey) - const survey = await Survey.findOne(); - - if (!survey) { - res.status(404).json({ message: "Survey not found" }); - return; - } - - // Update the survey with new questions - survey.question = questions; - await survey.save(); - - res.status(200).json(survey); - } catch (error) { - if (error instanceof Error) { - res.status(500).json({ message: error.message }); - } else { - res.status(500).json({ message: "An unknown error occurred" }); - } - } +export const updateSurvey = async ( + req: Request, + res: Response +): Promise => { + try { + const { questions } = req.body; + + console.log(questions); + + // Find the single existing survey (since there is only one survey) + const survey = await Survey.findOne(); + + if (!survey) { + res.status(404).json({ message: "Survey not found" }); + return; + } + + // Update the survey with new questions + survey.questions = questions; + await survey.save(); + console.log("survey", survey); + + res.status(200).json(survey); + } catch (error) { + if (error instanceof Error) { + console.error(error); + res.status(500).json({ message: error.message }); + } else { + res.status(500).json({ message: "An unknown error occurred" }); + } + } }; // @desc Delete the survey // @route DELETE /api/surveys // @access Public -export const deleteSurvey = async (req: Request, res: Response): Promise => { - try { - // Since there is only one survey, no need to look for it by ID - const survey = await Survey.findOne(); - - if (!survey) { - res.status(404).json({ - success: false, - message: "Survey not found", - }); - return; - } - - // Delete the survey - await Survey.deleteOne({ _id: survey._id }); - - res.status(200).json({ - success: true, - message: "Survey deleted successfully", - }); - } catch (error) { - if (error instanceof Error) { - res.status(500).json({ message: error.message }); - } else { - res.status(500).json({ message: "An unknown error occurred" }); - } - } +export const deleteSurvey = async ( + req: Request, + res: Response +): Promise => { + try { + // Since there is only one survey, no need to look for it by ID + const survey = await Survey.findOne(); + + if (!survey) { + res.status(404).json({ + success: false, + message: "Survey not found", + }); + return; + } + + // Delete the survey + await Survey.deleteOne({ _id: survey._id }); + + res.status(200).json({ + success: true, + message: "Survey deleted successfully", + }); + } catch (error) { + if (error instanceof Error) { + res.status(500).json({ message: error.message }); + } else { + res.status(500).json({ message: "An unknown error occurred" }); + } + } }; diff --git a/backend/controllers/userController.ts b/backend/controllers/userController.ts index 16fdeee..09663c9 100644 --- a/backend/controllers/userController.ts +++ b/backend/controllers/userController.ts @@ -16,6 +16,9 @@ export const getUsers = async (req: Request, res: Response): Promise => { pagination = "true", } = req.query; + // Extract _id parameter with proper typing + const _id = req.query._id as string | string[] | undefined; + let query: any = {}; if (search) { @@ -29,6 +32,26 @@ export const getUsers = async (req: Request, res: Response): Promise => { query.userType = userType; } + // Handle _id filtering - support both single ID and array of IDs + if (_id) { + if (Array.isArray(_id)) { + // Multiple IDs: _id[] + const objectIds = _id.map((id) => new mongoose.Types.ObjectId(id)); + query._id = { $in: objectIds }; + } else { + // Single ID or comma-separated string + const ids = _id.split(","); + if (ids.length > 1) { + const objectIds = ids.map( + (id) => new mongoose.Types.ObjectId(id.trim()) + ); + query._id = { $in: objectIds }; + } else { + query._id = new mongoose.Types.ObjectId(ids[0].trim()); + } + } + } + // If pagination=false, return all matching users if (pagination === "false") { const users = await User.find(query) @@ -36,6 +59,7 @@ export const getUsers = async (req: Request, res: Response): Promise => { "name email role company certification address1 city state zip phone language certification" ) .populate("role"); + res.json({ users, total: users.length, pages: 1 }); return; } @@ -273,11 +297,15 @@ export const checkAdmin = async (req: AuthenticatedRequest, res: Response) => { } }; -export const getUserById = async (req: Request, res: Response) => { +export const getUserById = async ( + req: Request, + res: Response +): Promise => { try { const user = await User.findById(req.params.id); if (!user) { - return res.status(404).json({ message: "User not found" }); + res.status(404).json({ message: "User not found" }); + return; } res.json(user); } catch (error) { diff --git a/backend/models/courseModel.ts b/backend/models/courseModel.ts index bcfdb1a..e5f032b 100644 --- a/backend/models/courseModel.ts +++ b/backend/models/courseModel.ts @@ -33,6 +33,7 @@ export interface ICourse extends Document { productInfo: string; shortUrl: string; draft: boolean; + registrationLimit: number; } const CourseSchema: Schema = new Schema( @@ -49,7 +50,7 @@ const CourseSchema: Schema = new Schema( ref: "Rating", }, ], - className: { type: String, required: true }, + className: { type: String, required: false }, discussion: { type: String, required: false }, categories: [{ type: String, required: false }], creditNumber: { type: Number, required: false }, @@ -96,6 +97,7 @@ const CourseSchema: Schema = new Schema( productInfo: { type: String, required: false }, shortUrl: { type: String, required: false }, draft: { type: Boolean, required: true, default: true }, + registrationLimit: { type: Number, required: false, default: 0 }, }, { timestamps: true, diff --git a/backend/models/surveyModel.ts b/backend/models/surveyModel.ts index f30d6f1..626a623 100644 --- a/backend/models/surveyModel.ts +++ b/backend/models/surveyModel.ts @@ -5,7 +5,7 @@ import { IQuestion } from "./questionModel"; export interface ISurvey extends Document { // Define fields here: id: string; - question: mongoose.Types.ObjectId[]; + questions: mongoose.Types.ObjectId[]; courseId: mongoose.Types.ObjectId; } diff --git a/backend/routes/surveyRoutes.ts b/backend/routes/surveyRoutes.ts index e665d07..d4bb3ab 100644 --- a/backend/routes/surveyRoutes.ts +++ b/backend/routes/surveyRoutes.ts @@ -15,7 +15,7 @@ router.get("/", getSurvey); // router.post("/", createSurvey); // PUT update survey by ID -router.put("/:id", updateSurvey); +router.put("/", updateSurvey); // DELETE survey by ID router.delete("/:id", deleteSurvey); diff --git a/backend/routes/userRoutes.ts b/backend/routes/userRoutes.ts index 11329f5..daafb54 100644 --- a/backend/routes/userRoutes.ts +++ b/backend/routes/userRoutes.ts @@ -1,6 +1,7 @@ import express from "express"; import { getUsers, + getUserById, getOrCreateUser, updateUser, deleteUser, @@ -14,6 +15,9 @@ const router = express.Router(); // GET all users or by filter router.get("/", getUsers); +// GET user by ID +router.get("/:id", getUserById); + // POST new user if does not exist, otherwise return existing user for login router.post("/", getOrCreateUser); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index db1519d..8b2dcb7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,6 +26,7 @@ "quill": "^2.0.3", "react": "^18.3.1", "react-country-state-city": "^1.1.12", + "react-datepicker": "^8.4.0", "react-dom": "^18.3.1", "react-icons": "^5.4.0", "react-quill": "^2.0.0", @@ -3039,28 +3040,56 @@ "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==" }, "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.13", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.13.tgz", + "integrity": "sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.4", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz", + "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, "node_modules/@googlemaps/js-api-loader": { @@ -6241,6 +6270,15 @@ "node": ">=0.8" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -14194,6 +14232,21 @@ "react": ">=16" } }, + "node_modules/react-datepicker": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.4.0.tgz", + "integrity": "sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.3", + "clsx": "^2.1.1", + "date-fns": "^4.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -16328,6 +16381,12 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7d80b36..3687688 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "quill": "^2.0.3", "react": "^18.3.1", "react-country-state-city": "^1.1.12", + "react-datepicker": "^8.4.0", "react-dom": "^18.3.1", "react-icons": "^5.4.0", "react-quill": "^2.0.0", diff --git a/frontend/public/images/pfp.jpg b/frontend/public/images/pfp.jpg new file mode 100644 index 0000000..82d4b6d Binary files /dev/null and b/frontend/public/images/pfp.jpg differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4c06dff..5a0877b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import React from "react"; import AppRoutes from "./routes/appRoutes"; +import "react-datepicker/dist/react-datepicker.css"; const App: React.FC = () => { return ( diff --git a/frontend/src/components/AdminCoursePreview/AdminCoursePreview.tsx b/frontend/src/components/AdminCoursePreview/AdminCoursePreview.tsx index a93340c..89d897c 100644 --- a/frontend/src/components/AdminCoursePreview/AdminCoursePreview.tsx +++ b/frontend/src/components/AdminCoursePreview/AdminCoursePreview.tsx @@ -35,7 +35,7 @@ interface AdminCoursePreviewProps { } const elemColors: Record = { - Ongoing: "#30CD5A", + Ongoing: "#444444", "Open Registration": "#F79518", "Closed Registration": "#BE0000", Storage: "#444444", @@ -103,7 +103,14 @@ function AdminCoursePreview({ style={{ accentColor: "#8757a3" }} />
- {product.course.className} + + {product.course.className}{" "} + {product.course.draft ? ( +

[Draft]

+ ) : ( + "" + )} +
@@ -133,15 +140,15 @@ function AdminCoursePreview({ )} - + {/* {product.startTime.getMonth() + 1}/{product.startTime.getDate()}/ {product.startTime.getFullYear()} at {product.startTime.getHours()}: {product.startTime.getMinutes().toString().padStart(2, "0")}{" "} {product.timeZone} - + */} -
+ {/*
Live
-
- {product.course.students.length} registered -
+
+
*/} + +
+ {product.course.students.length} + {product.course.registrationLimit > 0 + ? `/${product.course.registrationLimit}` + : ""}{" "} + registered
diff --git a/frontend/src/components/EditCourseSidebar/editCoursePageSideBar.tsx b/frontend/src/components/EditCourseSidebar/editCoursePageSideBar.tsx index cb267a6..33c97bc 100644 --- a/frontend/src/components/EditCourseSidebar/editCoursePageSideBar.tsx +++ b/frontend/src/components/EditCourseSidebar/editCoursePageSideBar.tsx @@ -39,7 +39,21 @@ const EditSideBar: React.FC = ({ children }) => { const loadCourse = async () => { try { const res = await apiClient.get(`/courses/${courseId}`); - setAllFields({ ...res.data.data, _id: courseId }); + const course = res.data.data; + + const cleanedCourse = { + ...course, + speakers: Array.isArray(course.speakers) + ? course.speakers.map((s: any) => s._id.toString()) + : [], + _id: courseId, + // Convert date strings to Date objects + time: course.time ? new Date(course.time) : new Date(), + regStart: course.regStart ? new Date(course.regStart) : new Date(), + regEnd: course.regEnd ? new Date(course.regEnd) : undefined, + }; + + setAllFields(cleanedCourse); setFetchedFromBackend(); } catch (err) { console.error("Failed to fetch course", err); @@ -65,7 +79,7 @@ const EditSideBar: React.FC = ({ children }) => { highlightLeftOffset: "51.4px", }, { - name: "Pricing", + name: "Settings", path: `${basePath}/pricing`, highlightLeftOffset: "51.4px", }, @@ -90,7 +104,7 @@ const EditSideBar: React.FC = ({ children }) => { highlightLeftOffset: "24.5px", }, { - name: "Participation", + name: "Progress", path: `${basePath}/participation`, highlightLeftOffset: "16px", }, @@ -103,11 +117,11 @@ const EditSideBar: React.FC = ({ children }) => { const filteredSidebarItems = sidebarItems.filter((item) => { if (isManagePage) { - return ["Speakers", "Registrants", "Participation", "Email"].includes( + return ["Speakers", "Registrants", "Progress", "Email"].includes( item.name ); } else { - return !["Registrants", "Participation", "Email"].includes(item.name); + return !["Registrants", "Progress", "Email"].includes(item.name); } }); diff --git a/frontend/src/components/GlobalBlackBar/globalBlackBar.tsx b/frontend/src/components/GlobalBlackBar/globalBlackBar.tsx index 5b46be3..f8a911c 100644 --- a/frontend/src/components/GlobalBlackBar/globalBlackBar.tsx +++ b/frontend/src/components/GlobalBlackBar/globalBlackBar.tsx @@ -9,25 +9,27 @@ const GlobalBlackBar = () => { useEffect(() => { const checkTranslateElement = () => { - const translateElement = document.getElementById("google_translate_element"); + const translateElement = document.getElementById( + "google_translate_element" + ); if (translateElement && !translateElement.hasChildNodes()) { if (window.googleTranslateElementInit) { window.googleTranslateElementInit(); } } }; - + setTimeout(checkTranslateElement, 1000); const handleTranslateClick = () => { - const googleElement = document.querySelector('.goog-te-gadget-simple'); + const googleElement = document.querySelector(".goog-te-gadget-simple"); if (googleElement) { (googleElement as HTMLElement).click(); } else { - console.log('bruh'); + console.log("bruh"); if (window.googleTranslateElementInit) { window.googleTranslateElementInit(); setTimeout(() => { - const newElement = document.querySelector('.goog-te-gadget-simple'); + const newElement = document.querySelector(".goog-te-gadget-simple"); if (newElement) { (newElement as HTMLElement).click(); } @@ -38,12 +40,12 @@ const GlobalBlackBar = () => { const translateLabel = translateLabelRef.current; if (translateLabel) { - translateLabel.addEventListener('click', handleTranslateClick); + translateLabel.addEventListener("click", handleTranslateClick); } return () => { if (translateLabel) { - translateLabel.removeEventListener('click', handleTranslateClick); + translateLabel.removeEventListener("click", handleTranslateClick); } }; }, []); @@ -54,74 +56,58 @@ const GlobalBlackBar = () => {
- Traducir
- + { if (course._id) { console.log("course:", course); await apiClient.put(`/courses/${course._id}`, course); + navigate("/admin/products"); alert("Course updated!"); } else { const res = await apiClient.post("/courses", course); + navigate("/admin/products"); alert("Course created!"); // Optionally redirect to edit mode after creating: // navigate(`/admin/product/edit/${res.data.data._id}/details`); @@ -36,19 +38,32 @@ const SaveCourseButton = ({ prevLink, nextLink }: SaveCourseButtonProps) => { } }; + const handlePublish = async () => { + await apiClient.put(`/courses/${course._id}`, { draft: !course.draft }); + navigate("/admin/products"); + }; + const navigate = useNavigate(); return (
+ + {prevLink === "" ? ( <> ) : ( @@ -62,14 +77,18 @@ const SaveCourseButton = ({ prevLink, nextLink }: SaveCourseButtonProps) => { )} - + {nextLink === "" ? ( + <> + ) : ( + + )}
); }; diff --git a/frontend/src/components/Sidebar/sidebar.tsx b/frontend/src/components/Sidebar/sidebar.tsx index 580f28b..c16df35 100644 --- a/frontend/src/components/Sidebar/sidebar.tsx +++ b/frontend/src/components/Sidebar/sidebar.tsx @@ -46,11 +46,11 @@ export const items = [ description: "Catalog", href: "/catalog", }, - { - icon: , - description: "Podcasts", - href: "#", - }, + // { + // icon: , + // description: "Podcasts", + // href: "#", + // }, { icon: , description: "Calendar", @@ -65,7 +65,7 @@ export const items = [ { icon: , description: "Contact", - href: "mailto:info@fostersource.org", + href: "mailto:info@bethesource.org", }, ]; @@ -154,13 +154,7 @@ export function Profile({ isCollapsed, isLoggedIn, name, role }: ProfileProps) {
)} {isLoggedIn && ( - Profile + Profile )} {isLoggedIn && !isCollapsed && (
diff --git a/frontend/src/components/dropdown-select.tsx b/frontend/src/components/dropdown-select.tsx index d57c4d5..107b68b 100644 --- a/frontend/src/components/dropdown-select.tsx +++ b/frontend/src/components/dropdown-select.tsx @@ -50,7 +50,7 @@ const Dropdown: React.FC = ({ buttonLabel, menuItems }) => { {isOpen && (
>; + workshop: any; + prerequisites: { survey: string; certificate: string }; + setPrerequisites: Dispatch< + SetStateAction<{ survey: string; certificate: string }> + >; } interface CheckboxProps { - label: string; - checked: boolean; - onChange: (checked: boolean) => void; - } - - export function Checkbox({ label, checked, onChange }: CheckboxProps) { - return ( - - ); - } - + label: string; + checked: boolean; + onChange: (checked: boolean) => void; +} -export default function CertificateCard({ workshop, prerequisites, setPrerequisites }: WorkshopProps) { - const formatMenuItems = [ - { label: "None Selected", onClick: () => setPrerequisites((prev) => ({ ...prev, survey: "None Selected" })) }, - { label: "Survey", onClick: () => setPrerequisites((prev) => ({ ...prev, certificate: "Survey" })) } - ]; +export function Checkbox({ label, checked, onChange }: CheckboxProps) { + return ( + + ); +} - return ( -
-
-
- -

{workshop?.name || "Certificate"}

-
- -

Certificate Template

-

Prerequisites

+export default function CertificateCard({ + workshop, + prerequisites, + setPrerequisites, +}: WorkshopProps) { + const formatMenuItems = [ + { + label: "None Selected", + onClick: () => + setPrerequisites((prev) => ({ ...prev, survey: "None Selected" })), + }, + { + label: "Survey", + onClick: () => + setPrerequisites((prev) => ({ ...prev, certificate: "Survey" })), + }, + ]; + + return ( +
+
+
+ +

+ {workshop?.name || "Certificate"} +

+
+ +

Certificate Template

+ {/*

Prerequisites

@@ -56,37 +71,42 @@ export default function CertificateCard({ workshop, prerequisites, setPrerequisi throw new Error("Function not implemented."); } } />
- -
Preview
-
-

Content

-
- -
-
-
- -
-
- - - -
- -
+ */} +
Preview
+
+

Content

+
+ +
+
+ +
+
+ +
+
+ -
-
- ); + {/*
+ +
*/} +
+
+ ); } diff --git a/frontend/src/pages/Admin/ComponentPage/Component.tsx b/frontend/src/pages/Admin/ComponentPage/Component.tsx index 8e902ee..c6eab79 100644 --- a/frontend/src/pages/Admin/ComponentPage/Component.tsx +++ b/frontend/src/pages/Admin/ComponentPage/Component.tsx @@ -3,6 +3,7 @@ import Dropdown from "../../../components/dropdown-select"; import WorkshopCard from "./WorkshopCard"; import SurveyCard from "./SurveyCard"; import CertificateCard from "./CertificateCard"; +import SaveCourseButton from "../../../components/SaveCourseButtons"; // Card Component interface CardProps { @@ -42,41 +43,44 @@ export default function Component({ certificate, }: InclusiveSupportPageProps) { return ( -
- - ): void { - throw new Error("Function not implemented."); - }} - /> - - ): void { - throw new Error("Function not implemented."); - }} - /> - - ): void { - throw new Error("Function not implemented."); - }} - /> +
+
+ + ): void { + throw new Error("Function not implemented."); + }} + /> + + ): void { + throw new Error("Function not implemented."); + }} + /> + + ): void { + throw new Error("Function not implemented."); + }} + /> +
+
); } diff --git a/frontend/src/pages/Admin/ComponentPage/SurveyCard.tsx b/frontend/src/pages/Admin/ComponentPage/SurveyCard.tsx index ff3639f..2a22d19 100644 --- a/frontend/src/pages/Admin/ComponentPage/SurveyCard.tsx +++ b/frontend/src/pages/Admin/ComponentPage/SurveyCard.tsx @@ -124,7 +124,7 @@ export default function SurveyCard({

Survey has {survey.questions.length} questions{" "}

-

Prerequisites

+ {/*

Prerequisites

-
+
*/}
Preview
@@ -171,11 +171,11 @@ export default function SurveyCard({ Edit Component -
+ {/*
-
+
*/}
); diff --git a/frontend/src/pages/Admin/ComponentPage/WorkshopCard.tsx b/frontend/src/pages/Admin/ComponentPage/WorkshopCard.tsx index 62e01b7..0f21809 100644 --- a/frontend/src/pages/Admin/ComponentPage/WorkshopCard.tsx +++ b/frontend/src/pages/Admin/ComponentPage/WorkshopCard.tsx @@ -158,7 +158,7 @@ export default function WorkshopCard({

Live Event {formatWebinarDate(webinar.startTime)}

-

Prerequisites

+ {/*

Prerequisites

-
+ */}
Preview
@@ -211,11 +211,11 @@ export default function WorkshopCard({ Edit Component -
+ {/*
-
+
*/}
); diff --git a/frontend/src/pages/Admin/EditCoursePage/editCoursePage.tsx b/frontend/src/pages/Admin/EditCoursePage/editCoursePage.tsx index ac78370..c248609 100644 --- a/frontend/src/pages/Admin/EditCoursePage/editCoursePage.tsx +++ b/frontend/src/pages/Admin/EditCoursePage/editCoursePage.tsx @@ -227,9 +227,9 @@ const EditCourse = () => {

Details

-
-
-

Catalog

+
+
+

Thumbnail

e.preventDefault()} @@ -283,42 +283,6 @@ const EditCourse = () => { )}
-

Banner Image

-
e.preventDefault()} - onDrop={handleBannerDrop} - > -
- {/* Upload Icon */} - ⬆️ - - {/* Upload Text */} -

- Choose a file or drag & drop it here -

-

- JPEG or PNG format, up to 50MB -

- - {/* File Input */} - - - - {/* Show Selected File */} -
-
{bannerImage && (
@@ -335,22 +299,22 @@ const EditCourse = () => { )}
-
-
+
+

Title

setField("className", e.target.value)} />
-

Summary

-
+

Summary

+