├── Backend ├── config │ ├── cloudinary.js │ └── database.js ├── controllers │ ├── Auth.js │ ├── Category.js │ ├── Course.js │ ├── CourseProgress.js │ ├── Payment.js │ ├── Profile.js │ ├── RatingAndReview.js │ ├── Section.js │ ├── SubSection.js │ └── User.js ├── middleware │ └── verifyJWT.js ├── models │ ├── Course.js │ ├── CourseProgress.js │ ├── Profile.js │ ├── RatingAndReview.js │ ├── Section.js │ ├── SubSection.js │ └── User.js ├── nodemon.config.json ├── package-lock.json ├── package.json ├── routes │ ├── Auth.js │ ├── Course.js │ ├── CourseProgress.js │ ├── Instructor.js │ ├── Payment.js │ ├── Profile.js │ ├── RatingAndReview.js │ └── User.js ├── server.js └── utils │ └── fileUploader.js ├── Frontend ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.jsx │ ├── assets │ │ ├── images │ │ │ ├── 567828_67d0.jpg │ │ │ ├── 851712_fc61_6.jpg │ │ │ ├── Udemy-logo.png │ │ │ ├── angela_yu.jpg │ │ │ ├── blankProfile.webp │ │ │ ├── cart.png │ │ │ ├── check.png │ │ │ ├── comp1.svg │ │ │ ├── comp2.svg │ │ │ ├── comp3.svg │ │ │ ├── comp4.svg │ │ │ ├── comp5.svg │ │ │ ├── front-img.jpg │ │ │ ├── get-rewarded.jpg │ │ │ ├── heart.png │ │ │ ├── imagePlaceholder.jpg │ │ │ ├── infinity.png │ │ │ ├── inspire-learners.jpg │ │ │ ├── jpeg1.jpg │ │ │ ├── jpeg2.jpg │ │ │ ├── jpeg3.jpg │ │ │ ├── jpeg4.jpg │ │ │ ├── lady.jpg │ │ │ ├── logo-long.svg │ │ │ ├── logo-udemy-words.svg │ │ │ ├── logo-udemy.svg │ │ │ ├── man.jpg │ │ │ ├── pic1.jpg │ │ │ ├── pic10.jpg │ │ │ ├── pic11.jpg │ │ │ ├── pic13.jpg │ │ │ ├── pic14.jpg │ │ │ ├── pic15.jpg │ │ │ ├── pic16.jpg │ │ │ ├── pic17.jpg │ │ │ ├── pic18.jpg │ │ │ ├── pic19.jpg │ │ │ ├── pic2.jpg │ │ │ ├── pic20.jpg │ │ │ ├── pic21.jpg │ │ │ ├── pic22.jpg │ │ │ ├── pic3.jpg │ │ │ ├── pic4.jpg │ │ │ ├── pic5.jpg │ │ │ ├── pic6.jpg │ │ │ ├── pic7.jpg │ │ │ ├── pic8.jpg │ │ │ ├── pic9.jpg │ │ │ ├── play.png │ │ │ ├── python.jpg │ │ │ ├── pythonImage.jpg │ │ │ ├── rating.png │ │ │ ├── rustlang.png │ │ │ ├── scroll-arrow.png │ │ │ ├── scroll.png │ │ │ ├── search.png │ │ │ ├── star.png │ │ │ ├── teach-your-way.jpg │ │ │ ├── teaching-front.jpg │ │ │ ├── ulogo.png │ │ │ ├── world.png │ │ │ └── worldwide.png │ │ ├── logo-udemy.svg │ │ └── react.svg │ ├── components │ │ ├── NavLayout.jsx │ │ ├── Navbar.jsx │ │ └── Spinner.jsx │ ├── data │ │ ├── categories.js │ │ ├── dashoard-links.js │ │ ├── priceTier.js │ │ └── roles.js │ ├── features │ │ ├── Auth │ │ │ ├── Login │ │ │ │ └── index.jsx │ │ │ ├── PersistLogin.jsx │ │ │ ├── RequireAuth.jsx │ │ │ └── Signup │ │ │ │ └── index.jsx │ │ ├── Common │ │ │ └── Profile │ │ │ │ ├── BasicInformation.jsx │ │ │ │ ├── ProfileLayout.jsx │ │ │ │ └── ProfilePhoto.jsx │ │ ├── Courses │ │ │ ├── Course │ │ │ │ ├── Checkout.jsx │ │ │ │ ├── ClaimCertificate.jsx │ │ │ │ ├── CourseProgress.jsx │ │ │ │ ├── InstructorInfo.jsx │ │ │ │ ├── RatingAndReview.jsx │ │ │ │ ├── VideoPlayer.jsx │ │ │ │ └── index.jsx │ │ │ ├── Home │ │ │ │ ├── CourseCard.jsx │ │ │ │ ├── Section.jsx │ │ │ │ └── index.jsx │ │ │ ├── SearchCourses │ │ │ │ ├── WideCourseCard.jsx │ │ │ │ └── index.jsx │ │ │ ├── Success │ │ │ │ └── index.jsx │ │ │ └── Teaching │ │ │ │ └── index.jsx │ │ ├── Instructor │ │ │ ├── Course │ │ │ │ ├── CourseLayout.jsx │ │ │ │ ├── CreateCourse │ │ │ │ │ └── index.jsx │ │ │ │ ├── Curriculum │ │ │ │ │ ├── AddContent.jsx │ │ │ │ │ ├── ConfirmDeleteModal.jsx │ │ │ │ │ └── index.jsx │ │ │ │ ├── Goals │ │ │ │ │ ├── LearningObjectives.jsx │ │ │ │ │ ├── Prerequisites.jsx │ │ │ │ │ └── index.jsx │ │ │ │ ├── LandingPage │ │ │ │ │ ├── CoursePromo.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── useIsMount.jsx │ │ │ │ └── Sidebar.jsx │ │ │ └── DashBoard │ │ │ │ └── index.jsx │ │ └── Student │ │ │ └── MyCourses │ │ │ ├── CourseProgressCard.jsx │ │ │ └── index.jsx │ ├── hooks │ │ └── useAuth.js │ ├── index.css │ ├── main.jsx │ ├── reducers │ │ ├── api │ │ │ ├── authApi.js │ │ │ ├── baseApi.js │ │ │ ├── courseApi.js │ │ │ ├── courseProgressApi.js │ │ │ ├── paymentApi.js │ │ │ ├── ratingAndReview.js │ │ │ └── userApi.js │ │ └── authSlice.js │ ├── state │ │ └── store.js │ └── utils │ │ └── CourseInfo.js ├── tailwind.config.js └── vite.config.js └── README.md /Backend/config/cloudinary.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require("cloudinary").v2; 2 | 3 | exports.cloudinaryConnect = () => { 4 | try { 5 | cloudinary.config({ 6 | cloud_name: process.env.CLOUD_NAME, 7 | api_key: process.env.CLOUDINARY_API_KEY, 8 | api_secret: process.env.CLOUDINARY_API_SECRET, 9 | }); 10 | } catch (error) { 11 | console.log(error); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /Backend/config/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | require("dotenv").config(); 3 | 4 | exports.connect = () => { 5 | mongoose.connect(process.env.MONGODB_URL, { 6 | useNewUrlParser: true, 7 | useUnifiedTopology:true, 8 | }) 9 | .then(() => console.log("DB Connected Successfully")) 10 | .catch( (error) => { 11 | console.log("DB Connection Failed"); 12 | console.error(error); 13 | process.exit(1); 14 | } ) 15 | }; -------------------------------------------------------------------------------- /Backend/controllers/Auth.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | const jwt = require("jsonwebtoken"); 3 | const User = require("../models/User"); 4 | 5 | const handleLogin = async (req, res) => { 6 | const { email, password } = req.body; 7 | if (!email || !password) 8 | return res.render("login", { 9 | message: "Email and Password Required", 10 | }); 11 | 12 | const foundUser = await User.findOne({ email: email }).exec(); 13 | if (!foundUser) 14 | return res.status(500).json({ 15 | message: "User Not Available", 16 | }); 17 | if (!bcrypt.compare(password, foundUser.password)) 18 | return res.status(401).json({ 19 | message: "Unauthorized", 20 | }); 21 | 22 | try { 23 | const accessToken = jwt.sign( 24 | { 25 | userInfo: { 26 | userId: foundUser._id, 27 | roles: foundUser.roles, 28 | }, 29 | }, 30 | process.env.ACCESS_TOKEN_SECRET, 31 | { expiresIn: "30m" } 32 | ); 33 | const refreshToken = jwt.sign( 34 | { userId: foundUser._id }, 35 | process.env.REFRESH_TOKEN_SECRET, 36 | { expiresIn: "1d" } 37 | ); 38 | 39 | res.cookie("jwt", refreshToken, { 40 | httpOnly: true, 41 | sameSite: "None", 42 | secure: true, 43 | maxAge: 24 * 60 * 60 * 1000, 44 | }); 45 | res.status(200).json({ success: true, accessToken }); 46 | } catch (error) { 47 | console.log(error); 48 | return res.status(500).json({ 49 | message: "Server Side Error Occured", 50 | }); 51 | } 52 | }; 53 | 54 | const handleRegister = async (req, res) => { 55 | const { fullName, email, password, roles } = req.body; 56 | if (!fullName || !email || !password || roles.length == 0) { 57 | return res.status(400).json({ 58 | message: "Required Fields Missing", 59 | }); 60 | } 61 | 62 | const duplicate = await User.findOne({ email: email }).exec(); 63 | 64 | if (duplicate) 65 | return res.status(409).json({ 66 | message: "User Already Exist", 67 | }); 68 | const [firstName, lastName] = fullName.split(" "); 69 | 70 | try { 71 | const hashedPassword = await bcrypt.hash(password, 10); 72 | await User.create({ 73 | email: email, 74 | password: hashedPassword, 75 | firstName: firstName, 76 | lastName: lastName, 77 | roles: roles, 78 | }); 79 | 80 | return res.status(200).json({ 81 | success: true, 82 | message: "Registerd", 83 | }); 84 | } catch (err) { 85 | console.log(err); 86 | return res.status(500).json({ 87 | message: "Internal Error Occured", 88 | }); 89 | } 90 | }; 91 | 92 | const handleRefresh = (req, res) => { 93 | const cookies = req.cookies; 94 | 95 | if (!cookies?.jwt) return res.status(401).json({ message: "Unauthorized" }); 96 | 97 | const refreshToken = cookies.jwt; 98 | 99 | jwt.verify( 100 | refreshToken, 101 | process.env.REFRESH_TOKEN_SECRET, 102 | async (err, decoded) => { 103 | if (err) return res.status(403).json({ message: "Forbidden" }); 104 | 105 | const foundUser = await User.findById(decoded.userId).exec(); 106 | 107 | if (!foundUser) return res.status(401).json({ message: "Unauthorized" }); 108 | 109 | const accessToken = jwt.sign( 110 | { 111 | userInfo: { 112 | userId: foundUser._id, 113 | roles: foundUser.roles, 114 | }, 115 | }, 116 | process.env.ACCESS_TOKEN_SECRET, 117 | { expiresIn: "15m" } 118 | ); 119 | 120 | res.json({ success: true, accessToken }); 121 | } 122 | ); 123 | }; 124 | 125 | const handleLogout = (req, res) => { 126 | const cookies = req.cookies; 127 | if (!cookies?.jwt) return res.status(204); //No content 128 | res.clearCookie("jwt", { httpOnly: true, sameSite: "None", secure: true }); 129 | res.json({ message: "Cookie cleared" }); 130 | }; 131 | 132 | const handleAddInstructor = async (req, res) => { 133 | try { 134 | const isUser = await User.findById(req.userInfo.userId); 135 | if (!isUser) { 136 | return res.status(404).json({ 137 | success: false, 138 | message: "User not found", 139 | }); 140 | } 141 | if (isUser.roles.includes("Instructor")) { 142 | return res.status(409).json({ 143 | success: true, 144 | message: "User Already has Instructor role", 145 | }); 146 | } 147 | const updatedUser = await User.findByIdAndUpdate( 148 | { 149 | _id: isUser._id, 150 | }, 151 | { 152 | $push: { 153 | roles: "Instructor", 154 | }, 155 | }, 156 | { new: true } 157 | ); 158 | const accessToken = jwt.sign( 159 | { 160 | userInfo: { 161 | userId: updatedUser._id, 162 | roles: updatedUser.roles, 163 | }, 164 | }, 165 | process.env.ACCESS_TOKEN_SECRET, 166 | { expiresIn: "15m" } 167 | ); 168 | 169 | res.json({ success: true, accessToken }); 170 | } catch (error) { 171 | console.log(error); 172 | return res.status(500).json({ 173 | success: false, 174 | message: "Internal Error Occured", 175 | error: error.message, 176 | }); 177 | } 178 | }; 179 | 180 | module.exports = { 181 | handleLogin, 182 | handleRegister, 183 | handleRefresh, 184 | handleLogout, 185 | handleAddInstructor, 186 | }; 187 | -------------------------------------------------------------------------------- /Backend/controllers/Category.js: -------------------------------------------------------------------------------- 1 | const Category = require("../models/Category"); 2 | 3 | exports.createCategory = async (req, res) => { 4 | try { 5 | const { name, description } = req.body; 6 | if (!name) { 7 | return res 8 | .status(400) 9 | .json({ success: false, message: "All fields are required" }); 10 | } 11 | const CategorysDetails = await Category.create({ 12 | name: name, 13 | description: description, 14 | }); 15 | console.log(CategorysDetails); 16 | return res.status(200).json({ 17 | success: true, 18 | message: "Categorys Created Successfully", 19 | }); 20 | } catch (error) { 21 | return res.status(500).json({ 22 | success: true, 23 | message: error.message, 24 | }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Backend/controllers/CourseProgress.js: -------------------------------------------------------------------------------- 1 | const SubSection = require("../models/SubSection"); 2 | const CourseProgress = require("../models/CourseProgress"); 3 | const Course = require("../models/Course"); 4 | exports.updateCourseProgress = async (req, res) => { 5 | const { courseId, subSectionId } = req.body; 6 | const userId = req.userInfo.userId; 7 | try { 8 | const subSection = await SubSection.findById(subSectionId); 9 | if (!subSection) { 10 | return res.status(404).json({ 11 | success: false, 12 | message: "SubSection Not Found", 13 | }); 14 | } 15 | const courseProgress = await CourseProgress.findOne({ 16 | courseId: courseId, 17 | userId: userId, 18 | }); 19 | 20 | if (!courseProgress) { 21 | return res.status(404).json({ 22 | success: false, 23 | message: "Course Progress Not Found", 24 | }); 25 | } 26 | 27 | if (courseProgress.completedVideos.includes(subSectionId)) { 28 | return res.status(400).json({ 29 | success: false, 30 | message: "Already Completed", 31 | }); 32 | } 33 | 34 | courseProgress.completedVideos.push(subSectionId); 35 | await courseProgress.save(); 36 | 37 | return res.status(200).json({ 38 | success: true, 39 | message: "SubSection Updated Successfully", 40 | }); 41 | } catch (error) { 42 | console.log(error); 43 | return res.status(500).json({ 44 | success: false, 45 | message: "Internal Error Occured", 46 | }); 47 | } 48 | }; 49 | 50 | exports.getUserCourseProgress = async (req, res) => { 51 | try { 52 | const { courseId } = req.params; 53 | const userProgress = await CourseProgress.findOne({ 54 | courseId: courseId, 55 | userId: req.userInfo.userId, 56 | }).populate("userId"); 57 | 58 | return res.json({ 59 | userProgress, 60 | }); 61 | } catch (error) { 62 | console.log(error); 63 | return res.status(500).json({ 64 | message: "Internal Error Occured", 65 | }); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /Backend/controllers/Payment.js: -------------------------------------------------------------------------------- 1 | const Course = require("../models/Course"); 2 | const CourseProgress = require("../models/CourseProgress"); 3 | const User = require("../models/User"); 4 | 5 | const stripe = require("stripe")(process.env.STRIPE_KEY); 6 | 7 | exports.createSession = async (req, res) => { 8 | const { courseId } = req.body; 9 | const courseDetails = await Course.findById(courseId); 10 | if (!courseDetails) { 11 | return res.status(400).json({ 12 | success: false, 13 | message: "Course Not Found", 14 | }); 15 | } 16 | 17 | const session = await stripe.checkout.sessions.create({ 18 | payment_method_types: ["card"], 19 | line_items: [ 20 | { 21 | price_data: { 22 | currency: "inr", 23 | product_data: { 24 | name: courseDetails.courseName, 25 | images: [courseDetails.previewImage], 26 | }, 27 | unit_amount: courseDetails.price * 100, 28 | }, 29 | quantity: 1, 30 | }, 31 | ], 32 | mode: "payment", 33 | metadata: { 34 | userId: req.userInfo.userId, 35 | courseId: courseId, 36 | }, 37 | success_url: "http://localhost:5173/success/{CHECKOUT_SESSION_ID}", 38 | cancel_url: `http://localhost:5173/course/${courseId}`, 39 | }); 40 | return res.json({ success: true, id: session.id }); 41 | }; 42 | 43 | exports.enrollStudent = async (req, res) => { 44 | try { 45 | const session = await stripe.checkout.sessions.retrieve( 46 | req.params.sessionId 47 | ); 48 | if (!session) { 49 | return res.status(400).json({ 50 | success: false, 51 | message: "An Error Occured", 52 | }); 53 | } 54 | const userId = session.metadata.userId; 55 | const courseId = session.metadata.courseId; 56 | 57 | const alreddyEnrolled = await Course.findOne({ _id: courseId }); 58 | if (alreddyEnrolled.studentsEnrolled.includes(userId)) { 59 | return res.status(409).json({ 60 | success: false, 61 | message: "Already Enrolled", 62 | }); 63 | } 64 | 65 | const enrolledCourse = await Course.findByIdAndUpdate( 66 | courseId, 67 | { 68 | $push: { studentsEnrolled: userId }, 69 | }, 70 | { new: true } 71 | ); 72 | if (!enrolledCourse) { 73 | return res.status(400).json({ 74 | success: false, 75 | message: "Course Not Found", 76 | }); 77 | } 78 | const courseProgress = await CourseProgress.create({ 79 | courseId: courseId, 80 | userId: userId, 81 | completedVideos: [], 82 | }); 83 | 84 | const enrolledStudent = await User.findByIdAndUpdate( 85 | userId, 86 | { 87 | $push: { 88 | enrolledCourses: courseId, 89 | courseProgress: courseProgress._id, 90 | }, 91 | }, 92 | { new: true } 93 | ); 94 | return res.status(200).json({ 95 | success: true, 96 | message: "Student Enrolled Successfully", 97 | }); 98 | } catch (error) { 99 | console.log(error); 100 | return res.status(500).json({ 101 | success: false, 102 | message: "Internal Error Occured", 103 | }); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /Backend/controllers/Profile.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const { uploadFileToCloudinary } = require("../utils/fileUploader"); 3 | exports.editProfile = async (req, res) => { 4 | try { 5 | const { firstName, lastName, headline, biography } = req.body; 6 | const user = await User.findById(req.userInfo.userId); 7 | if (!user) { 8 | return res.status(404).json({ 9 | success: false, 10 | message: "User Not Fund", 11 | }); 12 | } 13 | if (firstName && lastName && headline && biography) { 14 | user.firstName = firstName; 15 | user.lastName = lastName; 16 | user.headline = headline; 17 | user.biography = biography; 18 | } 19 | 20 | if (req.files?.profilePicture) { 21 | const uploadDetails = await uploadFileToCloudinary( 22 | req.files.profilePicture, 23 | process.env.PROFILE_FOLDER_NAME 24 | ); 25 | user.profilePicture = uploadDetails.secure_url; 26 | } 27 | await user.save(); 28 | return res.status(200).json({ 29 | success: true, 30 | message: "Profile Updated Successfully", 31 | }); 32 | } catch (error) { 33 | console.log(error); 34 | return res.status(500).json({ 35 | success: false, 36 | message: "Internal Error Occured", 37 | error: error.message, 38 | }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /Backend/controllers/RatingAndReview.js: -------------------------------------------------------------------------------- 1 | const Course = require("../models/Course"); 2 | const RatingAndReview = require("../models/RatingAndReview"); 3 | 4 | exports.createRatingAndReview = async (req, res) => { 5 | try { 6 | const userId = req.userInfo.userId; 7 | const { rating, review, courseId } = req.body; 8 | const courseDetails = await Course.findOne({ 9 | _id: courseId, 10 | studentsEnrolled: { $elemMatch: { $eq: userId } }, 11 | }); 12 | if (!courseDetails) { 13 | return res.status(404).json({ 14 | success: false, 15 | message: "Student is not enrolled for this course", 16 | }); 17 | } 18 | 19 | const alreadyReviewed = await RatingAndReview.findOne({ 20 | user: userId, 21 | course: courseId, 22 | }); 23 | 24 | if (alreadyReviewed) { 25 | return res.status(409).json({ 26 | success: false, 27 | message: "You have already reviewed", 28 | }); 29 | } 30 | 31 | const ratingAndReview = await RatingAndReview.create({ 32 | rating, 33 | review, 34 | user: userId, 35 | course: courseId, 36 | }); 37 | 38 | const updateCourseDetails = await Course.findByIdAndUpdate( 39 | courseId, 40 | { 41 | $push: { ratingAndReviews: ratingAndReview._id }, 42 | }, 43 | { new: true } 44 | ); 45 | 46 | return res.status(200).json({ 47 | success: true, 48 | message: "Rating and Review added Successfully", 49 | }); 50 | } catch (error) { 51 | console.log(error); 52 | return res.status(500).json({ 53 | success: false, 54 | message: "Internal Error Occured", 55 | }); 56 | } 57 | }; 58 | 59 | exports.editRatingAndReview = async (req, res) => { 60 | try { 61 | const userId = req.userInfo.userId; 62 | const { rating, review, courseId } = req.body; 63 | const courseDetails = await Course.findOne({ 64 | _id: courseId, 65 | studentsEnrolled: { $elemMatch: { $eq: userId } }, 66 | }); 67 | if (!courseDetails) { 68 | return res.status(404).json({ 69 | success: false, 70 | message: "Student is not enrolled for this course", 71 | }); 72 | } 73 | 74 | const ratingAndReview = await RatingAndReview.findOneAndUpdate( 75 | { course: courseId, user: userId }, 76 | { rating, review } 77 | ); 78 | 79 | return res.status(200).json({ 80 | success: true, 81 | message: "Rating and Review Updated Successfully", 82 | }); 83 | } catch (error) { 84 | console.log(error); 85 | return res.status(500).json({ 86 | success: false, 87 | message: "Internal Error Occured", 88 | }); 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /Backend/controllers/Section.js: -------------------------------------------------------------------------------- 1 | const Section = require("../models/Section"); 2 | const Course = require("../models/Course"); 3 | const SubSection = require("../models/SubSection"); 4 | 5 | exports.getAllSections = async (req, res) => { 6 | const sections = await Section.find().populate("subSection").exec(); 7 | return res.json({ 8 | success: true, 9 | data: sections, 10 | message: "Success", 11 | }); 12 | }; 13 | 14 | exports.createSection = async (req, res) => { 15 | try { 16 | const { sectionName, courseId } = req.body; 17 | 18 | if (!sectionName || !courseId) { 19 | return res.status(400).json({ 20 | success: false, 21 | message: "Missing required fields", 22 | }); 23 | } 24 | 25 | const newSection = await Section.create({ sectionName }); 26 | 27 | const updatedCourse = await Course.findByIdAndUpdate( 28 | courseId, 29 | { 30 | $push: { 31 | courseContent: newSection._id, 32 | }, 33 | }, 34 | { new: true } 35 | ); 36 | 37 | res.status(200).json({ 38 | success: true, 39 | message: "Section created successfully", 40 | }); 41 | } catch (error) { 42 | res.status(500).json({ 43 | success: false, 44 | message: "Internal server error", 45 | error: error.message, 46 | }); 47 | } 48 | }; 49 | 50 | exports.updateSection = async (req, res) => { 51 | try { 52 | const { sectionName, sectionId } = req.body; 53 | const section = await Section.findByIdAndUpdate( 54 | sectionId, 55 | { 56 | sectionName, 57 | }, 58 | { new: true } 59 | ); 60 | 61 | res.status(200).json({ 62 | success: true, 63 | message: "Section Updated SUCCESSFULLY!", 64 | }); 65 | } catch (error) { 66 | console.error("Error updating section:", error); 67 | res.status(500).json({ 68 | success: false, 69 | message: "Internal server error", 70 | }); 71 | } 72 | }; 73 | 74 | exports.deleteSection = async (req, res) => { 75 | try { 76 | console.log(req.body); 77 | const { deleteSectionId, courseId } = req.body; 78 | const sectionId = deleteSectionId; 79 | await Course.findByIdAndUpdate(courseId, { 80 | $pull: { 81 | courseContent: sectionId, 82 | }, 83 | }); 84 | const section = await Section.findById(sectionId); 85 | if (!section) { 86 | return res.status(404).json({ 87 | success: false, 88 | message: "Section not Found", 89 | }); 90 | } 91 | 92 | await SubSection.deleteMany({ _id: { $in: section.subSection } }); 93 | 94 | await Section.findByIdAndDelete(sectionId); 95 | 96 | const course = await Course.findById(courseId) 97 | .populate({ 98 | path: "courseContent", 99 | populate: { 100 | path: "subSection", 101 | }, 102 | }) 103 | .exec(); 104 | 105 | res.status(200).json({ 106 | success: true, 107 | message: "Section deleted", 108 | data: course, 109 | }); 110 | } catch (error) { 111 | console.error("Error deleting section:", error); 112 | res.status(500).json({ 113 | success: false, 114 | message: "Internal server error", 115 | }); 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /Backend/controllers/SubSection.js: -------------------------------------------------------------------------------- 1 | const SubSection = require("../models/SubSection"); 2 | const Section = require("../models/Section"); 3 | const { uploadFileToCloudinary } = require("../utils/fileUploader"); 4 | 5 | exports.createSubSection = async (req, res) => { 6 | try { 7 | const { sectionId, subSectionName } = req.body; 8 | console.log(sectionId, subSectionName); 9 | 10 | if (!sectionId || !subSectionName) { 11 | return res.status(400).json({ 12 | success: false, 13 | message: "all fields are REQUIRED !!", 14 | }); 15 | } 16 | 17 | const subSectionDetails = await SubSection.create({ 18 | subSectionName, 19 | }); 20 | 21 | const updatedSection = await Section.findByIdAndUpdate( 22 | { _id: sectionId }, 23 | { 24 | $push: { 25 | subSection: subSectionDetails._id, 26 | }, 27 | }, 28 | { 29 | new: true, 30 | } 31 | ).populate("subSection"); 32 | 33 | //sending.. final response 34 | return res.status(200).json({ 35 | success: true, 36 | message: "subSection created SUCCESSFULLY !", 37 | data: updatedSection, 38 | }); 39 | } catch (error) { 40 | return res.status(500).json({ 41 | success: false, 42 | message: "an error ocurred while creating sub-Section!", 43 | error: error.message, 44 | }); 45 | } 46 | }; 47 | 48 | exports.updateSubSection = async (req, res) => { 49 | try { 50 | const { subSectionId, subSectionName } = req.body; 51 | const subSection = await SubSection.findById(subSectionId); 52 | 53 | if (!subSection) { 54 | return res.status(404).json({ 55 | success: false, 56 | message: "SubSection not found", 57 | }); 58 | } 59 | 60 | if (subSectionName !== undefined) { 61 | subSection.subSectionName = subSectionName; 62 | } 63 | 64 | await subSection.save(); 65 | 66 | return res.json({ 67 | success: true, 68 | message: "Section updated successfully", 69 | }); 70 | } catch (error) { 71 | console.error(error); 72 | return res.status(500).json({ 73 | success: false, 74 | message: "An error occurred while updating the section", 75 | }); 76 | } 77 | }; 78 | 79 | exports.deleteSubSection = async (req, res) => { 80 | try { 81 | const { subSectionId, sectionId } = req.body; 82 | await Section.findByIdAndUpdate( 83 | { _id: sectionId }, 84 | { 85 | $pull: { 86 | subSection: subSectionId, 87 | }, 88 | } 89 | ); 90 | const subSection = await SubSection.findByIdAndDelete({ 91 | _id: subSectionId, 92 | }); 93 | 94 | if (!subSection) { 95 | return res 96 | .status(404) 97 | .json({ success: false, message: "SubSection not found" }); 98 | } 99 | 100 | const updatedSection = await Section.findById(sectionId).populate( 101 | "subSection" 102 | ); 103 | 104 | return res.json({ 105 | success: true, 106 | data: updatedSection, 107 | message: "SubSection deleted successfully", 108 | }); 109 | } catch (error) { 110 | console.error(error); 111 | return res.status(500).json({ 112 | success: false, 113 | message: "An error occurred while deleting the SubSection", 114 | }); 115 | } 116 | }; 117 | 118 | exports.addSubSectionContent = async (req, res) => { 119 | try { 120 | const { subSectionId } = req.body; 121 | const subSection = await SubSection.findById(subSectionId); 122 | if (!subSectionId) { 123 | return res.status(400).json({ 124 | success: false, 125 | message: "All fields required", 126 | }); 127 | } 128 | if (!subSection) { 129 | return res.status(404).json({ 130 | success: false, 131 | message: "Fields not found", 132 | }); 133 | } 134 | if (!req.files || req.files.videoFile == undefined) { 135 | return res.status(404).json({ 136 | success: false, 137 | message: "Video Not Found", 138 | }); 139 | } 140 | const uploadDetails = await uploadFileToCloudinary( 141 | req.files.videoFile, 142 | process.env.FOLDER_NAME 143 | ); 144 | await SubSection.findByIdAndUpdate( 145 | subSectionId, 146 | { 147 | videoUrl: uploadDetails.secure_url, 148 | timeDuration: uploadDetails.duration, 149 | videoDate: uploadDetails.created_at, 150 | videoName: req.files.videoFile.name, 151 | }, 152 | { new: true } 153 | ); 154 | return res.json({ 155 | success: true, 156 | message: "Video Uploaded successfully", 157 | }); 158 | } catch (error) { 159 | console.error(error); 160 | return res.status(500).json({ 161 | success: false, 162 | message: "An error occurred while uploding video", 163 | }); 164 | } 165 | }; 166 | -------------------------------------------------------------------------------- /Backend/controllers/User.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | 3 | exports.getInstructorDetails = async (req, res) => { 4 | try { 5 | const instructorDetails = await User.findById(req.userInfo.userId) 6 | .populate("courses") 7 | .select("-password"); 8 | if (!instructorDetails) { 9 | return res.status(404).json({ 10 | success: false, 11 | message: "Instructor Not Found", 12 | }); 13 | } 14 | return res.status(200).json({ 15 | success: true, 16 | data: instructorDetails, 17 | }); 18 | } catch (error) { 19 | return res.status(500).json({ 20 | success: false, 21 | message: "Internal Error Occured", 22 | error: error.message, 23 | }); 24 | } 25 | }; 26 | 27 | exports.getUserDetails = async (req, res) => { 28 | try { 29 | const userDetails = await User.findById(req.userInfo.userId).populate({ 30 | path: "courseProgress", 31 | populate: { 32 | path: "courseId", 33 | populate: { 34 | path: "courseContent", 35 | populate: { 36 | path: "subSection", 37 | }, 38 | }, 39 | }, 40 | }); 41 | return res.status(200).json({ 42 | success: true, 43 | data: userDetails, 44 | }); 45 | } catch (error) { 46 | console.log(error); 47 | return res.status(500).json({ 48 | success: false, 49 | message: "Internal Error Occured", 50 | error: error.message, 51 | }); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /Backend/middleware/verifyJWT.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | exports.verifyJWT = (req, res, next) => { 4 | const authHeader = req.headers.authorization || req.headers.Authorization; 5 | 6 | if (!authHeader?.startsWith("Bearer ")) { 7 | return res.status(401).json({ message: "Unauthorized" }); 8 | } 9 | 10 | const token = authHeader.split(" ")[1]; 11 | 12 | jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, decoded) => { 13 | if (err) { 14 | console.log(err); 15 | return res.status(403).json({ message: "Forbidden" }); 16 | } 17 | req.userInfo = decoded.userInfo; 18 | next(); 19 | }); 20 | }; 21 | 22 | exports.isStudent = (req, res, next) => { 23 | try { 24 | if (!req.userInfo.roles.includes("Student")) { 25 | return res.status(401).json({ 26 | success: false, 27 | message: "Protected route for students only", 28 | }); 29 | } 30 | next(); 31 | } catch (error) { 32 | return res.status(500).json({ 33 | success: false, 34 | message: "Internal error occured", 35 | }); 36 | } 37 | }; 38 | 39 | exports.isInstructor = (req, res, next) => { 40 | try { 41 | if (!req.userInfo.roles.includes("Instructor")) { 42 | return res.status(401).json({ 43 | success: false, 44 | message: "Protected route for instructor only", 45 | }); 46 | } 47 | next(); 48 | } catch (error) { 49 | console.log(error); 50 | return res.status(500).json({ 51 | success: false, 52 | message: "Internal error occured", 53 | }); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /Backend/models/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const courseSchema = new mongoose.Schema( 4 | { 5 | courseName: { 6 | type: String, 7 | }, 8 | courseSubtitle: { 9 | type: String, 10 | }, 11 | 12 | description: { 13 | type: String, 14 | }, 15 | 16 | locale: { 17 | type: String, 18 | }, 19 | instructionalLevel: { 20 | type: String, 21 | }, 22 | 23 | category: { 24 | type: String, 25 | }, 26 | subCategory: { 27 | type: String, 28 | }, 29 | 30 | previewImage: { 31 | type: String, 32 | }, 33 | 34 | promoVideo: { 35 | type: String, 36 | }, 37 | learningObjectives: { 38 | type: [ 39 | { 40 | objective: String, 41 | }, 42 | ], 43 | }, 44 | prerequisites: { 45 | type: [ 46 | { 47 | prerequisite: String, 48 | }, 49 | ], 50 | }, 51 | instructor: { 52 | type: mongoose.Schema.Types.ObjectId, 53 | ref: "User", 54 | required: true, 55 | }, 56 | courseContent: [ 57 | { 58 | type: mongoose.Schema.Types.ObjectId, 59 | ref: "Section", 60 | }, 61 | ], 62 | ratingAndReviews: [ 63 | { 64 | type: mongoose.Schema.Types.ObjectId, 65 | ref: "RatingAndReview", 66 | }, 67 | ], 68 | 69 | studentsEnrolled: [ 70 | { 71 | type: mongoose.Schema.Types.ObjectId, 72 | ref: "User", 73 | }, 74 | ], 75 | price: { 76 | type: String, 77 | }, 78 | status: { 79 | type: String, 80 | enum: ["Draft", "Published"], 81 | }, 82 | }, 83 | { timestamps: true } 84 | ); 85 | 86 | module.exports = mongoose.model("Course", courseSchema); 87 | -------------------------------------------------------------------------------- /Backend/models/CourseProgress.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const courseProgress = new mongoose.Schema({ 4 | courseId: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: "Course", 7 | }, 8 | userId: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "User", 11 | }, 12 | completedVideos: [ 13 | { 14 | type: mongoose.Schema.Types.ObjectId, 15 | ref: "SubSection", 16 | }, 17 | ], 18 | }); 19 | 20 | module.exports = mongoose.model("courseProgress", courseProgress); 21 | -------------------------------------------------------------------------------- /Backend/models/Profile.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const profileSchema = new mongoose.Schema({ 4 | firstName: { 5 | type: String, 6 | }, 7 | lastName: { 8 | type: String, 9 | }, 10 | headline: { 11 | type: String, 12 | }, 13 | biography: { 14 | type: String, 15 | }, 16 | profilePicture: { 17 | type: String, 18 | }, 19 | }); 20 | 21 | module.exports = mongoose.model("Profile", profileSchema); 22 | -------------------------------------------------------------------------------- /Backend/models/RatingAndReview.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const ratingAndReviewSchema = new mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: "User", 8 | }, 9 | rating: { 10 | type: Number, 11 | }, 12 | review: { 13 | type: String, 14 | }, 15 | course: { 16 | type: mongoose.Schema.Types.ObjectId, 17 | ref: "Course", 18 | }, 19 | }, 20 | { timestamps: true } 21 | ); 22 | 23 | module.exports = mongoose.model("RatingAndReview", ratingAndReviewSchema); 24 | -------------------------------------------------------------------------------- /Backend/models/Section.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const sectionSchema = new mongoose.Schema({ 4 | sectionName: { 5 | type: String, 6 | }, 7 | subSection: [ 8 | { 9 | type: mongoose.Schema.Types.ObjectId, 10 | required: true, 11 | ref: "SubSection", 12 | }, 13 | ], 14 | }); 15 | 16 | module.exports = mongoose.model("Section", sectionSchema); 17 | -------------------------------------------------------------------------------- /Backend/models/SubSection.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const subSectionSchema = new mongoose.Schema({ 4 | subSectionName: { 5 | type: String, 6 | }, 7 | timeDuration: { 8 | type: String, 9 | }, 10 | videoName: { 11 | type: String, 12 | }, 13 | videoUrl: { 14 | type: String, 15 | }, 16 | videoDate: { 17 | type: String, 18 | }, 19 | }); 20 | 21 | module.exports = mongoose.model("SubSection", subSectionSchema); 22 | -------------------------------------------------------------------------------- /Backend/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | firstName: { 5 | type: String, 6 | }, 7 | lastName: { 8 | type: String, 9 | }, 10 | headline: { 11 | type: String, 12 | }, 13 | biography: { 14 | type: String, 15 | }, 16 | profilePicture: { 17 | type: String, 18 | }, 19 | email: { type: String, required: true }, 20 | password: { type: String, required: true }, 21 | courses: [ 22 | /*Created Courses*/ 23 | { 24 | type: mongoose.Schema.Types.ObjectId, 25 | ref: "Course", 26 | }, 27 | ], 28 | roles: { 29 | type: [String], 30 | }, 31 | enrolledCourses: { 32 | type: mongoose.Schema.Types.ObjectId, 33 | ref: "Course", 34 | }, 35 | courseProgress: [ 36 | { 37 | type: mongoose.Schema.Types.ObjectId, 38 | ref: "courseProgress", 39 | }, 40 | ], 41 | }); 42 | 43 | module.exports = mongoose.model("User", userSchema); 44 | -------------------------------------------------------------------------------- /Backend/nodemon.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "node_modules" 4 | ] 5 | } -------------------------------------------------------------------------------- /Backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon server.js --config nodemon.config.json", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcrypt": "^5.1.1", 15 | "cloudinary": "^1.41.0", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.3.1", 19 | "ejs": "^3.1.9", 20 | "express": "^4.18.2", 21 | "express-fileupload": "^1.4.3", 22 | "jsonwebtoken": "^9.0.2", 23 | "mongoose": "^8.0.0", 24 | "stripe": "^14.9.0" 25 | }, 26 | "devDependencies": { 27 | "nodemon": "^3.0.1" 28 | }, 29 | "proxy": "http://localhost:5173" 30 | } 31 | -------------------------------------------------------------------------------- /Backend/routes/Auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const { 4 | handleLogin, 5 | handleRegister, 6 | handleRefresh, 7 | handleLogout, 8 | handleAddInstructor, 9 | } = require("../controllers/Auth.js"); 10 | const { verifyJWT } = require("../middleware/verifyJWT.js"); 11 | 12 | router.post("/login", handleLogin); 13 | 14 | router.post("/signup", handleRegister); 15 | 16 | router.get("/refresh", handleRefresh); 17 | 18 | router.post("/logout", handleLogout); 19 | 20 | //Add Instructor Role to User 21 | router.get("/addInstructor", verifyJWT, handleAddInstructor); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /Backend/routes/Course.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | const { 5 | createCourse, 6 | handleEditGoals, 7 | handleEditBasics, 8 | getInstructorCourseDetails, 9 | getCategoryCourses, 10 | getFullCourseDetails, 11 | getSearchCourses, 12 | publishCourse, 13 | getStudentDetails, 14 | } = require("../controllers/Course"); 15 | 16 | const { 17 | getAllSections, 18 | createSection, 19 | updateSection, 20 | deleteSection, 21 | } = require("../controllers/Section"); 22 | 23 | const { 24 | createSubSection, 25 | addSubSectionContent, 26 | updateSubSection, 27 | deleteSubSection, 28 | } = require("../controllers/SubSection"); 29 | 30 | const { verifyJWT, isInstructor } = require("../middleware/verifyJWT"); 31 | 32 | //----------------------------Instructor Course -------------------------------------- 33 | router.post("/createCourse", verifyJWT, isInstructor, createCourse); 34 | router.post("/publishCourse", verifyJWT, isInstructor, publishCourse); 35 | router.get("/getCourseDetails/:courseId", getInstructorCourseDetails); 36 | 37 | //------------------------------- Section ---------------------------------------------- 38 | router.get("/getsections", getAllSections); 39 | router.post("/createSection", verifyJWT, isInstructor, createSection); 40 | router.post("/updateSection", verifyJWT, isInstructor, updateSection); 41 | router.post("/deleteSection", verifyJWT, isInstructor, deleteSection); 42 | 43 | router.post("/editCourseGoals", verifyJWT, isInstructor, handleEditGoals); 44 | router.post("/editCourseBasics", verifyJWT, isInstructor, handleEditBasics); 45 | 46 | // ----------------------------- SubSection ------------------------------------- 47 | router.post("/createSubSection", verifyJWT, isInstructor, createSubSection); 48 | router.post("/updateSubSection", verifyJWT, isInstructor, updateSubSection); 49 | router.post("/deleteSubSection", verifyJWT, isInstructor, deleteSubSection); 50 | router.post( 51 | "/addSubSectionContent", 52 | verifyJWT, 53 | isInstructor, 54 | addSubSectionContent 55 | ); 56 | 57 | //----------------------------- Course ---------------------------------------- 58 | router.get("/getCategoryCourses/:category", getCategoryCourses); 59 | router.get("/getFullCourseDetails/:courseId", verifyJWT, getFullCourseDetails); 60 | router.get("/student", verifyJWT, getStudentDetails); 61 | router.get("/search/:query", getSearchCourses); 62 | 63 | module.exports = router; 64 | -------------------------------------------------------------------------------- /Backend/routes/CourseProgress.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | getUserCourseProgress, 4 | updateCourseProgress, 5 | } = require("../controllers/CourseProgress"); 6 | const { verifyJWT } = require("../middleware/verifyJWT"); 7 | const router = express.Router(); 8 | 9 | router.get( 10 | "/getUserCourseProgress/:courseId", 11 | verifyJWT, 12 | getUserCourseProgress 13 | ); 14 | router.post("/updateCourseProgress", verifyJWT, updateCourseProgress); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /Backend/routes/Instructor.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { verifyJWT, isInstructor } = require("../middleware/verifyJWT"); 3 | const { getInstructorDetails } = require("../controllers/User"); 4 | const router = express.Router(); 5 | 6 | router.get("/", verifyJWT, isInstructor, getInstructorDetails); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /Backend/routes/Payment.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { createSession, enrollStudent } = require("../controllers/Payment"); 3 | const { verifyJWT } = require("../middleware/verifyJWT"); 4 | const router = express.Router(); 5 | 6 | router.post("/createSession", verifyJWT, createSession); 7 | router.get("/completePayment/:sessionId", verifyJWT, enrollStudent); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /Backend/routes/Profile.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const { editProfile } = require("../controllers/Profile"); 4 | const { verifyJWT } = require("../middleware/verifyJWT"); 5 | 6 | router.post("/edit", verifyJWT, editProfile); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /Backend/routes/RatingAndReview.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | createRatingAndReview, 4 | editRatingAndReview, 5 | } = require("../controllers/RatingAndReview"); 6 | const { verifyJWT } = require("../middleware/verifyJWT"); 7 | 8 | const router = express.Router(); 9 | 10 | router.post("/create", verifyJWT, createRatingAndReview); 11 | router.post("/edit", verifyJWT, editRatingAndReview); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /Backend/routes/User.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { getUserDetails } = require("../controllers/User"); 3 | const { verifyJWT } = require("../middleware/verifyJWT"); 4 | const router = express.Router(); 5 | 6 | router.get("/getUserDetails", verifyJWT, getUserDetails); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /Backend/server.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | dotenv.config(); 3 | const express = require("express"); 4 | const app = express(); 5 | 6 | const authRoutes = require("./routes/Auth"); 7 | const profileRoutes = require("./routes/Profile"); 8 | const userRoutes = require("./routes/User"); 9 | const instructorRoutes = require("./routes/Instructor"); 10 | const paymentRoutes = require("./routes/Payment"); 11 | const ratingAndReviewRoutes = require("./routes/RatingAndReview"); 12 | const courseProgressRoutes = require("./routes/CourseProgress"); 13 | const courseRoutes = require("./routes/Course"); 14 | const database = require("./config/database"); 15 | const cookieParser = require("cookie-parser"); 16 | const cors = require("cors"); 17 | const { cloudinaryConnect } = require("./config/cloudinary"); 18 | fileUpload = require("express-fileupload"); 19 | 20 | const PORT = process.env.PORT || 4000; 21 | 22 | //database connect 23 | database.connect(); 24 | //middlewares 25 | app.use(express.json()); 26 | app.use(cookieParser()); 27 | app.use( 28 | cors({ 29 | origin: ["http://localhost:5173"], 30 | credentials: true, 31 | }) 32 | ); 33 | 34 | app.use( 35 | fileUpload({ 36 | useTempFiles: true, 37 | tempFileDir: "/tmp", 38 | }) 39 | ); 40 | //cloudinary connection 41 | cloudinaryConnect(); 42 | 43 | //routes 44 | app.use("/api/v1/auth", authRoutes); 45 | app.use("/api/v1/profile", profileRoutes); 46 | app.use("/api/v1/course", courseRoutes); 47 | app.use("/api/v1/user", userRoutes); 48 | app.use("/api/v1/instructor", instructorRoutes); 49 | app.use("/api/v1/payment", paymentRoutes); 50 | app.use("/api/v1/ratingAndReview", ratingAndReviewRoutes); 51 | app.use("/api/v1/courseProgress", courseProgressRoutes); 52 | 53 | app.get("/", (req, res) => { 54 | return res.json({ 55 | success: true, 56 | message: "Your server is up and running....", 57 | }); 58 | }); 59 | 60 | app.listen(PORT, () => { 61 | console.log(`App is running at ${PORT}`); 62 | }); 63 | -------------------------------------------------------------------------------- /Backend/utils/fileUploader.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require("cloudinary").v2; 2 | 3 | exports.uploadFileToCloudinary = async (file, folder, height, quality) => { 4 | const options = { folder }; 5 | if (height) { 6 | options.height = height; 7 | } 8 | if (quality) { 9 | options.quality = quality; 10 | } 11 | options.resource_type = "auto"; 12 | 13 | return await cloudinary.uploader.upload(file.tempFilePath, options); 14 | }; 15 | -------------------------------------------------------------------------------- /Frontend/README.md: -------------------------------------------------------------------------------- 1 | ## Learning Management System (Udemy Clone) 2 | -------------------------------------------------------------------------------- /Frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@fortawesome/fontawesome-svg-core": "^6.4.2", 14 | "@fortawesome/free-solid-svg-icons": "^6.4.2", 15 | "@fortawesome/react-fontawesome": "^0.2.0", 16 | "@react-pdf/renderer": "^3.1.14", 17 | "@reduxjs/toolkit": "^1.9.7", 18 | "@smastrom/react-rating": "^1.4.0", 19 | "@stripe/react-stripe-js": "^2.4.0", 20 | "@stripe/stripe-js": "^2.2.2", 21 | "jwt-decode": "^4.0.0", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-hook-form": "^7.48.2", 25 | "react-icons": "^4.12.0", 26 | "react-loader-spinner": "^6.1.0", 27 | "react-redux": "^8.1.3", 28 | "react-router-dom": "^6.19.0" 29 | }, 30 | "devDependencies": { 31 | "@hookform/devtools": "^4.3.1", 32 | "@types/react": "^18.2.15", 33 | "@types/react-dom": "^18.2.7", 34 | "@vitejs/plugin-react": "^4.0.3", 35 | "autoprefixer": "^10.4.16", 36 | "eslint": "^8.45.0", 37 | "eslint-plugin-react": "^7.32.2", 38 | "eslint-plugin-react-hooks": "^4.6.0", 39 | "eslint-plugin-react-refresh": "^0.4.3", 40 | "postcss": "^8.4.31", 41 | "tailwindcss": "^3.3.5", 42 | "vite": "^4.4.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /Frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Frontend/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/App.css -------------------------------------------------------------------------------- /Frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 2 | import Teaching from "./features/Courses/Teaching"; 3 | import Signup from "./features/Auth/Signup"; 4 | import CreateCourse from "./features/Instructor/Course/CreateCourse"; 5 | import CourseLayout from "./features/Instructor/Course/CourseLayout"; 6 | import CourseGoals from "./features/Instructor/Course/Goals"; 7 | import Dashboard from "./features/Instructor/DashBoard"; 8 | import LandingPage from "./features/Instructor/Course/LandingPage"; 9 | import Curriculum from "./features/Instructor/Course/Curriculum"; 10 | import Home from "./features/Courses/Home"; 11 | import Login from "./features/auth/Login"; 12 | import PersistLogin from "./features/Auth/PersistLogin"; 13 | import RequireAuth from "./features/Auth/RequireAuth"; 14 | import ProfileLayout from "./features/Common/Profile/ProfileLayout"; 15 | import BasicInformation from "./features/Common/Profile/BasicInformation"; 16 | import ProfilePhoto from "./features/Common/Profile/ProfilePhoto"; 17 | import Course from "./features/Courses/Course"; 18 | import Success from "./features/Courses/Success"; 19 | import MyCourses from "./features/Student/MyCourses"; 20 | import { ROLES } from "./data/roles"; 21 | import "@smastrom/react-rating/style.css"; 22 | import "./App.css"; 23 | import SearchCourses from "./features/Courses/SearchCourses"; 24 | import NavLayout from "./components/NavLayout"; 25 | 26 | function App() { 27 | return ( 28 | 29 | 30 | } 33 | /> 34 | } 37 | /> 38 | } /> 39 | }> 40 | }> 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | 46 | 49 | } 50 | > 51 | } /> 52 | } /> 53 | }> 54 | } 57 | /> 58 | } /> 59 | 60 | 61 | 62 | }> 63 | } /> 64 | } /> 65 | }> 66 | } /> 67 | } /> 68 | 69 | }> 70 | } /> 71 | } 74 | /> 75 | } /> 76 | 77 | 78 | 79 | 80 | 81 | ); 82 | } 83 | 84 | export default App; 85 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/567828_67d0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/567828_67d0.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/851712_fc61_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/851712_fc61_6.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/Udemy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/Udemy-logo.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/angela_yu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/angela_yu.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/blankProfile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/blankProfile.webp -------------------------------------------------------------------------------- /Frontend/src/assets/images/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/cart.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/check.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/comp1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/comp2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/comp3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/comp4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/comp5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/front-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/front-img.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/get-rewarded.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/get-rewarded.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/heart.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/imagePlaceholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/imagePlaceholder.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/infinity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/infinity.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/inspire-learners.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/inspire-learners.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/jpeg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/jpeg1.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/jpeg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/jpeg2.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/jpeg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/jpeg3.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/jpeg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/jpeg4.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/lady.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/lady.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/logo-long.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/logo-udemy-words.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/logo-udemy.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Frontend/src/assets/images/man.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/man.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic1.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic10.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic11.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic13.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic14.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic15.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic16.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic17.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic18.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic19.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic2.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic20.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic21.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic22.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic3.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic4.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic5.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic6.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic7.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic8.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pic9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pic9.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/play.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/python.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/python.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/pythonImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/pythonImage.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/rating.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/rustlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/rustlang.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/scroll-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/scroll-arrow.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/scroll.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/search.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/star.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/teach-your-way.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/teach-your-way.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/teaching-front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/teaching-front.jpg -------------------------------------------------------------------------------- /Frontend/src/assets/images/ulogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/ulogo.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/world.png -------------------------------------------------------------------------------- /Frontend/src/assets/images/worldwide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya7812/Learning-Management-System/03de86f910e89f2f32d1f2b21192cad10a0d9575/Frontend/src/assets/images/worldwide.png -------------------------------------------------------------------------------- /Frontend/src/assets/logo-udemy.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Frontend/src/components/NavLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import Navbar from "./Navbar"; 3 | 4 | const NavLayout = () => { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | }; 12 | 13 | export default NavLayout; 14 | -------------------------------------------------------------------------------- /Frontend/src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from "react-router-dom"; 2 | import { useEffect, useRef } from "react"; 3 | import { MdLogout } from "react-icons/md"; 4 | import { useLogoutMutation } from "../reducers/api/authApi"; 5 | import useAuth from "../hooks/useAuth"; 6 | import Logo from "../assets/logo-udemy.svg"; 7 | import Search from "../assets/images/search.png"; 8 | import BlankProfile from "../assets/images/blankProfile.webp"; 9 | import { useGetUserDetailsQuery } from "../reducers/api/userApi"; 10 | 11 | function Navbar() { 12 | const { data: userDetails } = useGetUserDetailsQuery(); 13 | const [logout, { isSuccess }] = useLogoutMutation(); 14 | const navigate = useNavigate(); 15 | const queryRef = useRef(null); 16 | const { isStudent, isInstructor } = useAuth(); 17 | 18 | useEffect(() => { 19 | if (isSuccess) navigate("/join/login"); 20 | }, [isSuccess, navigate]); 21 | const handleSubmit = (e) => { 22 | e.preventDefault(); 23 | navigate(`/search/${queryRef.current.value}`); 24 | }; 25 | 26 | return ( 27 |
28 |
29 | 30 | 31 | 32 |
33 |
Categories
34 |
38 | 44 | 51 |
52 | {isInstructor ? ( 53 | Instructor 54 | ) : ( 55 | Teach on Udemy 56 | )} 57 | {isStudent ? ( 58 |
59 | My Learning 60 | 67 | 76 | 77 | 80 |
81 | ) : ( 82 |
83 |
84 | Log In 85 |
86 |
87 | Sign Up 88 |
89 |
90 | )} 91 |
92 | ); 93 | } 94 | 95 | export default Navbar; 96 | -------------------------------------------------------------------------------- /Frontend/src/components/Spinner.jsx: -------------------------------------------------------------------------------- 1 | import { TailSpin } from "react-loader-spinner"; 2 | const Spinner = () => { 3 | return ( 4 |
5 | 15 |
16 | ); 17 | }; 18 | export default Spinner; 19 | -------------------------------------------------------------------------------- /Frontend/src/data/categories.js: -------------------------------------------------------------------------------- 1 | export const categories = [ 2 | { 3 | Development: [ 4 | "Android Development", 5 | "iOS Development", 6 | "Web Development", 7 | "Game Development", 8 | "Software Testing", 9 | "Programming Languauge", 10 | "No-code Development", 11 | ], 12 | }, 13 | { 14 | Finance: [ 15 | "Accounting", 16 | "Economics", 17 | "Finance", 18 | "Taxes", 19 | "Investing", 20 | "Money Management Tools", 21 | "Compliance", 22 | ], 23 | }, 24 | { 25 | Design: [ 26 | "Web Design", 27 | "Desing Tools", 28 | "Game Design", 29 | "Fashion Design", 30 | "Architectural Design", 31 | "Other Design", 32 | ], 33 | }, 34 | { 35 | "Personal Development": [ 36 | "Personal Transformation", 37 | "Leadership", 38 | "Career Development", 39 | "Happiness", 40 | "Creativity", 41 | "Influence", 42 | ], 43 | }, 44 | { 45 | Lifestyle: [ 46 | "Arts", 47 | "Makeup", 48 | "Food", 49 | "Gaming", 50 | "Travel", 51 | "Home Improvement", 52 | ], 53 | }, 54 | { 55 | Business: [ 56 | "Entrepreneueship", 57 | "Communication", 58 | "Management", 59 | "Sales", 60 | "Operations", 61 | "Business Law", 62 | "Industry", 63 | "Media", 64 | ], 65 | }, 66 | ]; 67 | -------------------------------------------------------------------------------- /Frontend/src/data/dashoard-links.js: -------------------------------------------------------------------------------- 1 | export const DashboardLinks = [ 2 | { 3 | title: "Plan your course", 4 | sublinks: [ 5 | { 6 | subtitle: "Intended Learners", 7 | link: "goals", 8 | }, 9 | ], 10 | }, 11 | { 12 | title: "Create your content", 13 | sublinks: [ 14 | { 15 | subtitle: "Curriculum", 16 | link: "curriculum", 17 | }, 18 | ], 19 | }, 20 | { 21 | title: "Publish your Course", 22 | sublinks: [ 23 | { 24 | subtitle: "Course landing page", 25 | link: "basics", 26 | }, 27 | ], 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /Frontend/src/data/priceTier.js: -------------------------------------------------------------------------------- 1 | export const PRICE_TIER = [ 2 | { 3 | FREE: 0, 4 | }, 5 | { 6 | "₹499": 499, 7 | }, 8 | { 9 | "₹799": 799, 10 | }, 11 | { 12 | "₹999": 999, 13 | }, 14 | { 15 | "₹1499": 1499, 16 | }, 17 | { 18 | "₹1999": 1999, 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /Frontend/src/data/roles.js: -------------------------------------------------------------------------------- 1 | export const ROLES = { 2 | Student: "Student", 3 | Instructor: "Instructor", 4 | }; 5 | -------------------------------------------------------------------------------- /Frontend/src/features/Auth/Login/index.jsx: -------------------------------------------------------------------------------- 1 | import { useForm } from "react-hook-form"; 2 | import { useDispatch } from "react-redux"; 3 | import { Link, useLocation, useNavigate } from "react-router-dom"; 4 | import { useLoginMutation } from "../../../reducers/api/authApi"; 5 | import { setCredentials } from "../../../reducers/authSlice"; 6 | 7 | const Login = () => { 8 | const navigate = useNavigate(); 9 | const location = useLocation(); 10 | const dispatch = useDispatch(); 11 | const [login] = useLoginMutation(); 12 | const { register, handleSubmit } = useForm(); 13 | let from = location.state?.from?.pathname || "/"; 14 | 15 | const onSubmit = async (data) => { 16 | const result = await login({ ...data }).unwrap(); 17 | if (result.success) { 18 | dispatch(setCredentials(result.accessToken)); 19 | navigate(from, { replace: true }); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 |
26 |
30 |

Log in to your Udemy account

31 |
32 | 38 | 44 |
45 | 46 | 52 |
53 |
54 | 58 | New to udemy 59 | 60 | 61 | Home 62 | 63 |
64 | ); 65 | //{errors && This field is required} 66 | }; 67 | 68 | export default Login; 69 | -------------------------------------------------------------------------------- /Frontend/src/features/Auth/PersistLogin.jsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { Navigate, Outlet, useLocation } from "react-router-dom"; 3 | import { useEffect, useRef, useState } from "react"; 4 | import Spinner from "../../components/Spinner"; 5 | import { useRefreshMutation } from "../../reducers/api/authApi"; 6 | 7 | const PersistLogin = () => { 8 | const location = useLocation(); 9 | const token = useSelector((state) => state.auth.token); 10 | const effectRan = useRef(false); 11 | const [success, setSuccess] = useState(false); 12 | const [refresh, { isLoading, isError, isSuccess, isUninitialized }] = 13 | useRefreshMutation(); 14 | 15 | useEffect(() => { 16 | if (effectRan.current === true) { 17 | const verifyRefreshToken = async () => { 18 | try { 19 | await refresh(); 20 | setSuccess(true); 21 | } catch (err) { 22 | console.error(err); 23 | } 24 | }; 25 | 26 | if (!token) verifyRefreshToken(); 27 | } 28 | 29 | return () => (effectRan.current = true); 30 | 31 | // eslint-disable-next-line 32 | }, []); 33 | if (isLoading) { 34 | return ; 35 | } 36 | if (isError) { 37 | if (location.pathname == "/") { 38 | return ; 39 | } 40 | return ; 41 | } 42 | if (success && isSuccess) { 43 | return ; 44 | } 45 | if (token && isUninitialized) { 46 | return ; 47 | } 48 | }; 49 | 50 | export default PersistLogin; 51 | -------------------------------------------------------------------------------- /Frontend/src/features/Auth/RequireAuth.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { useLocation, Navigate, Outlet } from "react-router-dom"; 3 | import useAuth from "../../hooks/useAuth"; 4 | 5 | const RequireAuth = ({ allowedRoles }) => { 6 | const location = useLocation(); 7 | const { roles } = useAuth(); 8 | 9 | if (roles.some((role) => allowedRoles.includes(role))) { 10 | return ; 11 | } 12 | return ; 13 | }; 14 | export default RequireAuth; 15 | -------------------------------------------------------------------------------- /Frontend/src/features/Auth/Signup/index.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { useForm } from "react-hook-form"; 3 | import { useSignupMutation } from "../../../reducers/api/authApi"; 4 | import { Link, useNavigate } from "react-router-dom"; 5 | 6 | const Signup = ({ roles }) => { 7 | const navigate = useNavigate(); 8 | const [signup] = useSignupMutation(); 9 | const { register, handleSubmit } = useForm(); 10 | 11 | const onSubmit = async (data) => { 12 | const result = await signup({ roles, ...data }).unwrap(); 13 | 14 | if (result.success) { 15 | navigate("/join/login", { replace: true }); 16 | } 17 | }; 18 | 19 | return ( 20 |
21 |
22 |
26 |

Become a Udemy instructor

27 |

28 | Discover a supportive community of online instructors. Get instant 29 | access to all course creation resources. 30 |

31 |
32 | 38 | 44 | 50 |
51 | 52 | 58 |
59 |
60 | 64 | Already have account 65 | 66 |
67 | ); 68 | //{errors && This field is required} 69 | }; 70 | 71 | export default Signup; 72 | -------------------------------------------------------------------------------- /Frontend/src/features/Common/Profile/BasicInformation.jsx: -------------------------------------------------------------------------------- 1 | import { useForm } from "react-hook-form"; 2 | import { 3 | useEditProfileMutation, 4 | useGetUserDetailsQuery, 5 | } from "../../../reducers/api/userApi"; 6 | 7 | const BasicInformation = () => { 8 | const { data } = useGetUserDetailsQuery(); 9 | const [editProfile] = useEditProfileMutation(); 10 | const { register, handleSubmit, getValues } = useForm({ 11 | defaultValues: { 12 | firstName: data?.firstName, 13 | lastName: data?.lastName, 14 | headline: data?.headline, 15 | biography: data?.biography, 16 | }, 17 | }); 18 | const onSubmit = async () => { 19 | const firstName = getValues("firstName"); 20 | const lastName = getValues("lastName"); 21 | const headline = getValues("headline"); 22 | const biography = getValues("biography"); 23 | 24 | await editProfile({ 25 | firstName, 26 | lastName, 27 | headline, 28 | biography, 29 | }); 30 | }; 31 | return ( 32 |
33 |
34 |
35 |
36 | 39 | 45 |
46 |
47 | 50 | 56 |
57 |
58 |
59 |
60 | 63 | 70 |
71 |
72 | 75 | 81 |
82 |
83 | 86 |
87 |
88 | ); 89 | }; 90 | 91 | export default BasicInformation; 92 | -------------------------------------------------------------------------------- /Frontend/src/features/Common/Profile/ProfileLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from "react-router-dom"; 2 | import Spinner from "../../../components/Spinner"; 3 | import { useGetUserDetailsQuery } from "../../../reducers/api/userApi"; 4 | import useAuth from "../../../hooks/useAuth"; 5 | 6 | const ProfileLayout = () => { 7 | const { isLoading } = useGetUserDetailsQuery(); 8 | const { isInstructor } = useAuth(); 9 | if (isLoading) return ; 10 | return ( 11 |
12 |

Profile & Settings

13 |
14 |

15 | 20 | Udemy Profile 21 | 22 |

23 |

24 | 25 | Profile Picture 26 | 27 |

28 |
29 | 30 |
31 | ); 32 | }; 33 | 34 | export default ProfileLayout; 35 | -------------------------------------------------------------------------------- /Frontend/src/features/Common/Profile/ProfilePhoto.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { 4 | useEditProfileMutation, 5 | useGetUserDetailsQuery, 6 | } from "../../../reducers/api/userApi"; 7 | 8 | const ProfilePhoto = () => { 9 | const [inputImage, setInputImage] = useState(false); 10 | const { data } = useGetUserDetailsQuery(); 11 | const [editProfile, { isLoading }] = useEditProfileMutation(); 12 | const { register, handleSubmit, getValues, watch } = useForm(); 13 | const pictureWatch = watch("profilePicture"); 14 | useEffect(() => { 15 | setInputImage(getValues("profilePicture")[0]); 16 | // eslint-disable-next-line react-hooks/exhaustive-deps 17 | }, [pictureWatch]); 18 | 19 | const onSubmit = async () => { 20 | const profilePicture = getValues("profilePicture")[0]; 21 | await editProfile({ 22 | profilePicture, 23 | }); 24 | }; 25 | 26 | return ( 27 |
28 |

Preview image

29 | 36 | 42 | 48 |
49 | ); 50 | }; 51 | 52 | export default ProfilePhoto; 53 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Course/Checkout.jsx: -------------------------------------------------------------------------------- 1 | import { loadStripe } from "@stripe/stripe-js"; 2 | import { useParams } from "react-router-dom"; 3 | import { useCreateSessionMutation } from "../../../reducers/api/paymentApi"; 4 | 5 | export default function Checkout() { 6 | const { courseId } = useParams(); 7 | const [createSession] = useCreateSessionMutation(); 8 | 9 | const handleSubmit = async (e) => { 10 | e.preventDefault(); 11 | try { 12 | const stripe = await loadStripe(import.meta.env.VITE_STRIPE_PROMISE); 13 | 14 | const response = await createSession({ courseId }).unwrap(); 15 | if (!response.success) { 16 | return; 17 | } 18 | const result = await stripe.redirectToCheckout({ 19 | sessionId: response.id, 20 | }); 21 | if (result.error) { 22 | console.log(result.error); 23 | } 24 | } catch (error) { 25 | console.log(error); 26 | } 27 | }; 28 | 29 | return ( 30 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Course/ClaimCertificate.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Page, 3 | Text, 4 | View, 5 | Document, 6 | StyleSheet, 7 | Image, 8 | PDFDownloadLink, 9 | } from "@react-pdf/renderer"; 10 | import Logo from "../../../assets/images/ulogo.png"; 11 | 12 | // Create styles 13 | const styles = StyleSheet.create({ 14 | page: { 15 | flexDirection: "row", 16 | backgroundColor: "#E4E4E4", 17 | }, 18 | text: { 19 | padding: "20px", 20 | fontSize: "20px", 21 | }, 22 | title: { 23 | padding: "20px", 24 | fontSize: "50px", 25 | fontWeight: "bold", 26 | }, 27 | name: { 28 | marginTop: "100px", 29 | padding: "20px", 30 | fontSize: "40px", 31 | fontWeight: "bold", 32 | }, 33 | }); 34 | 35 | // Create Document Component 36 | // eslint-disable-next-line react/prop-types 37 | function PDF({ userName, courseName }) { 38 | return ( 39 | 40 | 41 | 42 | 43 | Fake Certificate of Completion 44 | 45 | 46 | {courseName} 47 | {userName} 48 | 49 | 50 | 51 | ); 52 | } 53 | 54 | // eslint-disable-next-line react/prop-types 55 | export default function ClaimCertificate({ courseName, userName }) { 56 | return ( 57 |
58 | } 60 | fileName="certificate.pdf" 61 | style={{ width: "full" }} 62 | className="bg-purple-700 text-white font-bold p-2 text-center mt-4 mb-2 text-lg block" 63 | > 64 | {/*eslint-disable-next-line no-unused-vars*/} 65 | {({ blob, url, loading, error }) => 66 | loading ? "Loading Certificate..." : "Claim Certificate!" 67 | } 68 | 69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Course/CourseProgress.jsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom"; 2 | import Spinner from "../../../components/Spinner"; 3 | import ClaimCertificate from "./ClaimCertificate"; 4 | import { useGetFullCourseDetailsQuery } from "../../../reducers/api/courseApi"; 5 | import { useGetUserProgressQuery } from "../../../reducers/api/courseProgressApi"; 6 | 7 | const CourseProgress = () => { 8 | const { courseId } = useParams(); 9 | const { data, isLoading } = useGetFullCourseDetailsQuery(courseId); 10 | const { data: courseProgress } = useGetUserProgressQuery(courseId); 11 | if (isLoading) { 12 | return ; 13 | } 14 | let totalContent = 0; 15 | let userContent = 0; 16 | data?.courseDetails.courseContent.forEach((section) => { 17 | section?.subSection?.forEach((subsection) => { 18 | if ( 19 | courseProgress?.userProgress?.completedVideos.includes(subsection?._id) 20 | ) { 21 | userContent++; 22 | } 23 | totalContent++; 24 | }); 25 | }); 26 | 27 | return ( 28 |
29 |

Course Progress

30 |
31 | 32 |

33 | {Math.round((userContent / totalContent) * 100)} % 34 |

35 |
36 | {/* If Progress is 100% Then Claim Certificate */} 37 | {userContent / totalContent == 1 && ( 38 | 46 | )} 47 |
48 | ); 49 | }; 50 | 51 | export default CourseProgress; 52 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Course/InstructorInfo.jsx: -------------------------------------------------------------------------------- 1 | import { useGetFullCourseDetailsQuery } from "../../../reducers/api/courseApi"; 2 | import { useParams } from "react-router-dom"; 3 | import { IoIosStar, IoMdPlayCircle } from "react-icons/io"; 4 | import { SlBadge } from "react-icons/sl"; 5 | import { MdPeople } from "react-icons/md"; 6 | import BlankProfile from "../../../assets/images/blankProfile.webp"; 7 | import { CourseInfo } from "../../../utils/CourseInfo"; 8 | import Spinner from "../../../components/Spinner"; 9 | 10 | const InstructorInfo = () => { 11 | const { courseId } = useParams(); 12 | const { data, isLoading } = useGetFullCourseDetailsQuery(courseId); 13 | if (isLoading) { 14 | return ; 15 | } 16 | const info = data?.courseDetails.instructor; 17 | const { studentsEnrolled, ratingAvg, ratingLength } = CourseInfo( 18 | info?.courses 19 | ); 20 | 21 | return ( 22 |
23 |

Instructor

24 |

25 | {info?.firstName + " " + info?.lastName} 26 |

27 |

{info?.headline}

28 |
29 | 34 |
35 |

36 | {ratingAvg} Instructor Rating 37 |

38 |

39 | {ratingLength} Review 40 |

41 |

42 | {studentsEnrolled} Students 43 |

44 |

45 | {info?.courses.length} Courses 46 |

47 |
48 |
49 |

{info?.biography}

50 |
51 | ); 52 | }; 53 | 54 | export default InstructorInfo; 55 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Course/RatingAndReview.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { Rating } from "@smastrom/react-rating"; 3 | import { useState } from "react"; 4 | import { useParams } from "react-router-dom"; 5 | import { 6 | useCreateRatingMutation, 7 | useEditRatingMutation, 8 | } from "../../../reducers/api/ratingAndReview"; 9 | import { useGetFullCourseDetailsQuery } from "../../../reducers/api/courseApi"; 10 | import Spinner from "../../../components/Spinner"; 11 | 12 | import useAuth from "../../../hooks/useAuth"; 13 | 14 | const RatingAndReview = ({ rating = 0, review = "" }) => { 15 | const { courseId } = useParams(); 16 | const { data, isLoading } = useGetFullCourseDetailsQuery(courseId); 17 | const { userId } = useAuth(); 18 | const [inputRating, setInputRating] = useState(rating); 19 | const [inputReview, setInputReview] = useState(review); 20 | const [isEditRating, setIsEditRating] = useState(false); 21 | const [createRating] = useCreateRatingMutation(); 22 | const [editRating] = useEditRatingMutation(); 23 | if (isLoading) { 24 | return ; 25 | } 26 | 27 | let userRating; 28 | if (data?.courseDetails?.studentsEnrolled?.includes(userId)) { 29 | userRating = data?.courseDetails?.ratingAndReviews?.find( 30 | (rating) => rating?.user._id == userId 31 | ); 32 | } 33 | 34 | const handleRating = (e) => { 35 | setInputRating(e); 36 | }; 37 | 38 | const handleReview = (e) => { 39 | setInputReview(e.target.value); 40 | }; 41 | 42 | const handleSubmit = async (e) => { 43 | e.preventDefault(); 44 | const rating = inputRating; 45 | const review = inputReview; 46 | if (!isEditRating) { 47 | await createRating({ courseId, rating, review }); 48 | } else { 49 | await editRating({ courseId, rating, review }); 50 | 51 | setIsEditRating(false); 52 | } 53 | }; 54 | 55 | return ( 56 |
57 |
58 | {data?.courseDetails?.studentsEnrolled?.includes(userId) ? ( 59 | userRating && !isEditRating ? ( 60 |
61 |

Your Review

62 |
63 |
64 | 69 |

{userRating.updatedAt.substring(0, 10)}

70 |
71 |
72 |

{userRating?.review}

73 | 82 |
83 |
84 |
85 | ) : ( 86 |
87 |

Rate this course

88 |

Tell others what you think

89 | 95 | 102 | 103 | 104 | ) 105 | ) : null} 106 |
107 |
108 |

Reviews

109 | {data?.courseDetails.ratingAndReviews.map((rating) => { 110 | return ( 111 |
115 |

116 | {rating.user.firstName + " " + rating.user.lastName} 117 |

118 |
119 | 120 |

{rating.updatedAt?.substring(0, 10)}

121 |
122 |

{rating.review}

123 |
124 | ); 125 | })} 126 |
127 |
128 | ); 129 | }; 130 | 131 | export default RatingAndReview; 132 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Course/VideoPlayer.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { useParams } from "react-router-dom"; 3 | import { useUpdateCourseProgressMutation } from "../../../reducers/api/courseProgressApi"; 4 | 5 | /* eslint-disable react/prop-types */ 6 | const VideoPlayer = ({ 7 | setIsVideoPlaying, 8 | section = "", 9 | currentSectionIndex, 10 | setCurrentSectionIndex, 11 | currentSubSectionIndex, 12 | setCurrentSubSectionIndex, 13 | courseContent = "", 14 | }) => { 15 | const { courseId } = useParams(); 16 | const [updateCourseProgress] = useUpdateCourseProgressMutation(); 17 | 18 | const handleVideoEnd = async () => { 19 | if (currentSubSectionIndex + 1 < section.subSection.length) { 20 | setCurrentSubSectionIndex(currentSubSectionIndex + 1); 21 | } else { 22 | if (currentSectionIndex + 1 < courseContent.length) { 23 | setCurrentSubSectionIndex(0); 24 | setCurrentSectionIndex(currentSectionIndex + 1); 25 | } else { 26 | setCurrentSubSectionIndex(0); 27 | setCurrentSectionIndex(0); 28 | } 29 | } 30 | await updateCourseProgress({ 31 | courseId, 32 | subSectionId: 33 | courseContent[currentSectionIndex].subSection[currentSubSectionIndex] 34 | ._id, 35 | }); 36 | }; 37 | return ( 38 |
{ 41 | setIsVideoPlaying(false); 42 | }} 43 | > 44 |
e.stopPropagation()} 47 | > 48 | 59 |

60 | { 61 | courseContent[currentSectionIndex].subSection[ 62 | currentSubSectionIndex 63 | ].subSectionName 64 | } 65 |

66 |
67 |
68 | ); 69 | }; 70 | 71 | export default VideoPlayer; 72 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Home/CourseCard.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { CourseInfo } from "../../../utils/CourseInfo"; 3 | import { Link } from "react-router-dom"; 4 | import { Rating, ThinStar } from "@smastrom/react-rating"; 5 | const ratingStyles = { 6 | itemShapes: ThinStar, 7 | activeFillColor: "#cfb700", 8 | inactiveFillColor: "#fbf1a9", 9 | }; 10 | 11 | const CourseCard = ({ course }) => { 12 | const { ratingAvg, ratingLength } = CourseInfo([course]); 13 | 14 | return ( 15 |
16 | 17 | 22 |

{course?.courseName}

23 |

24 | {course?.instructor?.firstName} {course?.instructor?.lastName} 25 |

26 |
27 | {ratingAvg} 28 | 34 |

({ratingLength})

35 |
36 |

{course?.price > 0 ? "₹ " + course?.price : "FREE"}

37 | 38 |
39 | ); 40 | }; 41 | 42 | export default CourseCard; 43 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Home/Section.jsx: -------------------------------------------------------------------------------- 1 | import { useGetCategoryCoursesQuery } from "../../../reducers/api/courseApi"; 2 | import Spinner from "../../../components/Spinner"; 3 | import CourseCard from "./CourseCard"; 4 | 5 | const Section = () => { 6 | const { data, isLoading } = useGetCategoryCoursesQuery("Development"); 7 | if (isLoading) { 8 | return ; 9 | } 10 | 11 | return ( 12 |
13 | {data?.map((course, index) => { 14 | return ; 15 | })} 16 |
17 | ); 18 | }; 19 | 20 | export default Section; 21 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import FrontImg from "../../../assets/images/front-img.jpg"; 2 | import Section from "./Section"; 3 | 4 | const Home = () => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
Learning that gets you
11 |
12 | Skills for your present (and your future). Get started with us. 13 |
14 |
15 |
16 |
17 |

What to learn next

18 |

19 | Popular Courses in Development 20 |

21 |
22 |
23 |
24 |
25 |
26 |
27 | ); 28 | }; 29 | 30 | export default Home; 31 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/SearchCourses/WideCourseCard.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | 3 | import { Link } from "react-router-dom"; 4 | import { CourseInfo } from "../../../utils/CourseInfo"; 5 | import { Rating } from "@smastrom/react-rating"; 6 | 7 | const WideCourseCard = ({ course }) => { 8 | const { ratingAvg, ratingLength } = CourseInfo([course]); 9 | 10 | return ( 11 | 15 | 16 |
17 |
18 |

{course.courseName}

19 |

{course.courseSubtitle}

20 |

21 | {course.instructor.firstName} {course.instructor.lastName} 22 |

23 |

24 |

{ratingAvg}

25 | 26 |

({ratingLength})

27 |

28 |

{course.instructionalLevel} Level

29 |
30 |
31 | {course.price == 0 ? "FREE " : "₹ " + course.price} 32 |
33 |
34 | 35 | ); 36 | }; 37 | 38 | export default WideCourseCard; 39 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/SearchCourses/index.jsx: -------------------------------------------------------------------------------- 1 | import { useSearchCoursesQuery } from "../../../reducers/api/courseApi"; 2 | import { useParams } from "react-router-dom"; 3 | import Spinner from "../../../components/Spinner"; 4 | import WideCourseCard from "./WideCourseCard"; 5 | 6 | const SearchCourses = () => { 7 | const params = useParams(); 8 | const { query } = params; 9 | const { data, isLoading } = useSearchCoursesQuery(query); 10 | if (isLoading) { 11 | return ; 12 | } 13 | 14 | return ( 15 |
16 |
17 |

18 | {data?.courses.length} results for {query} 19 |

20 |
21 |
22 | {data?.courses.map((course) => { 23 | return ; 24 | })} 25 |
26 |
27 | ); 28 | }; 29 | 30 | export default SearchCourses; 31 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Success/index.jsx: -------------------------------------------------------------------------------- 1 | import { Link, useParams } from "react-router-dom"; 2 | import { useEnrollStudentQuery } from "../../../reducers/api/paymentApi"; 3 | import Spinner from "../../../components/Spinner"; 4 | 5 | const Success = () => { 6 | const { sessionId } = useParams(); 7 | const { data, isLoading } = useEnrollStudentQuery(sessionId); 8 | if (isLoading) { 9 | return ; 10 | } 11 | 12 | return ( 13 |
14 |
15 |
16 |

17 | {data?.success 18 | ? "You Have Successfully Enrolled For This Course" 19 | : "An Error Occured"} 20 |

21 |
22 | 26 | View Purchased Courses 27 | 28 | 29 | Browse Other Courses 30 | 31 |
32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default Success; 39 | -------------------------------------------------------------------------------- /Frontend/src/features/Courses/Teaching/index.jsx: -------------------------------------------------------------------------------- 1 | import TeachingHome from "../../../assets/images/teaching-front.jpg"; 2 | import TeachYourWay from "../../../assets/images/teach-your-way.jpg"; 3 | import InspireLearners from "../../../assets/images/inspire-learners.jpg"; 4 | import GetRewarded from "../../../assets/images/get-rewarded.jpg"; 5 | import useAuth from "../../../hooks/useAuth"; 6 | import { useLocation, useNavigate } from "react-router-dom"; 7 | import { useAddInstructorMutation } from "../../../reducers/api/authApi"; 8 | 9 | const Teaching = () => { 10 | const { status } = useAuth(); 11 | const location = useLocation(); 12 | const navigate = useNavigate(); 13 | const [addInstructor] = useAddInstructorMutation(); 14 | const handleStartCLick = async () => { 15 | if (!status) { 16 | navigate("join/instructor-login", { 17 | state: { from: location }, 18 | replace: true, 19 | }); 20 | } 21 | if (status == "Instructor") { 22 | navigate("/instructor"); 23 | } 24 | if (status == "Student") { 25 | const result = await addInstructor(); 26 | if (result?.data?.success) navigate("/instructor"); 27 | } 28 | }; 29 | 30 | return ( 31 |
32 |
33 | 38 |
39 |

Come teach with us

40 |

41 | Beacome an instructor and change lives - including your own 42 |

43 | 49 |
50 |
51 |
52 |

53 | So many reasons to start 54 |

55 |
56 |
57 |
58 | 59 |
60 |

Teach Your way

61 |

62 | Publish the course you want, in the way you want, and always have 63 | control of your own content 64 |

65 |
66 |
67 |
68 | 69 |
70 |

Inspire Learners

71 |

72 | Teach what you know and help learners explore their interest, gain 73 | new skills, and advance their careers 74 |

75 |
76 |
77 |
78 | 79 |
80 |

Get Rewarded

81 |

82 | Expand your professional network, build your expertise, and earn 83 | money on each paid enrollment 84 |

85 |
86 |
87 |
88 |
89 | ); 90 | }; 91 | 92 | export default Teaching; 93 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/CourseLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet, Link, useParams } from "react-router-dom"; 2 | import { FaChevronLeft } from "react-icons/fa"; 3 | import Spinner from "../../../components/Spinner"; 4 | import Sidebar from "./Sidebar"; 5 | import { 6 | useGetCourseDetailsQuery, 7 | usePublishCourseMutation, 8 | } from "../../../reducers/api/courseApi"; 9 | 10 | const CourseLayout = () => { 11 | const params = useParams(); 12 | const { courseId } = params; 13 | const { data, isLoading } = useGetCourseDetailsQuery(courseId); 14 | const [publishCourse, { isLoading: isPublishLoading }] = 15 | usePublishCourseMutation(); 16 | const handlePublishCourse = async () => { 17 | await publishCourse({ courseId }); 18 | }; 19 | if (isLoading) { 20 | return ; 21 | } 22 | let totalDuration = 0; 23 | data?.courseDetails.courseContent.forEach((section) => { 24 | section.subSection.forEach((subsection) => { 25 | console.log(subsection.timeDuration); 26 | totalDuration = totalDuration + Math.round(subsection.timeDuration); 27 | }); 28 | }); 29 | totalDuration = Math.ceil(totalDuration / 60); 30 | 31 | return ( 32 | <> 33 |
34 |
35 | 36 | 37 |

Back To Courses

38 | 39 |

{data?.courseDetails?.courseName}

40 |

41 | {data?.courseDetails?.status} 42 |

43 |

{totalDuration} minutes Content Length

44 |
45 |
46 | {data?.courseDetails.status != "Published" && ( 47 | 55 | )} 56 |
57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 | 65 | ); 66 | }; 67 | 68 | export default CourseLayout; 69 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/CreateCourse/index.jsx: -------------------------------------------------------------------------------- 1 | import { useForm } from "react-hook-form"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { useCreateCourseMutation } from "../../../../reducers/api/courseApi"; 4 | 5 | const CreateCourse = () => { 6 | const navigate = useNavigate(); 7 | const { register, handleSubmit } = useForm(); 8 | 9 | const [createCourse, { loading }] = useCreateCourseMutation(); 10 | 11 | const onSubmit = async (data) => { 12 | try { 13 | const result = await createCourse(data).unwrap(); 14 | navigate(`/instructor/course/${result.data._id}/manage/basics`); 15 | 16 | //dispatch(setCredentials({ accessToken })) 17 | //navigate('/home') 18 | } catch (err) { 19 | console.log(err); 20 | } 21 | }; 22 | return ( 23 |
24 | {loading &&

Loading

} 25 |
26 |

27 | Give working title to course and choose category 28 |

29 |

30 | Don't worry you can change it later 31 |

32 |
33 | 36 | 43 |
44 |
45 |

Category

46 | 59 |
60 | 66 |
67 |
68 | ); 69 | }; 70 | 71 | export default CreateCourse; 72 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/Curriculum/AddContent.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { useFormContext, Controller } from "react-hook-form"; 3 | import { useState } from "react"; 4 | import { useAddSubSectionContentMutation } from "../../../../reducers/api/courseApi"; 5 | 6 | const AddContent = ({ 7 | setIsAddNewContent, 8 | videoUrl, 9 | subSectionId, 10 | videoName, 11 | date, 12 | }) => { 13 | const [isLoading, setIsloading] = useState(false); 14 | const [isReplaceContent, setIsReplaceContent] = useState(false); 15 | const [addSubSectionContent] = useAddSubSectionContentMutation(); 16 | const { setValue, resetField, control, register, getValues } = 17 | useFormContext(); 18 | const handleFileChange = (e) => { 19 | // When a new file is selected, update the form state 20 | setValue("file", e.target.files); 21 | }; 22 | 23 | const handleUploadContent = async () => { 24 | setIsloading(true); 25 | 26 | const file = getValues("file")[0]; 27 | await addSubSectionContent({ 28 | subSectionId, 29 | file, 30 | }); 31 | 32 | setIsAddNewContent(false); 33 | setIsReplaceContent(false); 34 | setIsloading(false); 35 | }; 36 | 37 | const handleCancel = () => { 38 | setIsAddNewContent(false); 39 | setIsReplaceContent(false); 40 | resetField("file"); 41 | }; 42 | 43 | return ( 44 |
45 | {videoUrl && !isReplaceContent ? ( 46 |
47 |
48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 |
52 | Name 53 | Date
60 | {videoName} 61 | {date}
66 | 74 |
75 |
76 | ) : ( 77 | <> 78 | 93 | 94 |
95 | 102 | 110 |
111 | 112 | )} 113 |
114 | ); 115 | }; 116 | export default AddContent; 117 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/Curriculum/ConfirmDeleteModal.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | const ConfirmDeleteModal = ({ onCancel, onConfirm }) => { 3 | return ( 4 |
8 |
e.stopPropagation()} 11 | > 12 |

Are you sure you want to delete this Section?

13 |
14 | 17 | 24 |
25 |
26 |
27 | ); 28 | }; 29 | 30 | export default ConfirmDeleteModal; 31 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/Goals/LearningObjectives.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useFieldArray, useForm } from "react-hook-form"; 3 | import { useParams } from "react-router-dom"; 4 | import { MdDelete } from "react-icons/md"; 5 | import { 6 | useEditCourseGoalsMutation, 7 | useGetCourseDetailsQuery, 8 | } from "../../../../reducers/api/courseApi"; 9 | 10 | const LearningObjectives = () => { 11 | const params = useParams(); 12 | const { courseId } = params; 13 | 14 | const { data } = useGetCourseDetailsQuery(courseId); 15 | const [editCourseGoals, { isLoading }] = useEditCourseGoalsMutation(); 16 | 17 | const { register, control, setValue, handleSubmit } = useForm({ 18 | defaultValues: { 19 | learningObjectives: data?.courseDetails.learningObjectives 20 | ? [...data.courseDetails.learningObjectives] 21 | : [], 22 | }, 23 | }); 24 | 25 | const { fields, append, remove } = useFieldArray({ 26 | name: "learningObjectives", 27 | control, 28 | }); 29 | 30 | useEffect(() => { 31 | data.courseDetails.learningObjectives.forEach((goal, index) => { 32 | setValue(`learningObjectives.${index}.objective`, goal.objective); 33 | }); 34 | // eslint-disable-next-line react-hooks/exhaustive-deps 35 | }, []); 36 | 37 | const onSubmit = async (data) => { 38 | await editCourseGoals({ data, courseId }); 39 | }; 40 | 41 | return ( 42 |
43 |
44 |

What will students learn in your course?

45 |

46 | Enter learning objectives or outcomes that learners can expect to 47 | achieve after completing your course. 48 |

49 | {fields.map((field, index) => { 50 | return ( 51 |
52 | 60 | {index > 0 && ( 61 | 64 | )} 65 |
66 | ); 67 | })} 68 |
69 | 76 | 83 |
84 |
85 |
86 | ); 87 | }; 88 | 89 | export default LearningObjectives; 90 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/Goals/Prerequisites.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useFieldArray, useForm } from "react-hook-form"; 3 | import { useParams } from "react-router-dom"; 4 | import { MdDelete } from "react-icons/md"; 5 | import { 6 | useEditCourseGoalsMutation, 7 | useGetCourseDetailsQuery, 8 | } from "../../../../reducers/api/courseApi"; 9 | 10 | const Prerequisites = () => { 11 | const params = useParams(); 12 | const { courseId } = params; 13 | 14 | const { data } = useGetCourseDetailsQuery(courseId); 15 | const [editCourseGoals, { isLoading }] = useEditCourseGoalsMutation(); 16 | const { register, control, setValue, handleSubmit } = useForm({ 17 | defaultValues: { 18 | prerequisites: data?.courseDetails.prerequisites 19 | ? [...data.courseDetails.prerequisites] 20 | : [], 21 | }, 22 | }); 23 | const { fields, append, remove } = useFieldArray({ 24 | name: "prerequisites", 25 | control, 26 | }); 27 | 28 | useEffect(() => { 29 | data.courseDetails.prerequisites.forEach((prerequisite, index) => { 30 | setValue( 31 | `prerequisites.${index}.prerequisite`, 32 | prerequisite.prerequisite 33 | ); 34 | }); 35 | // eslint-disable-next-line react-hooks/exhaustive-deps 36 | }, []); 37 | 38 | const onSubmit = async (data) => { 39 | await editCourseGoals({ data, courseId }); 40 | }; 41 | return ( 42 |
43 |
44 |

45 | What are the requirements or prerequisites for taking your course? 46 |

47 |

48 | List the required skills, experience, tools or equipment learners 49 | should have prior to taking your course. If there are no requirements, 50 | use this space as an opportunity to lower the barrier for beginners. 51 |

52 | {fields.map((field, index) => { 53 | return ( 54 |
55 | 63 | {index > 0 && ( 64 | 67 | )} 68 |
69 | ); 70 | })} 71 |
72 | 79 | 86 |
87 |
88 |
89 | ); 90 | }; 91 | export default Prerequisites; 92 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/Goals/index.jsx: -------------------------------------------------------------------------------- 1 | import LearningObjectives from "./LearningObjectives"; 2 | import Prerequisites from "./Prerequisites"; 3 | 4 | const CourseGoals = () => { 5 | return ( 6 |
7 |

Intended Learners

8 |
9 |

10 | The following descriptions will be publicly visible on your Course 11 | Landing Page and will have a direct impact on your course performance. 12 | These descriptions will help learners decide if your course is right 13 | for them. 14 |

15 | 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default CourseGoals; 23 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/LandingPage/CoursePromo.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { useFormContext, Controller } from "react-hook-form"; 3 | import imagePlaceholder from "../../../../assets/images/imagePlaceholder.jpg"; 4 | import { useState } from "react"; 5 | 6 | const CoursePromo = ({ previewImage, promoVideo }) => { 7 | const [isAddImage, setIsAddImage] = useState(false); 8 | const [isAddVideo, setIsAddVideo] = useState(false); 9 | const { register, control } = useFormContext(); 10 | return ( 11 |
12 | 15 |
16 | 21 | 22 |
23 |

24 | Upload your course image here. It must meet our course image quality 25 | standards to be accepted. Important guidelines: 750x422 pixels; 26 | .jpg, .jpeg,. gif, or .png. no text on the image. 27 |

28 | {!isAddImage && previewImage ? ( 29 | 38 | ) : ( 39 | ( 44 | 45 | )} 46 | /> 47 | )} 48 |
49 |
50 | 51 | 54 |
55 |
56 | 59 |
60 | 61 |
62 |

63 | Your promo video is a quick and compelling way for students to 64 | preview what they will learn in your course. Students considering 65 | your course are more likely to enroll if your promo video is 66 | well-made. 67 |

68 | {!isAddVideo && promoVideo ? ( 69 | 78 | ) : ( 79 | ( 84 | 85 | )} 86 | /> 87 | )} 88 |
89 |
90 |
91 | ); 92 | }; 93 | 94 | export default CoursePromo; 95 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/LandingPage/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { FormProvider, useForm } from "react-hook-form"; 4 | import { useIsMount } from "./useIsMount"; 5 | import CoursePromo from "./CoursePromo"; 6 | import { 7 | useEditCourseBasicsMutation, 8 | useGetCourseDetailsQuery, 9 | } from "../../../../reducers/api/courseApi"; 10 | import { categories } from "../../../../data/categories"; 11 | import { PRICE_TIER } from "../../../../data/priceTier"; 12 | 13 | const locales = ["English", "German", "French", "Spanish", "Hindi", "Marathi"]; 14 | const instructionaleLevel = ["Beginner", "Medium", "Advanced", "All"]; 15 | 16 | const LandingPage = () => { 17 | const isMount = useIsMount(); 18 | const params = useParams(); 19 | const { courseId } = params; 20 | const { data } = useGetCourseDetailsQuery(courseId); 21 | const [editCourseBasics] = useEditCourseBasicsMutation(); 22 | 23 | const [subCategories, setSubCategories] = useState( 24 | data?.courseDetails?.category 25 | ? categories?.find((category) => category[data?.courseDetails?.category])[ 26 | data?.courseDetails?.category 27 | ] 28 | : [] 29 | ); 30 | const methods = useForm({ 31 | defaultValues: { 32 | courseName: data.courseDetails?.courseName, 33 | courseSubtitle: data.courseDetails?.courseSubtitle, 34 | description: data.courseDetails?.description, 35 | locale: data.courseDetails?.locale, 36 | instructionalLevel: data.courseDetails?.instructionalLevel, 37 | category: data.courseDetails?.category, 38 | subCategory: data.courseDetails?.subCategory, 39 | price: data.courseDetails?.price, 40 | previewImage: "", 41 | promoVideo: "", 42 | }, 43 | }); 44 | const { register, handleSubmit, formState, getValues, watch } = methods; 45 | const watchCategory = watch("category"); 46 | const { isDirty } = formState; 47 | useEffect(() => { 48 | if (!isMount) { 49 | const selectedCategoryObject = categories.find( 50 | (category) => category[getValues("category")] 51 | ); 52 | setSubCategories( 53 | selectedCategoryObject 54 | ? selectedCategoryObject[getValues("category")] 55 | : [] 56 | ); 57 | } 58 | // eslint-disable-next-line react-hooks/exhaustive-deps 59 | }, [watchCategory]); 60 | 61 | const onSubmit = async () => { 62 | //const formData = new FormData(); 63 | const courseName = getValues("courseName"); 64 | const courseSubtitle = getValues("courseSubtitle"); 65 | const description = getValues("description"); 66 | const locale = getValues("locale"); 67 | const instructionalLevel = getValues("instructionalLevel"); 68 | const category = getValues("category"); 69 | const subCategory = getValues("subCategory"); 70 | const price = getValues("price"); 71 | const previewImage = getValues("previewImage")[0] 72 | ? getValues("previewImage")[0] 73 | : null; 74 | const promoVideo = getValues("promoVideo") 75 | ? getValues("promoVideo")[0] 76 | : null; 77 | await editCourseBasics({ 78 | courseId, 79 | courseName, 80 | courseSubtitle, 81 | description, 82 | locale, 83 | instructionalLevel, 84 | category, 85 | subCategory, 86 | price, 87 | previewImage, 88 | promoVideo, 89 | }); 90 | }; 91 | return ( 92 |
93 | 94 |
95 |
96 |

97 | Course Landing Page 98 |

99 | 106 |
107 |

108 | Your course landing page is crucial to your success on Udemy. If its 109 | done right, it can also help you gain visibility in search engines 110 | like Google. As you complete this section, think about creating a 111 | compelling Course Landing Page that demonstrates why someone would 112 | want to enroll in your course. Learn more about creating your course 113 | landing page and course title standards. 114 |

115 | 118 | 124 |

125 | Your title should be a mix of attention-grabbing, informative, and 126 | optimized for search 127 |

128 | 129 | 132 | 138 |

139 | Use 1 or 2 related keywords, and mention 3-4 of the most important 140 | areas that you have covered during your course. 141 |

142 | 143 | 146 | 156 |

157 | Description should have minimum 50 words. 158 |

159 | 160 |

Basic Info

161 |
162 | 175 | 176 | 189 | 205 |
206 | 207 |
208 | 221 |
222 |
223 |

Price

224 | 240 |
241 | 242 | 246 | 247 |
248 |
249 | ); 250 | }; 251 | 252 | export default LandingPage; 253 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/LandingPage/useIsMount.jsx: -------------------------------------------------------------------------------- 1 | // Fire useEffect expect on Initial Render 2 | 3 | import { useRef, useEffect } from "react"; 4 | 5 | export const useIsMount = () => { 6 | const isMountRef = useRef(true); 7 | useEffect(() => { 8 | isMountRef.current = false; 9 | }, []); 10 | return isMountRef.current; 11 | }; 12 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/Course/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { Link, useParams } from "react-router-dom"; 2 | import { DashboardLinks } from "../../../data/dashoard-links"; 3 | 4 | const Sidebar = () => { 5 | const params = useParams(); 6 | const { courseId } = params; 7 | return ( 8 |
9 | {DashboardLinks.map((ele, i) => { 10 | return ( 11 |
12 |

{ele.title}

13 |
14 | {ele.sublinks.map((subtitle, index) => { 15 | return ( 16 |
17 | 20 | {subtitle.subtitle} 21 | 22 |
23 | ); 24 | })} 25 |
26 |
27 | ); 28 | })} 29 |
30 | ); 31 | }; 32 | 33 | export default Sidebar; 34 | -------------------------------------------------------------------------------- /Frontend/src/features/Instructor/DashBoard/index.jsx: -------------------------------------------------------------------------------- 1 | import Spinner from "../../../components/Spinner"; 2 | import { Link } from "react-router-dom"; 3 | import BlankProfile from "../../../assets/images/blankProfile.webp"; 4 | import { useGetInstructorDetailsQuery } from "../../../reducers/api/courseApi"; 5 | import imagePlaceholder from "../../../assets/images/imagePlaceholder.jpg"; 6 | 7 | const Dashboard = () => { 8 | const { data, isLoading } = useGetInstructorDetailsQuery(); 9 | 10 | if (isLoading) { 11 | return ; 12 | } 13 | const profilePicture = data?.profilePicture; 14 | return ( 15 | <> 16 |
17 |
18 | Student 19 | 20 | 25 | 26 |
27 |
28 |

Jump into Course Creation

29 | 33 | Create Your Course 34 | 35 |
36 |
37 |
38 | {data?.courses.length > 0 && ( 39 |

Your Courses

40 | )} 41 | {data?.courses?.map((course) => { 42 | return ( 43 |
44 | 51 |
52 |
53 |

{course.courseName}

54 |

{course.status}

55 |
56 | 60 | Edit 61 | 62 |
63 |
64 | ); 65 | })}{" "} 66 |
67 | 68 | ); 69 | }; 70 | 71 | export default Dashboard; 72 | -------------------------------------------------------------------------------- /Frontend/src/features/Student/MyCourses/CourseProgressCard.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { Link } from "react-router-dom"; 3 | 4 | const CourseProgressCard = ({ course }) => { 5 | let progressVid = 0; 6 | let total = 0; 7 | course.courseId.courseContent.forEach((section) => { 8 | section.subSection.forEach((subsection) => { 9 | if (course.completedVideos.includes(subsection._id)) { 10 | progressVid++; 11 | } 12 | total++; 13 | }); 14 | }); 15 | let progress = 0; 16 | if (total > 0) { 17 | progress = progressVid / total; 18 | } 19 | 20 | return ( 21 | 25 | 26 |

{course.courseId.courseName}

27 |

28 | 29 | {progress * 100} % 30 |

31 | 32 | ); 33 | }; 34 | 35 | export default CourseProgressCard; 36 | -------------------------------------------------------------------------------- /Frontend/src/features/Student/MyCourses/index.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import Spinner from "../../../components/Spinner"; 3 | import { useGetUserDetailsQuery } from "../../../reducers/api/userApi"; 4 | import CourseProgressCard from "./CourseProgressCard"; 5 | 6 | const MyCourses = () => { 7 | const { data, isLoading } = useGetUserDetailsQuery(); 8 | if (isLoading) { 9 | return ; 10 | } 11 | return ( 12 |
13 |
14 |

My Courses

15 |
16 | {!data?.courseProgress.length ? ( 17 | 21 | Browse New Courses 22 | 23 | ) : ( 24 |
25 | {data?.courseProgress.map((course) => { 26 | return ; 27 | })} 28 |
29 | )} 30 |
31 | ); 32 | }; 33 | 34 | export default MyCourses; 35 | -------------------------------------------------------------------------------- /Frontend/src/hooks/useAuth.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux/es/hooks/useSelector"; 2 | import { selectCurrentToken } from "../reducers/authSlice"; 3 | import { jwtDecode } from "jwt-decode"; 4 | 5 | const useAuth = () => { 6 | const token = useSelector(selectCurrentToken); 7 | let isStudent = false; 8 | let isInstructor = false; 9 | let status = ""; 10 | if (token) { 11 | const decoded = jwtDecode(token); 12 | const { userId, roles } = decoded.userInfo; 13 | isStudent = roles?.includes("Student"); 14 | isInstructor = roles?.includes("Instructor"); 15 | if (isStudent) status = "Student"; 16 | if (isInstructor) status = "Instructor"; 17 | return { userId, roles, status, isStudent, isInstructor }; 18 | } 19 | return { userId: "", roles: [], isStudent, isInstructor, status }; 20 | }; 21 | 22 | export default useAuth; 23 | -------------------------------------------------------------------------------- /Frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | -------------------------------------------------------------------------------- /Frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | import {store} from './state/store.js' 6 | import { Provider } from 'react-redux' 7 | 8 | ReactDOM.createRoot(document.getElementById('root')).render( 9 | 10 | 11 | 12 | 13 | , 14 | ) 15 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/authApi.js: -------------------------------------------------------------------------------- 1 | import { setCredentials, logOut } from "../authSlice"; 2 | import { apiSlice } from "./baseApi"; 3 | export const authApi = apiSlice.injectEndpoints({ 4 | endpoints: (builder) => ({ 5 | login: builder.mutation({ 6 | query: (data) => ({ 7 | url: "auth/login", 8 | method: "POST", 9 | body: data, 10 | }), 11 | }), 12 | signup: builder.mutation({ 13 | query: (data) => ({ 14 | url: "auth/signup", 15 | method: "POST", 16 | body: data, 17 | }), 18 | }), 19 | refresh: builder.mutation({ 20 | query: () => ({ 21 | url: "/auth/refresh", 22 | method: "GET", 23 | }), 24 | async onQueryStarted(arg, { dispatch, queryFulfilled }) { 25 | try { 26 | const { data } = await queryFulfilled; 27 | const { accessToken } = data; 28 | dispatch(setCredentials(accessToken)); 29 | } catch (err) { 30 | console.log(err); 31 | } 32 | }, 33 | }), 34 | logout: builder.mutation({ 35 | query: () => ({ 36 | url: "/auth/logout", 37 | method: "POST", 38 | }), 39 | async onQueryStarted(arg, { dispatch, queryFulfilled }) { 40 | try { 41 | await queryFulfilled; 42 | dispatch(logOut()); 43 | setTimeout(() => { 44 | dispatch(apiSlice.util.resetApiState()); 45 | }, 1000); 46 | } catch (error) { 47 | console.log(error); 48 | } 49 | }, 50 | }), 51 | addInstructor: builder.mutation({ 52 | query: () => ({ 53 | url: "/auth/addInstructor", 54 | method: "GET", 55 | }), 56 | async onQueryStarted(arg, { dispatch, queryFulfilled }) { 57 | try { 58 | const { data } = await queryFulfilled; 59 | const { accessToken } = data; 60 | dispatch(setCredentials(accessToken)); 61 | } catch (err) { 62 | console.log(err); 63 | } 64 | }, 65 | }), 66 | }), 67 | }); 68 | 69 | export const { 70 | useLoginMutation, 71 | useSignupMutation, 72 | useRefreshMutation, 73 | useLogoutMutation, 74 | useAddInstructorMutation, 75 | } = authApi; 76 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/baseApi.js: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; 2 | import { setCredentials } from "../authSlice"; 3 | 4 | const baseQuery = fetchBaseQuery({ 5 | baseUrl: "http://localhost:4000/api/v1", 6 | credentials: "include", 7 | prepareHeaders: (headers, { getState }) => { 8 | const token = getState().auth.token; 9 | 10 | if (token) { 11 | headers.set("authorization", `Bearer ${token}`); 12 | } 13 | return headers; 14 | }, 15 | }); 16 | 17 | const baseQueryWithReauth = async (args, api, extraOptions) => { 18 | let result = await baseQuery(args, api, extraOptions); 19 | 20 | if (result?.error?.status === 403) { 21 | const refreshResult = await baseQuery("/auth/refresh", api, extraOptions); 22 | 23 | if (refreshResult?.data) { 24 | api.dispatch(setCredentials({ ...refreshResult.data })); 25 | 26 | result = await baseQuery(args, api, extraOptions); 27 | } else { 28 | if (refreshResult?.error?.status === 403) { 29 | refreshResult.error.data.message = "Your login has expired."; 30 | } 31 | return refreshResult; 32 | } 33 | } 34 | 35 | return result; 36 | }; 37 | 38 | export const apiSlice = createApi({ 39 | baseQuery: baseQueryWithReauth, 40 | tagTypes: ["Course", "User", "Courses", "CourseProgress"], 41 | // eslint-disable-next-line no-unused-vars 42 | endpoints: (builder) => ({}), 43 | }); 44 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/courseApi.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | import { apiSlice } from "./baseApi"; 4 | 5 | // I think RTK query is not working as expected with form data that's why we need this two functions 6 | const dataForm = ({ subSectionId = null, file = null }) => { 7 | const formData = new FormData(); 8 | formData.append("subSectionId", subSectionId); 9 | formData.append("videoFile", file); 10 | return formData; 11 | }; 12 | 13 | const basicsDataForm = ({ 14 | courseId, 15 | courseName, 16 | courseSubtitle, 17 | description, 18 | locale, 19 | instructionalLevel, 20 | category, 21 | subCategory, 22 | price, 23 | previewImage, 24 | promoVideo, 25 | }) => { 26 | const formData = new FormData(); 27 | formData.append("courseId", courseId); 28 | formData.append("courseName", courseName); 29 | formData.append("courseSubtitle", courseSubtitle); 30 | formData.append("description", description); 31 | formData.append("locale", locale); 32 | formData.append("instructionalLevel", instructionalLevel); 33 | formData.append("category", category); 34 | formData.append("subCategory", subCategory); 35 | formData.append("price", price); 36 | formData.append("previewImage", previewImage); 37 | formData.append("promoVideo", promoVideo); 38 | return formData; 39 | }; 40 | 41 | export const courseApi = apiSlice.injectEndpoints({ 42 | endpoints: (builder) => ({ 43 | createCourse: builder.mutation({ 44 | query: (data) => ({ 45 | url: "course/createcourse", 46 | method: "POST", 47 | body: data, 48 | }), 49 | invalidatesTags: ["Course"], 50 | }), 51 | getAllCourses: builder.query({ 52 | query: () => ({ 53 | url: "course/getAllCourses", 54 | }), 55 | transformResponse: (response, meta, arg) => response.data, 56 | providesTags: ["Course"], 57 | }), 58 | getCategoryCourses: builder.query({ 59 | query: (category) => ({ 60 | url: `course/getCategoryCourses/${category}`, 61 | }), 62 | transformResponse: (response, meta, arg) => response.data, 63 | providesTags: ["Course"], 64 | }), 65 | searchCourses: builder.query({ 66 | query: (query) => ({ 67 | url: `course/search/${query}`, 68 | }), 69 | providesTags: ["Course"], 70 | }), 71 | getInstructorDetails: builder.query({ 72 | query: () => ({ 73 | url: "instructor", 74 | }), 75 | transformResponse: (response) => response.data, 76 | providesTags: ["Course"], 77 | }), 78 | getCourseDetails: builder.query({ 79 | query: (courseId) => ({ 80 | url: `course/getCourseDetails/${courseId}`, 81 | }), 82 | transformResponse: (response, meta, arg) => response.data, 83 | providesTags: ["Course"], 84 | }), 85 | getFullCourseDetails: builder.query({ 86 | query: (courseId) => ({ 87 | url: `course/getFullCourseDetails/${courseId}`, 88 | }), 89 | transformResponse: (response, meta, arg) => response.data, 90 | providesTags: ["Course"], 91 | }), 92 | 93 | getsections: builder.query({ 94 | query: () => ({ 95 | url: "course/getsections", 96 | }), 97 | providesTags: ["Course"], 98 | transformResponse: (response, meta, arg) => response.data, 99 | }), 100 | editCourseGoals: builder.mutation({ 101 | query: (data) => ({ 102 | url: "course/editCourseGoals", 103 | method: "POST", 104 | body: data, 105 | }), 106 | invalidatesTags: ["Course"], 107 | }), 108 | editCourseBasics: builder.mutation({ 109 | query: (data) => ({ 110 | url: "course/editCourseBasics", 111 | method: "POST", 112 | body: basicsDataForm(data), 113 | }), 114 | invalidatesTags: ["Course"], 115 | }), 116 | 117 | createSection: builder.mutation({ 118 | query: (data) => ({ 119 | url: "course/createSection", 120 | method: "POST", 121 | body: data, 122 | }), 123 | invalidatesTags: ["Course"], 124 | }), 125 | 126 | updateSection: builder.mutation({ 127 | query: (data) => ({ 128 | url: "course/updateSection", 129 | method: "POST", 130 | body: data, 131 | }), 132 | invalidatesTags: ["Course"], 133 | }), 134 | 135 | deleteSection: builder.mutation({ 136 | query: (data) => ({ 137 | url: "course/deleteSection", 138 | method: "POST", 139 | body: data, 140 | }), 141 | invalidatesTags: ["Course"], 142 | }), 143 | 144 | createSubSection: builder.mutation({ 145 | query: (data) => ({ 146 | url: "course/createSubSection", 147 | method: "POST", 148 | body: data, 149 | }), 150 | invalidatesTags: ["Course"], 151 | }), 152 | addSubSectionContent: builder.mutation({ 153 | query: (data) => ({ 154 | url: "course/addSubSectionContent", 155 | method: "POST", 156 | body: dataForm({ 157 | file: data.file, 158 | subSectionId: data.subSectionId, 159 | }), 160 | }), 161 | invalidatesTags: ["Course"], 162 | }), 163 | updateSubSection: builder.mutation({ 164 | query: (data) => ({ 165 | url: "course/updateSubSection", 166 | method: "POST", 167 | body: data, 168 | }), 169 | invalidatesTags: ["Course"], 170 | }), 171 | deleteSubSection: builder.mutation({ 172 | query: (data) => ({ 173 | url: "course/deleteSubSection", 174 | method: "POST", 175 | body: data, 176 | }), 177 | invalidatesTags: ["Course"], 178 | }), 179 | publishCourse: builder.mutation({ 180 | query: (data) => ({ 181 | url: "course/publishCourse", 182 | method: "POST", 183 | body: data, 184 | }), 185 | invalidatesTags: ["Course"], 186 | }), 187 | }), 188 | }); 189 | 190 | export const { 191 | useCreateCourseMutation, 192 | useGetAllCoursesQuery, 193 | useGetCourseDetailsQuery, 194 | useSearchCoursesQuery, 195 | useGetFullCourseDetailsQuery, 196 | useGetCategoryCoursesQuery, 197 | useGetInstructorDetailsQuery, 198 | useGetsectionsQuery, 199 | useEditCourseGoalsMutation, 200 | useEditCourseBasicsMutation, 201 | useCreateSectionMutation, 202 | useUpdateSectionMutation, 203 | useDeleteSectionMutation, 204 | useCreateSubSectionMutation, 205 | useAddSubSectionContentMutation, 206 | useUpdateSubSectionMutation, 207 | useDeleteSubSectionMutation, 208 | usePublishCourseMutation, 209 | } = courseApi; 210 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/courseProgressApi.js: -------------------------------------------------------------------------------- 1 | import { apiSlice } from "./baseApi"; 2 | 3 | export const courseProgressApi = apiSlice.injectEndpoints({ 4 | endpoints: (builder) => ({ 5 | getUserProgress: builder.query({ 6 | query: (courseId) => ({ 7 | url: `courseProgress/getUserCourseProgress/${courseId}`, 8 | }), 9 | providesTags: ["CourseProgress"], 10 | }), 11 | updateCourseProgress: builder.mutation({ 12 | query: (data) => ({ 13 | url: "courseProgress/updateCourseProgress", 14 | method: "POST", 15 | body: data, 16 | }), 17 | invalidatesTags: ["CourseProgress"], 18 | }), 19 | }), 20 | }); 21 | 22 | export const { useGetUserProgressQuery, useUpdateCourseProgressMutation } = 23 | courseProgressApi; 24 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/paymentApi.js: -------------------------------------------------------------------------------- 1 | import { apiSlice } from "./baseApi"; 2 | 3 | export const paymentApi = apiSlice.injectEndpoints({ 4 | endpoints: (builder) => ({ 5 | createSession: builder.mutation({ 6 | query: (data) => ({ 7 | url: "payment/createSession", 8 | method: "POST", 9 | body: data, 10 | }), 11 | }), 12 | enrollStudent: builder.query({ 13 | query: (sessionId) => ({ 14 | url: `payment/completePayment/${sessionId}`, 15 | method: "GET", 16 | }), 17 | }), 18 | }), 19 | }); 20 | 21 | export const { useCreateSessionMutation, useEnrollStudentQuery } = paymentApi; 22 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/ratingAndReview.js: -------------------------------------------------------------------------------- 1 | import { apiSlice } from "./baseApi"; 2 | 3 | export const ratingAndReviewApi = apiSlice.injectEndpoints({ 4 | endpoints: (builder) => ({ 5 | createRating: builder.mutation({ 6 | query: (data) => ({ 7 | url: "ratingAndReview/create", 8 | method: "POST", 9 | body: data, 10 | }), 11 | invalidatesTags: ["Course"], 12 | }), 13 | editRating: builder.mutation({ 14 | query: (data) => ({ 15 | url: "ratingAndReview/edit", 16 | method: "POST", 17 | body: data, 18 | }), 19 | invalidatesTags: ["Course"], 20 | }), 21 | }), 22 | }); 23 | 24 | export const { useCreateRatingMutation, useEditRatingMutation } = 25 | ratingAndReviewApi; 26 | -------------------------------------------------------------------------------- /Frontend/src/reducers/api/userApi.js: -------------------------------------------------------------------------------- 1 | import { setCredentials } from "../authSlice"; 2 | import { apiSlice } from "./baseApi"; 3 | 4 | const profileData = ({ 5 | firstName = "", 6 | lastName = "", 7 | headline = "", 8 | biography = "", 9 | profilePicture = "", 10 | }) => { 11 | const formData = new FormData(); 12 | formData.append("firstName", firstName); 13 | formData.append("lastName", lastName); 14 | formData.append("headline", headline); 15 | formData.append("biography", biography); 16 | formData.append("profilePicture", profilePicture); 17 | return formData; 18 | }; 19 | export const instructorApi = apiSlice.injectEndpoints({ 20 | endpoints: (builder) => ({ 21 | editProfile: builder.mutation({ 22 | query: (data) => ({ 23 | url: "profile/edit", 24 | method: "POST", 25 | body: profileData(data), 26 | }), 27 | invalidatesTags: ["User"], 28 | }), 29 | 30 | getUserDetails: builder.query({ 31 | query: () => ({ 32 | url: "user/getUserDetails", 33 | method: "GET", 34 | }), 35 | transformResponse: (response) => response.data, 36 | providesTags: ["User"], 37 | }), 38 | refresh: builder.mutation({ 39 | query: () => ({ 40 | url: "/auth/refresh", 41 | method: "GET", 42 | }), 43 | async onQueryStarted(arg, { dispatch, queryFulfilled }) { 44 | try { 45 | const { data } = await queryFulfilled; 46 | const { accessToken } = data; 47 | dispatch(setCredentials(accessToken)); 48 | } catch (err) { 49 | console.log(err); 50 | } 51 | }, 52 | }), 53 | }), 54 | }); 55 | 56 | export const { useEditProfileMutation, useGetUserDetailsQuery } = instructorApi; 57 | -------------------------------------------------------------------------------- /Frontend/src/reducers/authSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const authSlice = createSlice({ 4 | name: "auth", 5 | initialState: { token: null }, 6 | reducers: { 7 | setCredentials: (state, action) => { 8 | state.token = action.payload; 9 | }, 10 | // eslint-disable-next-line no-unused-vars 11 | logOut: (state, action) => { 12 | state.token = null; 13 | }, 14 | }, 15 | }); 16 | 17 | export const { setCredentials, logOut } = authSlice.actions; 18 | 19 | export default authSlice.reducer; 20 | 21 | export const selectCurrentToken = (state) => state.auth.token; 22 | -------------------------------------------------------------------------------- /Frontend/src/state/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { setupListeners } from "@reduxjs/toolkit/query"; 3 | import { apiSlice } from "../reducers/api/baseApi"; 4 | import authReducer from "../reducers/authSlice"; 5 | 6 | export const store = configureStore({ 7 | reducer: { 8 | [apiSlice.reducerPath]: apiSlice.reducer, 9 | auth: authReducer, 10 | }, 11 | middleware: (getDefaultMiddleware) => 12 | getDefaultMiddleware().concat(apiSlice.middleware), 13 | }); 14 | 15 | setupListeners(store.dispatch); 16 | -------------------------------------------------------------------------------- /Frontend/src/utils/CourseInfo.js: -------------------------------------------------------------------------------- 1 | export function CourseInfo(courses) { 2 | let studentsEnrolled = 0; 3 | courses?.forEach((course) => { 4 | studentsEnrolled += course.studentsEnrolled.length; 5 | }); 6 | 7 | let totalRating = 0; 8 | let ratingLength = 0; 9 | courses?.forEach((course) => { 10 | course.ratingAndReviews.forEach((review) => { 11 | totalRating += review.rating; 12 | }); 13 | ratingLength += course.ratingAndReviews.length; 14 | }); 15 | 16 | let ratingAvg = 0; 17 | if (ratingLength > 0) { 18 | ratingAvg = totalRating / ratingLength; 19 | } 20 | 21 | return { 22 | studentsEnrolled: studentsEnrolled, 23 | ratingLength: ratingLength, 24 | ratingAvg: ratingAvg, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /Frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors: { 10 | 'light-gray': '#f7f9fa', 11 | }, 12 | } 13 | }, 14 | plugins: [], 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Learning Management System (Udemy Clone) 3 | 4 | Full Stack Project project build with MERN Stack along with other technologies. 5 | 6 | ![1](https://github.com/aditya7812/Learning-Management-System/assets/117096897/e4763f04-a250-46f3-9a4d-25e2f1e3b493) 7 | 8 | 9 | ## Tech Stack 10 | 11 | **Client:** React, Redux-Toolkit, RTK Query, React-Hook-Form, TailwindCSS, react-pdf 12 | 13 | **Server:** Node, Express, MongoDB, Stripe, JWT, Cloudinary 14 | 15 | 16 | ## Features 17 | 18 | 19 | - Login/Signup 20 | - Two Roles (Student, Instructor) 21 | 22 | #### For Student 23 | - Create/Edit Profile 24 | - See Published Courses 25 | - See Course Details 26 | - Purchase Course 27 | - See Course Progress 28 | - Download Certificate 29 | - Give/Edit Rating/Review to Enrolled Course 30 | - See All Enrolled Course 31 | 32 | #### For Instructor 33 | - Create Course 34 | - Create/Edit Course Details 35 | - Add Sections/SubSections to Course 36 | - Set Price, Category, Subcateogry Level, Language of Course 37 | 38 | ##### User can have both roles at a time 39 | 40 | 41 | ## Run Locally 42 | 43 | Clone the project 44 | 45 | ```bash 46 | git clone https://github.com/aditya7812/Learning-Management-System.git 47 | ``` 48 | 49 | Go to the project directory 50 | 51 | ```bash 52 | cd Learning-Management-System 53 | ``` 54 | 55 | Install backend dependencies 56 | 57 | ```bash 58 | cd Backend 59 | npm install 60 | ``` 61 | 62 | Start the server 63 | 64 | ```bash 65 | npm run dev 66 | ``` 67 | 68 | Install frontend dependencies 69 | 70 | ```bash 71 | cd Frontend 72 | npm install 73 | ``` 74 | 75 | Start the server 76 | 77 | ```bash 78 | npm run dev 79 | ``` 80 | 81 | 82 | ## Environment Variables 83 | 84 | To run this project, you will need to add the following environment variables to your .env file 85 | ### Frontend 86 | 87 | `VITE_STRIPE_PROMISE` 88 | 89 | ### Backend 90 | 91 | `ACCESS_TOKEN_SECRET` 92 | 93 | `REFRESH_TOKEN_SECRET` 94 | 95 | `MONGODB_URL` 96 | 97 | `CLOUDINARY_API_KEY` 98 | 99 | `CLOUDINARY_API_SECRET` 100 | 101 | `CLOUD_NAME` 102 | 103 | `FOLDER_NAME` 104 | 105 | `PROFILE_FOLDER_NAME` 106 | 107 | `STRIPE_KEY` 108 | 109 | 110 | 111 | ## Screenshots 112 | 113 | #### Home 114 | 115 | 116 | ![2](https://github.com/aditya7812/Learning-Management-System/assets/117096897/243de49b-0579-4b9f-b00d-61e22d43fe1b) 117 | 118 | 119 | #### Login 120 | 121 | ![3](https://github.com/aditya7812/Learning-Management-System/assets/117096897/acbf4fe2-8c29-46b2-9c8e-cee8f58f44fd) 122 | 123 | 124 | #### Signup 125 | 126 | ![4](https://github.com/aditya7812/Learning-Management-System/assets/117096897/7766a96c-7a96-495d-a3b7-1a3ca7a959cc) 127 | 128 | 129 | #### Course Details 130 | 131 | ![5](https://github.com/aditya7812/Learning-Management-System/assets/117096897/3a892ac0-a9cf-4d6e-8344-20ef2387ea0f) 132 | 133 | ![6](https://github.com/aditya7812/Learning-Management-System/assets/117096897/33aaf4ca-c5ab-467e-b7da-ecaa47af18be) 134 | 135 | 136 | #### Purchase 137 | 138 | ![7](https://github.com/aditya7812/Learning-Management-System/assets/117096897/3af8f350-52d2-4f20-bae1-ac5fce682617) 139 | 140 | 141 | #### Certificate 142 | 143 | ![8](https://github.com/aditya7812/Learning-Management-System/assets/117096897/5ffa28ae-9ba7-44e0-91fa-1f0ef81dc576) 144 | 145 | 146 | #### Rating and Review 147 | 148 | ![9](https://github.com/aditya7812/Learning-Management-System/assets/117096897/d709c9c8-8cee-454c-8375-8660329ff0df) 149 | 150 | 151 | #### Purchased Courses 152 | 153 | ![10](https://github.com/aditya7812/Learning-Management-System/assets/117096897/aa56a934-2a0b-4e9f-aa42-1b3166dc6047) 154 | 155 | 156 | #### Search 157 | 158 | ![11](https://github.com/aditya7812/Learning-Management-System/assets/117096897/ea233a29-32e1-4fc2-a088-992be4eb6ba4) 159 | 160 | 161 | #### Profile 162 | 163 | ![12](https://github.com/aditya7812/Learning-Management-System/assets/117096897/cb0d25a4-54b3-4cb0-a27a-f827bc2752dd) 164 | 165 | 166 | #### Instructor Dashboard 167 | 168 | ![13](https://github.com/aditya7812/Learning-Management-System/assets/117096897/d8d9613f-89ed-4100-86cd-67d07b518a80) 169 | 170 | 171 | #### Course Creation 172 | 173 | ![14](https://github.com/aditya7812/Learning-Management-System/assets/117096897/d5a1ae99-e556-43c0-8760-f9633ef3a32b) 174 | 175 | ![15](https://github.com/aditya7812/Learning-Management-System/assets/117096897/4a1ed44b-7c4f-4719-8b12-05e89e492fc8) 176 | 177 | ![16](https://github.com/aditya7812/Learning-Management-System/assets/117096897/7cf908be-c2ca-4837-8992-97c195e2c957) 178 | 179 | ![17](https://github.com/aditya7812/Learning-Management-System/assets/117096897/af630496-90de-4cf2-95e3-9d5a48f6dd6a) 180 | 181 | ![18](https://github.com/aditya7812/Learning-Management-System/assets/117096897/e7ccd102-153b-4d67-b191-8ab85b55e231) 182 | 183 | ![19](https://github.com/aditya7812/Learning-Management-System/assets/117096897/58f66824-6964-49e1-b9ac-07f23a4159f9) 184 | 185 | 186 | 187 | ## Roadmap 188 | 189 | - Form validation with Zod 190 | - Display Success/Error Notification to User 191 | - Responsive Design 192 | - Different Payment Features 193 | - Instructor Earning Dashboard 194 | - Filter Courses by price/rating/category 195 | 196 | ##### If you want any other features you can use pull request 197 | 198 | 199 | ## Refernce 200 | [Dave Gray Mern Stack Course](https://github.com/gitdagray/mern_stack_course) 201 | 202 | Love Babbar Megaproject Repository 203 | 204 | 205 | 206 | 207 | 208 | ## License 209 | 210 | [MIT](https://choosealicense.com/licenses/mit/) 211 | 212 | 213 | --------------------------------------------------------------------------------