├── README.md ├── images ├── mainpage.png └── schema.png ├── package-lock.json ├── package.json ├── public ├── index.html └── robots.txt ├── server ├── config │ ├── cloudinary.js │ ├── database.js │ └── razorpay.js ├── controllers │ ├── Auth.js │ ├── Category.js │ ├── ContactUs.js │ ├── Course.js │ ├── Payments.js │ ├── Profile.js │ ├── RatingAndReview.js │ ├── ResetPassword.js │ ├── Section.js │ ├── Subsection.js │ └── courseProgress.js ├── index.js ├── mail │ └── templates │ │ ├── contactFormRes.js │ │ ├── courseEnrollmentEmail.js │ │ ├── emailVerificationTemplate.js │ │ ├── passwordUpdate.js │ │ └── paymentSuccessEmail.js ├── middlewares │ └── auth.js ├── models │ ├── Category.js │ ├── Course.js │ ├── CourseProgress.js │ ├── OTP.js │ ├── Profile.js │ ├── RatingAndRaview.js │ ├── Section.js │ ├── SubSection.js │ └── User.js ├── package-lock.json ├── package.json ├── routes │ ├── Contact.js │ ├── Course.js │ ├── Payments.js │ ├── Profile.js │ └── User.js └── utils │ ├── imageUploader.js │ ├── mailSender.js │ └── secToDuration.js ├── src ├── .DS_Store ├── App.css ├── App.js ├── assets │ ├── Images │ │ ├── Compare_with_others.png │ │ ├── Compare_with_others.svg │ │ ├── FoundingStory.png │ │ ├── Instructor.png │ │ ├── Know_your_progress.png │ │ ├── Know_your_progress.svg │ │ ├── Plan_your_lessons.png │ │ ├── Plan_your_lessons.svg │ │ ├── TimelineImage.png │ │ ├── aboutus1.webp │ │ ├── aboutus2.webp │ │ ├── aboutus3.webp │ │ ├── banner.mp4 │ │ ├── bghome.svg │ │ ├── boxoffice.png │ │ ├── frame.png │ │ ├── login.webp │ │ └── signup.webp │ ├── Logo │ │ ├── Logo-Full-Dark.png │ │ ├── Logo-Full-Light.png │ │ ├── Logo-Small-Dark.png │ │ ├── Logo-Small-Light.png │ │ └── rzp_logo.png │ └── TimeLineLogo │ │ ├── Logo1.svg │ │ ├── Logo2.svg │ │ ├── Logo3.svg │ │ └── Logo4.svg ├── components │ ├── ContactPage │ │ ├── ContactDetails.jsx │ │ ├── ContactForm.jsx │ │ └── ContactUsForm.jsx │ ├── common │ │ ├── ConfirmationModal.jsx │ │ ├── Footer.jsx │ │ ├── IconBtn.jsx │ │ ├── Navbar.jsx │ │ ├── RatingStars.jsx │ │ ├── ReviewSlider.jsx │ │ └── Tab.jsx │ └── core │ │ ├── AboutPage │ │ ├── ContactFormSection.jsx │ │ ├── LearningGrid.jsx │ │ ├── Quote.jsx │ │ └── Stats.jsx │ │ ├── Auth │ │ ├── LoginForm.jsx │ │ ├── OpenRoute.jsx │ │ ├── PrivateRoute.jsx │ │ ├── ProfileDropDown.jsx │ │ ├── SignupForm.jsx │ │ └── Template.jsx │ │ ├── Catalog │ │ ├── CourseSlider.jsx │ │ └── Course_Card.jsx │ │ ├── Course │ │ ├── CourseAccordionBar.jsx │ │ ├── CourseDetailsCard.js │ │ └── CourseSubSectionAccordion.jsx │ │ ├── Dashboard │ │ ├── AddCourse │ │ │ ├── CourseBuilder │ │ │ │ ├── CourseBuilderForm.jsx │ │ │ │ ├── NestedView.jsx │ │ │ │ └── SubSectionModal.jsx │ │ │ ├── CourseInformation │ │ │ │ ├── ChipInput.jsx │ │ │ │ ├── CourseInformationForm.jsx │ │ │ │ └── RequirementField.jsx │ │ │ ├── PublishCourse │ │ │ │ └── index.jsx │ │ │ ├── RenderSteps.jsx │ │ │ ├── Upload.jsx │ │ │ └── index.jsx │ │ ├── Cart │ │ │ ├── RenderCartCourses.jsx │ │ │ ├── RenderTotalAmount.jsx │ │ │ └── index.jsx │ │ ├── EditCourse │ │ │ └── index.js │ │ ├── EnrolledCourses.jsx │ │ ├── InstructorCourses │ │ │ └── CoursesTable.jsx │ │ ├── InstructorDashboard │ │ │ ├── Instructor.jsx │ │ │ └── InstructorChart.jsx │ │ ├── MyCourses.jsx │ │ ├── MyProfile.jsx │ │ ├── Settings │ │ │ ├── ChangeProfilePicture.jsx │ │ │ ├── DeleteAccount.jsx │ │ │ ├── EditProfile.jsx │ │ │ ├── UpdatePassword.jsx │ │ │ └── index.jsx │ │ ├── Sidebar.jsx │ │ └── SidebarLink.jsx │ │ ├── HomePage │ │ ├── Button.jsx │ │ ├── CodeBlocks.jsx │ │ ├── CourseCard.jsx │ │ ├── ExploreMore.jsx │ │ ├── HighlightText.jsx │ │ ├── InstructorSection.jsx │ │ ├── LearningLanguageSection.jsx │ │ └── TimelineSection.jsx │ │ └── ViewCourse │ │ ├── CourseReviewModal.jsx │ │ ├── VideoDetails.jsx │ │ └── VideoDetailsSidebar.jsx ├── data │ ├── countrycode.json │ ├── dashboard-links.js │ ├── footer-links.js │ ├── homepage-explore.js │ └── navbar-links.js ├── hooks │ ├── useOnClickOutside.js │ └── useRouteMatch.js ├── index.css ├── index.js ├── pages │ ├── About.jsx │ ├── Catalog.jsx │ ├── Contact.jsx │ ├── CourseDetails.jsx │ ├── Dashboard.jsx │ ├── Error.jsx │ ├── ForgotPassword.jsx │ ├── Home.jsx │ ├── Login.jsx │ ├── Signup.jsx │ ├── UpdatePassword.jsx │ ├── VerifyEmail.jsx │ └── ViewCourse.jsx ├── reducer │ └── index.js ├── services │ ├── apiconnector.js │ ├── apis.js │ ├── formatDate.js │ └── operations │ │ ├── SettingsAPI.js │ │ ├── authAPI.js │ │ ├── courseDetailsAPI.js │ │ ├── pageAndComponentData.js │ │ ├── profileAPI.js │ │ └── studentFeaturesAPI.js ├── slices │ ├── authSlice.js │ ├── cartSlice.js │ ├── courseSlice.js │ ├── profileSlice.js │ └── viewCourseSlice.js └── utils │ ├── avgRating.js │ ├── constants.js │ └── dateFormatter.js └── tailwind.config.js /images/mainpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/images/mainpage.png -------------------------------------------------------------------------------- /images/schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/images/schema.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tailwind-css-starter-pack", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ramonak/react-progress-bar": "^5.0.3", 7 | "@reduxjs/toolkit": "^1.9.5", 8 | "axios": "^1.4.0", 9 | "chart.js": "^4.3.0", 10 | "concurrently": "^8.0.1", 11 | "copy-to-clipboard": "^3.3.3", 12 | "react": "^18.2.0", 13 | "react-chartjs-2": "^5.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-dropzone": "^14.2.3", 16 | "react-hook-form": "^7.44.2", 17 | "react-hot-toast": "^2.4.1", 18 | "react-icons": "^4.8.0", 19 | "react-markdown": "^8.0.7", 20 | "react-otp-input": "^3.0.2", 21 | "react-rating-stars-component": "^2.2.0", 22 | "react-redux": "^8.0.5", 23 | "react-router-dom": "^6.11.2", 24 | "react-scripts": "5.0.1", 25 | "react-super-responsive-table": "^5.2.1", 26 | "react-type-animation": "^3.0.1", 27 | "redux-toolkit": "^1.1.2", 28 | "swiper": "^9.4.1", 29 | "video-react": "^0.16.0", 30 | "web-vitals": "^2.1.4" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/thepranaygupta/react-tailwind-css-starter-pack.git" 35 | }, 36 | "author": "Pranay Gupta", 37 | "bugs": { 38 | "url": "https://github.com/thepranaygupta/react-tailwind-css-starter-pack/issues" 39 | }, 40 | "scripts": { 41 | "start": "react-scripts start", 42 | "build": "react-scripts build", 43 | "eject": "react-scripts eject", 44 | "server": "cd server && npm run dev", 45 | "dev": "concurrently -n \"client,server\" -c \"bgBlue,bgYellow\" \"npm start\" \"npm run server\"" 46 | }, 47 | "eslintConfig": { 48 | "extends": [ 49 | "react-app", 50 | "react-app/jest" 51 | ] 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.2%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | }, 65 | "devDependencies": { 66 | "tailwindcss": "^3.2.7" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React & Tailwind CSS Starter Pack 8 | 9 | 10 | 11 | 12 |
13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/config/cloudinary.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require("cloudinary").v2; //! Cloudinary is being required 2 | 3 | exports.cloudinaryConnect = () => { 4 | try { 5 | cloudinary.config({ 6 | //! ######## Configuring the Cloudinary to Upload MEDIA ######## 7 | cloud_name: process.env.CLOUD_NAME, 8 | api_key: process.env.API_KEY, 9 | api_secret: process.env.API_SECRET, 10 | }); 11 | } catch (error) { 12 | console.log(error); 13 | } 14 | }; -------------------------------------------------------------------------------- /server/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 | }; -------------------------------------------------------------------------------- /server/config/razorpay.js: -------------------------------------------------------------------------------- 1 | const Razorpay = require("razorpay"); 2 | 3 | 4 | exports.instance = new Razorpay({ 5 | key_id: process.env.RAZORPAY_KEY, 6 | key_secret: process.env.RAZORPAY_SECRET, 7 | }); -------------------------------------------------------------------------------- /server/controllers/Category.js: -------------------------------------------------------------------------------- 1 | const { Mongoose } = require("mongoose"); 2 | const Category = require("../models/Category"); 3 | function getRandomInt(max) { 4 | return Math.floor(Math.random() * max) 5 | } 6 | 7 | exports.createCategory = async (req, res) => { 8 | try { 9 | const { name, description } = req.body; 10 | if (!name) { 11 | return res 12 | .status(400) 13 | .json({ success: false, message: "All fields are required" }); 14 | } 15 | const CategorysDetails = await Category.create({ 16 | name: name, 17 | description: description, 18 | }); 19 | console.log(CategorysDetails); 20 | return res.status(200).json({ 21 | success: true, 22 | message: "Categorys Created Successfully", 23 | }); 24 | } catch (error) { 25 | return res.status(500).json({ 26 | success: true, 27 | message: error.message, 28 | }); 29 | } 30 | }; 31 | 32 | exports.showAllCategories = async (req, res) => { 33 | try { 34 | console.log("INSIDE SHOW ALL CATEGORIES"); 35 | const allCategorys = await Category.find({}); 36 | res.status(200).json({ 37 | success: true, 38 | data: allCategorys, 39 | }); 40 | } catch (error) { 41 | return res.status(500).json({ 42 | success: false, 43 | message: error.message, 44 | }); 45 | } 46 | }; 47 | 48 | //categoryPageDetails 49 | 50 | exports.categoryPageDetails = async (req, res) => { 51 | try { 52 | const { categoryId } = req.body 53 | console.log("PRINTING CATEGORY ID: ", categoryId); 54 | // Get courses for the specified category 55 | const selectedCategory = await Category.findById(categoryId) 56 | .populate({ 57 | path: "courses", 58 | match: { status: "Published" }, 59 | populate: "ratingAndReviews", 60 | }) 61 | .exec() 62 | 63 | //console.log("SELECTED COURSE", selectedCategory) 64 | // Handle the case when the category is not found 65 | if (!selectedCategory) { 66 | console.log("Category not found.") 67 | return res 68 | .status(404) 69 | .json({ success: false, message: "Category not found" }) 70 | } 71 | // Handle the case when there are no courses 72 | if (selectedCategory.courses.length === 0) { 73 | console.log("No courses found for the selected category.") 74 | return res.status(404).json({ 75 | success: false, 76 | message: "No courses found for the selected category.", 77 | }) 78 | } 79 | 80 | // Get courses for other categories 81 | const categoriesExceptSelected = await Category.find({ 82 | _id: { $ne: categoryId }, 83 | }) 84 | let differentCategory = await Category.findOne( 85 | categoriesExceptSelected[getRandomInt(categoriesExceptSelected.length)] 86 | ._id 87 | ) 88 | .populate({ 89 | path: "courses", 90 | match: { status: "Published" }, 91 | }) 92 | .exec() 93 | //console.log("Different COURSE", differentCategory) 94 | // Get top-selling courses across all categories 95 | const allCategories = await Category.find() 96 | .populate({ 97 | path: "courses", 98 | match: { status: "Published" }, 99 | populate: { 100 | path: "instructor", 101 | }, 102 | }) 103 | .exec() 104 | const allCourses = allCategories.flatMap((category) => category.courses) 105 | const mostSellingCourses = allCourses 106 | .sort((a, b) => b.sold - a.sold) 107 | .slice(0, 10) 108 | // console.log("mostSellingCourses COURSE", mostSellingCourses) 109 | res.status(200).json({ 110 | success: true, 111 | data: { 112 | selectedCategory, 113 | differentCategory, 114 | mostSellingCourses, 115 | }, 116 | }) 117 | } catch (error) { 118 | return res.status(500).json({ 119 | success: false, 120 | message: "Internal server error", 121 | error: error.message, 122 | }) 123 | } 124 | } -------------------------------------------------------------------------------- /server/controllers/ContactUs.js: -------------------------------------------------------------------------------- 1 | const { contactUsEmail } = require("../mail/templates/contactFormRes") 2 | const mailSender = require("../utils/mailSender") 3 | 4 | exports.contactUsController = async (req, res) => { 5 | const { email, firstname, lastname, message, phoneNo, countrycode } = req.body 6 | console.log(req.body) 7 | try { 8 | const emailRes = await mailSender( 9 | email, 10 | "Your Data send successfully", 11 | contactUsEmail(email, firstname, lastname, message, phoneNo, countrycode) 12 | ) 13 | console.log("Email Res ", emailRes) 14 | return res.json({ 15 | success: true, 16 | message: "Email send successfully", 17 | }) 18 | } catch (error) { 19 | console.log("Error", error) 20 | console.log("Error message :", error.message) 21 | return res.json({ 22 | success: false, 23 | message: "Something went wrong...", 24 | }) 25 | } 26 | } -------------------------------------------------------------------------------- /server/controllers/ResetPassword.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const mailSender = require("../utils/mailSender"); 3 | const bcrypt = require("bcryptjs"); 4 | const crypto = require("crypto"); 5 | 6 | exports.resetPasswordToken = async (req, res) => { 7 | try { 8 | const email = req.body.email; 9 | const user = await User.findOne({ email: email }); 10 | if (!user) { 11 | return res.json({ 12 | success: false, 13 | message: `This Email: ${email} is not Registered With Us Enter a Valid Email `, 14 | }); 15 | } 16 | const token = crypto.randomBytes(20).toString("hex"); 17 | 18 | const updatedDetails = await User.findOneAndUpdate( 19 | { email: email }, 20 | { 21 | token: token, 22 | resetPasswordExpires: Date.now() + 3600000, 23 | }, 24 | { new: true } 25 | ); 26 | console.log("DETAILS", updatedDetails); 27 | 28 | const url = `http://localhost:3000/update-password/${token}`; 29 | 30 | await mailSender( 31 | email, 32 | "Password Reset", 33 | `Your Link for email verification is ${url}. Please click this url to reset your password.` 34 | ); 35 | 36 | res.json({ 37 | success: true, 38 | message: 39 | "Email Sent Successfully, Please Check Your Email to Continue Further", 40 | }); 41 | } catch (error) { 42 | return res.json({ 43 | error: error.message, 44 | success: false, 45 | message: `Some Error in Sending the Reset Message`, 46 | }); 47 | } 48 | }; 49 | 50 | exports.resetPassword = async (req, res) => { 51 | try { 52 | const { password, confirmPassword, token } = req.body; 53 | 54 | if (confirmPassword !== password) { 55 | return res.json({ 56 | success: false, 57 | message: "Password and Confirm Password Does not Match", 58 | }); 59 | } 60 | const userDetails = await User.findOne({ token: token }); 61 | if (!userDetails) { 62 | return res.json({ 63 | success: false, 64 | message: "Token is Invalid", 65 | }); 66 | } 67 | if (!(userDetails.resetPasswordExpires > Date.now())) { 68 | return res.status(403).json({ 69 | success: false, 70 | message: `Token is Expired, Please Regenerate Your Token`, 71 | }); 72 | } 73 | const encryptedPassword = await bcrypt.hash(password, 10); 74 | await User.findOneAndUpdate( 75 | { token: token }, 76 | { password: encryptedPassword }, 77 | { new: true } 78 | ); 79 | res.json({ 80 | success: true, 81 | message: `Password Reset Successful`, 82 | }); 83 | } catch (error) { 84 | return res.json({ 85 | error: error.message, 86 | success: false, 87 | message: `Some Error in Updating the Password`, 88 | }); 89 | } 90 | }; -------------------------------------------------------------------------------- /server/controllers/Section.js: -------------------------------------------------------------------------------- 1 | const Section = require("../models/Section"); 2 | const Course = require("../models/Course"); 3 | const SubSection = require("../models/SubSection"); 4 | // CREATE a new section 5 | exports.createSection = async (req, res) => { 6 | try { 7 | // Extract the required properties from the request body 8 | const { sectionName, courseId } = req.body; 9 | 10 | // Validate the input 11 | if (!sectionName || !courseId) { 12 | return res.status(400).json({ 13 | success: false, 14 | message: "Missing required properties", 15 | }); 16 | } 17 | 18 | // Create a new section with the given name 19 | const newSection = await Section.create({ sectionName }); 20 | 21 | // Add the new section to the course's content array 22 | const updatedCourse = await Course.findByIdAndUpdate( 23 | courseId, 24 | { 25 | $push: { 26 | courseContent: newSection._id, 27 | }, 28 | }, 29 | { new: true } 30 | ) 31 | .populate({ 32 | path: "courseContent", 33 | populate: { 34 | path: "subSection", 35 | }, 36 | }) 37 | .exec(); 38 | 39 | // Return the updated course object in the response 40 | res.status(200).json({ 41 | success: true, 42 | message: "Section created successfully", 43 | updatedCourse, 44 | }); 45 | } catch (error) { 46 | // Handle errors 47 | res.status(500).json({ 48 | success: false, 49 | message: "Internal server error", 50 | error: error.message, 51 | }); 52 | } 53 | }; 54 | 55 | // UPDATE a section 56 | exports.updateSection = async (req, res) => { 57 | try { 58 | const { sectionName, sectionId,courseId } = req.body; 59 | const section = await Section.findByIdAndUpdate( 60 | sectionId, 61 | { sectionName }, 62 | { new: true } 63 | ); 64 | 65 | const course = await Course.findById(courseId) 66 | .populate({ 67 | path:"courseContent", 68 | populate:{ 69 | path:"subSection", 70 | }, 71 | }) 72 | .exec(); 73 | 74 | res.status(200).json({ 75 | success: true, 76 | message: section, 77 | data:course, 78 | }); 79 | } catch (error) { 80 | console.error("Error updating section:", error); 81 | res.status(500).json({ 82 | success: false, 83 | message: "Internal server error", 84 | }); 85 | } 86 | }; 87 | 88 | // DELETE a section 89 | exports.deleteSection = async (req, res) => { 90 | try { 91 | 92 | const { sectionId, courseId } = req.body; 93 | await Course.findByIdAndUpdate(courseId, { 94 | $pull: { 95 | courseContent: sectionId, 96 | } 97 | }) 98 | const section = await Section.findById(sectionId); 99 | console.log(sectionId, courseId); 100 | if(!section) { 101 | return res.status(404).json({ 102 | success:false, 103 | message:"Section not Found", 104 | }) 105 | } 106 | 107 | //delete sub section 108 | await SubSection.deleteMany({_id: {$in: section.subSection}}); 109 | 110 | await Section.findByIdAndDelete(sectionId); 111 | 112 | //find the updated course and return 113 | const course = await Course.findById(courseId).populate({ 114 | path:"courseContent", 115 | populate: { 116 | path: "subSection" 117 | } 118 | }) 119 | .exec(); 120 | 121 | res.status(200).json({ 122 | success:true, 123 | message:"Section deleted", 124 | data:course 125 | }); 126 | } catch (error) { 127 | console.error("Error deleting section:", error); 128 | res.status(500).json({ 129 | success: false, 130 | message: "Internal server error", 131 | }); 132 | } 133 | }; -------------------------------------------------------------------------------- /server/controllers/courseProgress.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | const Section = require("../models/Section") 3 | const SubSection = require("../models/SubSection") 4 | const CourseProgress = require("../models/CourseProgress") 5 | const Course = require("../models/Course") 6 | 7 | exports.updateCourseProgress = async (req, res) => { 8 | const { courseId, subsectionId } = req.body 9 | const userId = req.user.id 10 | 11 | try { 12 | // Check if the subsection is valid 13 | const subsection = await SubSection.findById(subsectionId) 14 | if (!subsection) { 15 | return res.status(404).json({ error: "Invalid subsection" }) 16 | } 17 | 18 | // Find the course progress document for the user and course 19 | let courseProgress = await CourseProgress.findOne({ 20 | courseID: courseId, 21 | userId: userId, 22 | }) 23 | 24 | if (!courseProgress) { 25 | // If course progress doesn't exist, create a new one 26 | return res.status(404).json({ 27 | success: false, 28 | message: "Course progress Does Not Exist", 29 | }) 30 | } else { 31 | // If course progress exists, check if the subsection is already completed 32 | if (courseProgress.completedVideos.includes(subsectionId)) { 33 | return res.status(400).json({ error: "Subsection already completed" }) 34 | } 35 | 36 | // Push the subsection into the completedVideos array 37 | courseProgress.completedVideos.push(subsectionId) 38 | } 39 | 40 | // Save the updated course progress 41 | await courseProgress.save() 42 | 43 | return res.status(200).json({ message: "Course progress updated" }) 44 | } catch (error) { 45 | console.error(error) 46 | return res.status(500).json({ error: "Internal server error" }) 47 | } 48 | } 49 | 50 | // exports.getProgressPercentage = async (req, res) => { 51 | // const { courseId } = req.body 52 | // const userId = req.user.id 53 | 54 | // if (!courseId) { 55 | // return res.status(400).json({ error: "Course ID not provided." }) 56 | // } 57 | 58 | // try { 59 | // // Find the course progress document for the user and course 60 | // let courseProgress = await CourseProgress.findOne({ 61 | // courseID: courseId, 62 | // userId: userId, 63 | // }) 64 | // .populate({ 65 | // path: "courseID", 66 | // populate: { 67 | // path: "courseContent", 68 | // }, 69 | // }) 70 | // .exec() 71 | 72 | // if (!courseProgress) { 73 | // return res 74 | // .status(400) 75 | // .json({ error: "Can not find Course Progress with these IDs." }) 76 | // } 77 | // console.log(courseProgress, userId) 78 | // let lectures = 0 79 | // courseProgress.courseID.courseContent?.forEach((sec) => { 80 | // lectures += sec.subSection.length || 0 81 | // }) 82 | 83 | // let progressPercentage = 84 | // (courseProgress.completedVideos.length / lectures) * 100 85 | 86 | // // To make it up to 2 decimal point 87 | // const multiplier = Math.pow(10, 2) 88 | // progressPercentage = 89 | // Math.round(progressPercentage * multiplier) / multiplier 90 | 91 | // return res.status(200).json({ 92 | // data: progressPercentage, 93 | // message: "Succesfully fetched Course progress", 94 | // }) 95 | // } catch (error) { 96 | // console.error(error) 97 | // return res.status(500).json({ error: "Internal server error" }) 98 | // } 99 | // } -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | 4 | const userRoutes = require("./routes/User"); 5 | const profileRoutes = require("./routes/Profile"); 6 | const paymentRoutes = require("./routes/Payments"); 7 | const courseRoutes = require("./routes/Course"); 8 | const contactUsRoute = require("./routes/Contact"); 9 | const database = require("./config/database"); 10 | const cookieParser = require("cookie-parser"); 11 | const cors = require("cors"); 12 | const {cloudinaryConnect } = require("./config/cloudinary"); 13 | const fileUpload = require("express-fileupload"); 14 | const dotenv = require("dotenv"); 15 | 16 | dotenv.config(); 17 | const PORT = process.env.PORT || 4000; 18 | 19 | //database connect 20 | database.connect(); 21 | //middlewares 22 | app.use(express.json()); 23 | app.use(cookieParser()); 24 | app.use( 25 | cors({ 26 | origin:"http://localhost:3000", 27 | credentials:true, 28 | }) 29 | ) 30 | 31 | app.use( 32 | fileUpload({ 33 | useTempFiles:true, 34 | tempFileDir:"/tmp", 35 | }) 36 | ) 37 | //cloudinary connection 38 | cloudinaryConnect(); 39 | 40 | //routes 41 | app.use("/api/v1/auth", userRoutes); 42 | app.use("/api/v1/profile", profileRoutes); 43 | app.use("/api/v1/course", courseRoutes); 44 | app.use("/api/v1/payment", paymentRoutes); 45 | app.use("/api/v1/reach", contactUsRoute); 46 | 47 | //def route 48 | 49 | app.get("/", (req, res) => { 50 | return res.json({ 51 | success:true, 52 | message:'Your server is up and running....' 53 | }); 54 | }); 55 | 56 | app.listen(PORT, () => { 57 | console.log(`App is running at ${PORT}`) 58 | }) 59 | 60 | -------------------------------------------------------------------------------- /server/mail/templates/contactFormRes.js: -------------------------------------------------------------------------------- 1 | exports.contactUsEmail = ( 2 | email, 3 | firstname, 4 | lastname, 5 | message, 6 | phoneNo, 7 | countrycode 8 | ) => { 9 | return ` 10 | 11 | 12 | 13 | 14 | Contact Form Confirmation 15 | 72 | 73 | 74 | 75 | 76 |
77 | 79 |
Contact Form Confirmation
80 |
81 |

Dear ${firstname} ${lastname},

82 |

Thank you for contacting us. We have received your message and will respond to you as soon as possible. 83 |

84 |

Here are the details you provided:

85 |

Name: ${firstname} ${lastname}

86 |

Email: ${email}

87 |

Phone Number: ${phoneNo}

88 |

Message: ${message}

89 |

We appreciate your interest and will get back to you shortly.

90 |
91 |
If you have any further questions or need immediate assistance, please feel free to reach 92 | out to us at info@studynotion.com. We are here to help!
93 |
94 | 95 | 96 | ` 97 | } -------------------------------------------------------------------------------- /server/mail/templates/courseEnrollmentEmail.js: -------------------------------------------------------------------------------- 1 | exports.courseEnrollmentEmail = (courseName, name) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | Course Registration Confirmation 8 | 65 | 66 | 67 | 68 | 69 |
70 | 72 |
Course Registration Confirmation
73 |
74 |

Dear ${name},

75 |

You have successfully registered for the course "${courseName}". We 76 | are excited to have you as a participant!

77 |

Please log in to your learning dashboard to access the course materials and start your learning journey. 78 |

79 | Go to Dashboard 80 |
81 |
If you have any questions or need assistance, please feel free to reach out to us at info@studynotion.com. We are here to help!
83 |
84 | 85 | 86 | `; 87 | }; -------------------------------------------------------------------------------- /server/mail/templates/emailVerificationTemplate.js: -------------------------------------------------------------------------------- 1 | const otpTemplate = (otp) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | OTP Verification Email 8 | 64 | 65 | 66 | 67 | 68 |
69 | 71 |
OTP Verification Email
72 |
73 |

Dear User,

74 |

Thank you for registering with StudyNotion. To complete your registration, please use the following OTP 75 | (One-Time Password) to verify your account:

76 |

${otp}

77 |

This OTP is valid for 5 minutes. If you did not request this verification, please disregard this email. 78 | Once your account is verified, you will have access to our platform and its features.

79 |
80 |
If you have any questions or need assistance, please feel free to reach out to us at info@studynotion.com. We are here to help!
82 |
83 | 84 | 85 | `; 86 | }; 87 | module.exports = otpTemplate; -------------------------------------------------------------------------------- /server/mail/templates/passwordUpdate.js: -------------------------------------------------------------------------------- 1 | exports.passwordUpdated = (email, name) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | Password Update Confirmation 8 | 53 | 54 | 55 | 56 | 57 |
58 | 60 |
Password Update Confirmation
61 |
62 |

Hey ${name},

63 |

Your password has been successfully updated for the email ${email}. 64 |

65 |

If you did not request this password change, please contact us immediately to secure your account.

66 |
67 |
If you have any questions or need further assistance, please feel free to reach out to us 68 | at 69 | info@studynotion.com. We are here to help! 70 |
71 |
72 | 73 | 74 | `; 75 | }; -------------------------------------------------------------------------------- /server/mail/templates/paymentSuccessEmail.js: -------------------------------------------------------------------------------- 1 | exports.paymentSuccessEmail = (name, amount, orderId, paymentId) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | Payment Confirmation 8 | 65 | 66 | 67 | 68 | 69 |
70 | 72 |
Course Payment Confirmation
73 |
74 |

Dear ${name},

75 |

We have received a payment of ₹${amount}

. 76 |

Your Payment ID is ${paymentId}

77 |

Your Order ID is ${orderId}

78 |
79 |
If you have any questions or need assistance, please feel free to reach out to us at info@studynotion.com. We are here to help!
81 |
82 | 83 | 84 | ` 85 | } -------------------------------------------------------------------------------- /server/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | // Importing required modules 2 | const jwt = require("jsonwebtoken"); 3 | const dotenv = require("dotenv"); 4 | const User = require("../models/User"); 5 | // Configuring dotenv to load environment variables from .env file 6 | dotenv.config(); 7 | 8 | // This function is used as middleware to authenticate user requests 9 | exports.auth = async (req, res, next) => { 10 | try { 11 | // Extracting JWT from request cookies, body or header 12 | const token = 13 | req.cookies.token || 14 | req.body.token || 15 | req.header("Authorization").replace("Bearer ", ""); 16 | 17 | // If JWT is missing, return 401 Unauthorized response 18 | if (!token) { 19 | return res.status(401).json({ success: false, message: `Token Missing` }); 20 | } 21 | 22 | try { 23 | // Verifying the JWT using the secret key stored in environment variables 24 | const decode = await jwt.verify(token, process.env.JWT_SECRET); 25 | console.log(decode); 26 | // Storing the decoded JWT payload in the request object for further use 27 | req.user = decode; 28 | } catch (error) { 29 | // If JWT verification fails, return 401 Unauthorized response 30 | return res 31 | .status(401) 32 | .json({ success: false, message: "token is invalid" }); 33 | } 34 | 35 | // If JWT is valid, move on to the next middleware or request handler 36 | next(); 37 | } catch (error) { 38 | // If there is an error during the authentication process, return 401 Unauthorized response 39 | return res.status(401).json({ 40 | success: false, 41 | message: `Something Went Wrong While Validating the Token`, 42 | }); 43 | } 44 | }; 45 | exports.isStudent = async (req, res, next) => { 46 | try { 47 | const userDetails = await User.findOne({ email: req.user.email }); 48 | 49 | if (userDetails.accountType !== "Student") { 50 | return res.status(401).json({ 51 | success: false, 52 | message: "This is a Protected Route for Students", 53 | }); 54 | } 55 | next(); 56 | } catch (error) { 57 | return res 58 | .status(500) 59 | .json({ success: false, message: `User Role Can't be Verified` }); 60 | } 61 | }; 62 | exports.isAdmin = async (req, res, next) => { 63 | try { 64 | const userDetails = await User.findOne({ email: req.user.email }); 65 | 66 | if (userDetails.accountType !== "Admin") { 67 | return res.status(401).json({ 68 | success: false, 69 | message: "This is a Protected Route for Admin", 70 | }); 71 | } 72 | next(); 73 | } catch (error) { 74 | return res 75 | .status(500) 76 | .json({ success: false, message: `User Role Can't be Verified` }); 77 | } 78 | }; 79 | exports.isInstructor = async (req, res, next) => { 80 | try { 81 | const userDetails = await User.findOne({ email: req.user.email }); 82 | console.log(userDetails); 83 | 84 | console.log(userDetails.accountType); 85 | 86 | if (userDetails.accountType !== "Instructor") { 87 | return res.status(401).json({ 88 | success: false, 89 | message: "This is a Protected Route for Instructor", 90 | }); 91 | } 92 | next(); 93 | } catch (error) { 94 | return res 95 | .status(500) 96 | .json({ success: false, message: `User Role Can't be Verified` }); 97 | } 98 | }; -------------------------------------------------------------------------------- /server/models/Category.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define the Tags schema 4 | const categorySchema = new mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | description: { type: String }, 10 | courses: [ 11 | { 12 | type: mongoose.Schema.Types.ObjectId, 13 | ref: "Course", 14 | }, 15 | ], 16 | }); 17 | 18 | // Export the Tags model 19 | module.exports = mongoose.model("Category", categorySchema); -------------------------------------------------------------------------------- /server/models/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define the Courses schema 4 | const coursesSchema = new mongoose.Schema({ 5 | courseName: { type: String }, 6 | courseDescription: { type: String }, 7 | instructor: { 8 | type: mongoose.Schema.Types.ObjectId, 9 | required: true, 10 | ref: "user", 11 | }, 12 | whatYouWillLearn: { 13 | type: String, 14 | }, 15 | courseContent: [ 16 | { 17 | type: mongoose.Schema.Types.ObjectId, 18 | ref: "Section", 19 | }, 20 | ], 21 | ratingAndReviews: [ 22 | { 23 | type: mongoose.Schema.Types.ObjectId, 24 | ref: "RatingAndReview", 25 | }, 26 | ], 27 | price: { 28 | type: Number, 29 | }, 30 | thumbnail: { 31 | type: String, 32 | }, 33 | tag: { 34 | type: [String], 35 | required: true, 36 | }, 37 | category: { 38 | type: mongoose.Schema.Types.ObjectId, 39 | // required: true, 40 | ref: "Category", 41 | }, 42 | studentsEnrolled: [ 43 | { 44 | type: mongoose.Schema.Types.ObjectId, 45 | required: true, 46 | ref: "user", 47 | }, 48 | ], 49 | instructions: { 50 | type: [String], 51 | }, 52 | status: { 53 | type: String, 54 | enum: ["Draft", "Published"], 55 | }, 56 | createdAt: { 57 | type:Date, 58 | default:Date.now 59 | }, 60 | }); 61 | 62 | // Export the Courses model 63 | module.exports = mongoose.model("Course", coursesSchema); -------------------------------------------------------------------------------- /server/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) -------------------------------------------------------------------------------- /server/models/OTP.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const mailSender = require("../utils/mailSender"); 3 | const emailTemplate = require("../mail/templates/emailVerificationTemplate"); 4 | const OTPSchema = new mongoose.Schema({ 5 | email: { 6 | type: String, 7 | required: true, 8 | }, 9 | otp: { 10 | type: String, 11 | required: true, 12 | }, 13 | createdAt: { 14 | type: Date, 15 | default: Date.now, 16 | expires: 60 * 5, // The document will be automatically deleted after 5 minutes of its creation time 17 | }, 18 | }); 19 | 20 | // Define a function to send emails 21 | async function sendVerificationEmail(email, otp) { 22 | // Create a transporter to send emails 23 | 24 | // Define the email options 25 | 26 | // Send the email 27 | try { 28 | const mailResponse = await mailSender( 29 | email, 30 | "Verification Email", 31 | emailTemplate(otp) 32 | ); 33 | console.log("Email sent successfully: ", mailResponse.response); 34 | } catch (error) { 35 | console.log("Error occurred while sending email: ", error); 36 | throw error; 37 | } 38 | } 39 | 40 | // Define a post-save hook to send email after the document has been saved 41 | OTPSchema.pre("save", async function (next) { 42 | console.log("New document saved to database"); 43 | 44 | // Only send an email when a new document is created 45 | if (this.isNew) { 46 | await sendVerificationEmail(this.email, this.otp); 47 | } 48 | next(); 49 | }); 50 | 51 | const OTP = mongoose.model("OTP", OTPSchema); 52 | 53 | module.exports = OTP; -------------------------------------------------------------------------------- /server/models/Profile.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define the Profile schema 4 | const profileSchema = new mongoose.Schema({ 5 | gender: { 6 | type: String, 7 | }, 8 | dateOfBirth: { 9 | type: String, 10 | }, 11 | about: { 12 | type: String, 13 | trim: true, 14 | }, 15 | contactNumber: { 16 | type: Number, 17 | trim: true, 18 | }, 19 | }); 20 | 21 | // Export the Profile model 22 | module.exports = mongoose.model("Profile", profileSchema); 23 | -------------------------------------------------------------------------------- /server/models/RatingAndRaview.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define the RatingAndReview schema 4 | const ratingAndReviewSchema = new mongoose.Schema({ 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: "user", 9 | }, 10 | rating: { 11 | type: Number, 12 | required: true, 13 | }, 14 | review: { 15 | type: String, 16 | required: true, 17 | }, 18 | course: { 19 | type: mongoose.Schema.Types.ObjectId, 20 | required: true, 21 | ref: "Course", 22 | index: true, 23 | }, 24 | }); 25 | 26 | // Export the RatingAndReview model 27 | module.exports = mongoose.model("RatingAndReview", ratingAndReviewSchema); -------------------------------------------------------------------------------- /server/models/Section.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define the Section schema 4 | const sectionSchema = new mongoose.Schema({ 5 | sectionName: { 6 | type: String, 7 | }, 8 | subSection: [ 9 | { 10 | type: mongoose.Schema.Types.ObjectId, 11 | required: true, 12 | ref: "SubSection", 13 | }, 14 | ], 15 | }); 16 | 17 | // Export the Section model 18 | module.exports = mongoose.model("Section", sectionSchema); -------------------------------------------------------------------------------- /server/models/SubSection.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const SubSectionSchema = new mongoose.Schema({ 4 | title: { type: String }, 5 | timeDuration: { type: String }, 6 | description: { type: String }, 7 | videoUrl: { type: String }, 8 | }); 9 | 10 | module.exports = mongoose.model("SubSection", SubSectionSchema); -------------------------------------------------------------------------------- /server/models/User.js: -------------------------------------------------------------------------------- 1 | // Import the Mongoose library 2 | const mongoose = require("mongoose"); 3 | 4 | // Define the user schema using the Mongoose Schema constructor 5 | const userSchema = new mongoose.Schema( 6 | { 7 | // Define the name field with type String, required, and trimmed 8 | firstName: { 9 | type: String, 10 | required: true, 11 | trim: true, 12 | }, 13 | lastName: { 14 | type: String, 15 | required: true, 16 | trim: true, 17 | }, 18 | // Define the email field with type String, required, and trimmed 19 | email: { 20 | type: String, 21 | required: true, 22 | trim: true, 23 | }, 24 | 25 | // Define the password field with type String and required 26 | password: { 27 | type: String, 28 | required: true, 29 | }, 30 | // Define the role field with type String and enum values of "Admin", "Student", or "Visitor" 31 | accountType: { 32 | type: String, 33 | enum: ["Admin", "Student", "Instructor"], 34 | required: true, 35 | }, 36 | active: { 37 | type: Boolean, 38 | default: true, 39 | }, 40 | approved: { 41 | type: Boolean, 42 | default: true, 43 | }, 44 | additionalDetails: { 45 | type: mongoose.Schema.Types.ObjectId, 46 | required: true, 47 | ref: "Profile", 48 | }, 49 | courses: [ 50 | { 51 | type: mongoose.Schema.Types.ObjectId, 52 | ref: "Course", 53 | }, 54 | ], 55 | token: { 56 | type: String, 57 | }, 58 | resetPasswordExpires: { 59 | type: Date, 60 | }, 61 | image: { 62 | type: String, 63 | required: true, 64 | }, 65 | courseProgress: [ 66 | { 67 | type: mongoose.Schema.Types.ObjectId, 68 | ref: "courseProgress", 69 | }, 70 | ], 71 | 72 | // Add timestamps for when the document is created and last modified 73 | }, 74 | { timestamps: true } 75 | ); 76 | 77 | // Export the Mongoose model for the user schema, using the name "user" 78 | module.exports = mongoose.model("user", userSchema); -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcrypt": "^5.1.0", 15 | "bcryptjs": "^2.4.3", 16 | "cloudinary": "^1.36.4", 17 | "cookie-parser": "^1.4.6", 18 | "cors": "^2.8.5", 19 | "crypto-random-string": "^5.0.0", 20 | "dotenv": "^16.0.3", 21 | "express": "^4.18.2", 22 | "express-fileupload": "^1.4.0", 23 | "jsonwebtoken": "^9.0.0", 24 | "mongoose": "^7.0.3", 25 | "node-schedule": "^2.1.1", 26 | "nodemailer": "^6.9.1", 27 | "nodemon": "^2.0.22", 28 | "otp-generator": "^4.0.1", 29 | "razorpay": "^2.8.6" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/routes/Contact.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const { contactUsController } = require("../controllers/ContactUs") 4 | 5 | router.post("/contact", contactUsController) 6 | 7 | module.exports = router -------------------------------------------------------------------------------- /server/routes/Course.js: -------------------------------------------------------------------------------- 1 | // Import the required modules 2 | const express = require("express") 3 | const router = express.Router() 4 | 5 | // Import the Controllers 6 | 7 | // Course Controllers Import 8 | const { 9 | createCourse, 10 | getAllCourses, 11 | getCourseDetails, 12 | getFullCourseDetails, 13 | editCourse, 14 | getInstructorCourses, 15 | deleteCourse, 16 | } = require("../controllers/Course") 17 | 18 | 19 | // Categories Controllers Import 20 | const { 21 | showAllCategories, 22 | createCategory, 23 | categoryPageDetails, 24 | } = require("../controllers/Category") 25 | 26 | // Sections Controllers Import 27 | const { 28 | createSection, 29 | updateSection, 30 | deleteSection, 31 | } = require("../controllers/Section") 32 | 33 | // Sub-Sections Controllers Import 34 | const { 35 | createSubSection, 36 | updateSubSection, 37 | deleteSubSection, 38 | } = require("../controllers/Subsection") 39 | 40 | // Rating Controllers Import 41 | const { 42 | createRating, 43 | getAverageRating, 44 | getAllRating, 45 | } = require("../controllers/RatingAndReview") 46 | 47 | const { 48 | updateCourseProgress 49 | } = require("../controllers/courseProgress"); 50 | 51 | // Importing Middlewares 52 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth") 53 | 54 | // ******************************************************************************************************** 55 | // Course routes 56 | // ******************************************************************************************************** 57 | 58 | // Courses can Only be Created by Instructors 59 | router.post("/createCourse", auth, isInstructor, createCourse) 60 | //Add a Section to a Course 61 | router.post("/addSection", auth, isInstructor, createSection) 62 | // Update a Section 63 | router.post("/updateSection", auth, isInstructor, updateSection) 64 | // Delete a Section 65 | router.post("/deleteSection", auth, isInstructor, deleteSection) 66 | // Edit Sub Section 67 | router.post("/updateSubSection", auth, isInstructor, updateSubSection) 68 | // Delete Sub Section 69 | router.post("/deleteSubSection", auth, isInstructor, deleteSubSection) 70 | // Add a Sub Section to a Section 71 | router.post("/addSubSection", auth, isInstructor, createSubSection) 72 | // Get all Registered Courses 73 | router.get("/getAllCourses", getAllCourses) 74 | // Get Details for a Specific Courses 75 | router.post("/getCourseDetails", getCourseDetails) 76 | // Get Details for a Specific Courses 77 | router.post("/getFullCourseDetails", auth, getFullCourseDetails) 78 | // Edit Course routes 79 | router.post("/editCourse", auth, isInstructor, editCourse) 80 | // Get all Courses Under a Specific Instructor 81 | router.get("/getInstructorCourses", auth, isInstructor, getInstructorCourses) 82 | // Delete a Course 83 | router.delete("/deleteCourse", deleteCourse) 84 | 85 | router.post("/updateCourseProgress", auth, isStudent, updateCourseProgress); 86 | 87 | // ******************************************************************************************************** 88 | // Category routes (Only by Admin) 89 | // ******************************************************************************************************** 90 | // Category can Only be Created by Admin 91 | // TODO: Put IsAdmin Middleware here 92 | router.post("/createCategory", auth, isAdmin, createCategory) 93 | router.get("/showAllCategories", showAllCategories) 94 | router.post("/getCategoryPageDetails", categoryPageDetails) 95 | 96 | // ******************************************************************************************************** 97 | // Rating and Review 98 | // ******************************************************************************************************** 99 | router.post("/createRating", auth, isStudent, createRating) 100 | router.get("/getAverageRating", getAverageRating) 101 | router.get("/getReviews", getAllRating) 102 | 103 | module.exports = router -------------------------------------------------------------------------------- /server/routes/Payments.js: -------------------------------------------------------------------------------- 1 | // Import the required modules 2 | const express = require("express") 3 | const router = express.Router() 4 | 5 | const { capturePayment, verifyPayment, sendPaymentSuccessEmail } = require("../controllers/Payments") 6 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth") 7 | router.post("/capturePayment", auth, isStudent, capturePayment) 8 | router.post("/verifyPayment",auth, isStudent, verifyPayment) 9 | router.post("/sendPaymentSuccessEmail", auth, isStudent, sendPaymentSuccessEmail); 10 | 11 | module.exports = router -------------------------------------------------------------------------------- /server/routes/Profile.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const { auth, isInstructor } = require("../middlewares/auth") 4 | const { 5 | deleteAccount, 6 | updateProfile, 7 | getAllUserDetails, 8 | updateDisplayPicture, 9 | getEnrolledCourses, 10 | instructorDashboard, 11 | } = require("../controllers/Profile") 12 | 13 | // ******************************************************************************************************** 14 | // Profile routes 15 | // ******************************************************************************************************** 16 | // Delet User Account 17 | router.delete("/deleteProfile", auth, deleteAccount) 18 | router.put("/updateProfile", auth, updateProfile) 19 | router.get("/getUserDetails", auth, getAllUserDetails) 20 | // Get Enrolled Courses 21 | router.get("/getEnrolledCourses", auth, getEnrolledCourses) 22 | router.put("/updateDisplayPicture", auth, updateDisplayPicture) 23 | router.get("/instructorDashboard", auth, isInstructor, instructorDashboard) 24 | 25 | module.exports = router -------------------------------------------------------------------------------- /server/routes/User.js: -------------------------------------------------------------------------------- 1 | // Import the required modules 2 | const express = require("express") 3 | const router = express.Router() 4 | 5 | // Import the required controllers and middleware functions 6 | const { 7 | login, 8 | signup, 9 | sendotp, 10 | changePassword, 11 | } = require("../controllers/Auth") 12 | const { 13 | resetPasswordToken, 14 | resetPassword, 15 | } = require("../controllers/ResetPassword") 16 | 17 | const { auth } = require("../middlewares/auth") 18 | 19 | // Routes for Login, Signup, and Authentication 20 | 21 | // ******************************************************************************************************** 22 | // Authentication routes 23 | // ******************************************************************************************************** 24 | 25 | // Route for user login 26 | router.post("/login", login) 27 | 28 | // Route for user signup 29 | router.post("/signup", signup) 30 | 31 | // Route for sending OTP to the user's email 32 | router.post("/sendotp", sendotp) 33 | 34 | // Route for Changing the password 35 | router.post("/changepassword", auth, changePassword) 36 | 37 | // ******************************************************************************************************** 38 | // Reset Password 39 | // ******************************************************************************************************** 40 | 41 | // Route for generating a reset password token 42 | router.post("/reset-password-token", resetPasswordToken) 43 | 44 | // Route for resetting user's password after verification 45 | router.post("/reset-password", resetPassword) 46 | 47 | // Export the router for use in the main application 48 | module.exports = router -------------------------------------------------------------------------------- /server/utils/imageUploader.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require('cloudinary').v2 2 | 3 | 4 | exports.uploadImageToCloudinary = async (file, folder, height, quality) => { 5 | const options = {folder}; 6 | if(height) { 7 | options.height = height; 8 | } 9 | if(quality) { 10 | options.quality = quality; 11 | } 12 | options.resource_type = "auto"; 13 | 14 | return await cloudinary.uploader.upload(file.tempFilePath, options); 15 | } -------------------------------------------------------------------------------- /server/utils/mailSender.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | 3 | const mailSender = async (email, title, body) => { 4 | try{ 5 | let transporter = nodemailer.createTransport({ 6 | host:process.env.MAIL_HOST, 7 | auth:{ 8 | user: process.env.MAIL_USER, 9 | pass: process.env.MAIL_PASS, 10 | } 11 | }) 12 | 13 | 14 | let info = await transporter.sendMail({ 15 | from: 'StudyNotion || CodeHelp - by Babbar', 16 | to:`${email}`, 17 | subject: `${title}`, 18 | html: `${body}`, 19 | }) 20 | console.log(info); 21 | return info; 22 | } 23 | catch(error) { 24 | console.log(error.message); 25 | } 26 | } 27 | 28 | 29 | module.exports = mailSender; -------------------------------------------------------------------------------- /server/utils/secToDuration.js: -------------------------------------------------------------------------------- 1 | // Helper function to convert total seconds to the duration format 2 | function convertSecondsToDuration(totalSeconds) { 3 | const hours = Math.floor(totalSeconds / 3600) 4 | const minutes = Math.floor((totalSeconds % 3600) / 60) 5 | const seconds = Math.floor((totalSeconds % 3600) % 60) 6 | 7 | if (hours > 0) { 8 | return `${hours}h ${minutes}m` 9 | } else if (minutes > 0) { 10 | return `${minutes}m ${seconds}s` 11 | } else { 12 | return `${seconds}s` 13 | } 14 | } 15 | 16 | module.exports = { 17 | convertSecondsToDuration, 18 | } -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/.DS_Store -------------------------------------------------------------------------------- /src/assets/Images/Compare_with_others.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Compare_with_others.png -------------------------------------------------------------------------------- /src/assets/Images/FoundingStory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/FoundingStory.png -------------------------------------------------------------------------------- /src/assets/Images/Instructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Instructor.png -------------------------------------------------------------------------------- /src/assets/Images/Know_your_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Know_your_progress.png -------------------------------------------------------------------------------- /src/assets/Images/Plan_your_lessons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Plan_your_lessons.png -------------------------------------------------------------------------------- /src/assets/Images/TimelineImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/TimelineImage.png -------------------------------------------------------------------------------- /src/assets/Images/aboutus1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/aboutus1.webp -------------------------------------------------------------------------------- /src/assets/Images/aboutus2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/aboutus2.webp -------------------------------------------------------------------------------- /src/assets/Images/aboutus3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/aboutus3.webp -------------------------------------------------------------------------------- /src/assets/Images/banner.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/banner.mp4 -------------------------------------------------------------------------------- /src/assets/Images/boxoffice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/boxoffice.png -------------------------------------------------------------------------------- /src/assets/Images/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/frame.png -------------------------------------------------------------------------------- /src/assets/Images/login.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/login.webp -------------------------------------------------------------------------------- /src/assets/Images/signup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/signup.webp -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Full-Dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Full-Dark.png -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Full-Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Full-Light.png -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Small-Dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Small-Dark.png -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Small-Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Small-Light.png -------------------------------------------------------------------------------- /src/assets/Logo/rzp_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/rzp_logo.png -------------------------------------------------------------------------------- /src/assets/TimeLineLogo/Logo1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/TimeLineLogo/Logo2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/TimeLineLogo/Logo3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/TimeLineLogo/Logo4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/ContactPage/ContactDetails.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import * as Icon1 from "react-icons/bi" 3 | import * as Icon3 from "react-icons/hi2" 4 | import * as Icon2 from "react-icons/io5" 5 | 6 | const contactDetails = [ 7 | { 8 | icon: "HiChatBubbleLeftRight", 9 | heading: "Chat on us", 10 | description: "Our friendly team is here to help.", 11 | details: "info@studynotion.com", 12 | }, 13 | { 14 | icon: "BiWorld", 15 | heading: "Visit us", 16 | description: "Come and say hello at our office HQ.", 17 | details: 18 | "Akshya Nagar 1st Block 1st Cross, Rammurthy nagar, Bangalore-560016", 19 | }, 20 | { 21 | icon: "IoCall", 22 | heading: "Call us", 23 | description: "Mon - Fri From 8am to 5pm", 24 | details: "+123 456 7869", 25 | }, 26 | ] 27 | 28 | const ContactDetails = () => { 29 | return ( 30 |
31 | {contactDetails.map((ele, i) => { 32 | let Icon = Icon1[ele.icon] || Icon2[ele.icon] || Icon3[ele.icon] 33 | return ( 34 |
38 |
39 | 40 |

41 | {ele?.heading} 42 |

43 |
44 |

{ele?.description}

45 |

{ele?.details}

46 |
47 | ) 48 | })} 49 |
50 | ) 51 | } 52 | 53 | export default ContactDetails -------------------------------------------------------------------------------- /src/components/ContactPage/ContactForm.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ContactUsForm from "./ContactUsForm"; 3 | 4 | const ContactForm = () => { 5 | return ( 6 |
7 |

8 | Got a Idea? We've got the skills. Let's team up 9 |

10 |

11 | Tell us more about yourself and what you're got in mind. 12 |

13 | 14 |
15 | 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default ContactForm; -------------------------------------------------------------------------------- /src/components/common/ConfirmationModal.jsx: -------------------------------------------------------------------------------- 1 | import IconBtn from "./IconBtn" 2 | 3 | export default function ConfirmationModal({ modalData }) { 4 | return ( 5 |
6 |
7 |

8 | {modalData?.text1} 9 |

10 |

11 | {modalData?.text2} 12 |

13 |
14 | 18 | 24 |
25 |
26 |
27 | ) 28 | } -------------------------------------------------------------------------------- /src/components/common/IconBtn.jsx: -------------------------------------------------------------------------------- 1 | export default function IconBtn({ 2 | text, 3 | onclick, 4 | children, 5 | disabled, 6 | outline = false, 7 | customClasses, 8 | type, 9 | }) { 10 | return ( 11 | 28 | ) 29 | } -------------------------------------------------------------------------------- /src/components/common/RatingStars.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | import { 3 | TiStarFullOutline, 4 | TiStarHalfOutline, 5 | TiStarOutline, 6 | } from "react-icons/ti" 7 | 8 | function RatingStars({ Review_Count, Star_Size }) { 9 | const [starCount, SetStarCount] = useState({ 10 | full: 0, 11 | half: 0, 12 | empty: 0, 13 | }) 14 | 15 | useEffect(() => { 16 | const wholeStars = Math.floor(Review_Count) || 0 17 | SetStarCount({ 18 | full: wholeStars, 19 | half: Number.isInteger(Review_Count) ? 0 : 1, 20 | empty: Number.isInteger(Review_Count) ? 5 - wholeStars : 4 - wholeStars, 21 | }) 22 | }, [Review_Count]) 23 | return ( 24 |
25 | {[...new Array(starCount.full)].map((_, i) => { 26 | return 27 | })} 28 | {[...new Array(starCount.half)].map((_, i) => { 29 | return 30 | })} 31 | {[...new Array(starCount.empty)].map((_, i) => { 32 | return 33 | })} 34 |
35 | ) 36 | } 37 | 38 | export default RatingStars -------------------------------------------------------------------------------- /src/components/common/ReviewSlider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | import ReactStars from "react-rating-stars-component" 3 | // Import Swiper React components 4 | import { Swiper, SwiperSlide } from "swiper/react" 5 | 6 | // Import Swiper styles 7 | import "swiper/css" 8 | import "swiper/css/free-mode" 9 | import "swiper/css/pagination" 10 | import "../../App.css" 11 | // Icons 12 | import { FaStar } from "react-icons/fa" 13 | // Import required modules 14 | import { Autoplay, FreeMode, Pagination } from "swiper" 15 | 16 | // Get apiFunction and the endpoint 17 | import { apiConnector } from "../../services/apiconnector" 18 | import { ratingsEndpoints } from "../../services/apis" 19 | 20 | function ReviewSlider() { 21 | const [reviews, setReviews] = useState([]) 22 | const truncateWords = 15 23 | 24 | useEffect(() => { 25 | ;(async () => { 26 | const { data } = await apiConnector( 27 | "GET", 28 | ratingsEndpoints.REVIEWS_DETAILS_API 29 | ) 30 | if (data?.success) { 31 | setReviews(data?.data) 32 | } 33 | })() 34 | }, []) 35 | 36 | // console.log(reviews) 37 | 38 | return ( 39 |
40 |
41 | 53 | {reviews.map((review, i) => { 54 | return ( 55 | 56 |
57 |
58 | 67 |
68 |

{`${review?.user?.firstName} ${review?.user?.lastName}`}

69 |

70 | {review?.course?.courseName} 71 |

72 |
73 |
74 |

75 | {review?.review.split(" ").length > truncateWords 76 | ? `${review?.review 77 | .split(" ") 78 | .slice(0, truncateWords) 79 | .join(" ")} ...` 80 | : `${review?.review}`} 81 |

82 |
83 |

84 | {review.rating.toFixed(1)} 85 |

86 | } 93 | fullIcon={} 94 | /> 95 |
96 |
97 |
98 | ) 99 | })} 100 | {/* Slide 1 */} 101 |
102 |
103 |
104 | ) 105 | } 106 | 107 | export default ReviewSlider -------------------------------------------------------------------------------- /src/components/common/Tab.jsx: -------------------------------------------------------------------------------- 1 | export default function Tab({ tabData, field, setField }) { 2 | return ( 3 |
9 | {tabData.map((tab) => ( 10 | 21 | ))} 22 |
23 | ); 24 | } -------------------------------------------------------------------------------- /src/components/core/AboutPage/ContactFormSection.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ContactUsForm from "../../ContactPage/ContactUsForm"; 3 | 4 | const ContactFormSection = () => { 5 | return ( 6 |
7 |

Get in Touch

8 |

9 | We'd love to here for you, Please fill out this form. 10 |

11 |
12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default ContactFormSection; -------------------------------------------------------------------------------- /src/components/core/AboutPage/LearningGrid.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import HighlightText from "../../../components/core/HomePage/HighlightText"; 3 | import CTAButton from "../../../components/core/HomePage/Button"; 4 | 5 | const LearningGridArray = [ 6 | { 7 | order: -1, 8 | heading: "World-Class Learning for", 9 | highlightText: "Anyone, Anywhere", 10 | description: 11 | "Studynotion partners with more than 275+ leading universities and companies to bring flexible, affordable, job-relevant online learning to individuals and organizations worldwide.", 12 | BtnText: "Learn More", 13 | BtnLink: "/", 14 | }, 15 | { 16 | order: 1, 17 | heading: "Curriculum Based on Industry Needs", 18 | description: 19 | "Save time and money! The Belajar curriculum is made to be easier to understand and in line with industry needs.", 20 | }, 21 | { 22 | order: 2, 23 | heading: "Our Learning Methods", 24 | description: 25 | "Studynotion partners with more than 275+ leading universities and companies to bring", 26 | }, 27 | { 28 | order: 3, 29 | heading: "Certification", 30 | description: 31 | "Studynotion partners with more than 275+ leading universities and companies to bring", 32 | }, 33 | { 34 | order: 4, 35 | heading: `Rating "Auto-grading"`, 36 | description: 37 | "Studynotion partners with more than 275+ leading universities and companies to bring", 38 | }, 39 | { 40 | order: 5, 41 | heading: "Ready to Work", 42 | description: 43 | "Studynotion partners with more than 275+ leading universities and companies to bring", 44 | }, 45 | ]; 46 | 47 | const LearningGrid = () => { 48 | return ( 49 |
50 | {LearningGridArray.map((card, i) => { 51 | return ( 52 |
62 | {card.order < 0 ? ( 63 |
64 |
65 | {card.heading} 66 | 67 |
68 |

69 | {card.description} 70 |

71 | 72 |
73 | 74 | {card.BtnText} 75 | 76 |
77 |
78 | ) : ( 79 |
80 |

{card.heading}

81 | 82 |

83 | {card.description} 84 |

85 |
86 | )} 87 |
88 | ); 89 | })} 90 |
91 | ); 92 | }; 93 | 94 | export default LearningGrid; -------------------------------------------------------------------------------- /src/components/core/AboutPage/Quote.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HighlightText from '../HomePage/HighlightText' 3 | 4 | const Quote = () => { 5 | return ( 6 |
7 | We are passionate about revolutionizing the way we learn. Our 8 | innovative platform ,{" "} 9 | 10 | {" "} 11 | expertise 12 | 13 | , and community to create an 14 | 15 | {" "} 16 | unparalleled educational 17 | experience. 18 | 19 |
20 | ) 21 | } 22 | 23 | export default Quote -------------------------------------------------------------------------------- /src/components/core/AboutPage/Stats.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Stats = [ 4 | { count: "5K", label: "Active Students" }, 5 | { count: "10+", label: "Mentors" }, 6 | { count: "200+", label: "Courses" }, 7 | { count: "50+", label: "Awards" }, 8 | ]; 9 | 10 | const StatsComponenet = () => { 11 | return ( 12 |
13 | {/* Stats */} 14 |
15 |
16 | {Stats.map((data, index) => { 17 | return ( 18 |
19 |

20 | {data.count} 21 |

22 |

23 | {data.label} 24 |

25 |
26 | ); 27 | })} 28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default StatsComponenet; -------------------------------------------------------------------------------- /src/components/core/Auth/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai" 3 | import { useDispatch } from "react-redux" 4 | import { Link, useNavigate } from "react-router-dom" 5 | 6 | import { login } from "../../../services/operations/authAPI" 7 | 8 | function LoginForm() { 9 | const navigate = useNavigate() 10 | const dispatch = useDispatch() 11 | const [formData, setFormData] = useState({ 12 | email: "", 13 | password: "", 14 | }) 15 | 16 | const [showPassword, setShowPassword] = useState(false) 17 | 18 | const { email, password } = formData 19 | 20 | const handleOnChange = (e) => { 21 | setFormData((prevData) => ({ 22 | ...prevData, 23 | [e.target.name]: e.target.value, 24 | })) 25 | } 26 | 27 | const handleOnSubmit = (e) => { 28 | e.preventDefault() 29 | dispatch(login(email, password, navigate)) 30 | } 31 | 32 | return ( 33 |
37 | 54 | 86 | 92 |
93 | ) 94 | } 95 | 96 | export default LoginForm -------------------------------------------------------------------------------- /src/components/core/Auth/OpenRoute.jsx: -------------------------------------------------------------------------------- 1 | // This will prevent authenticated users from accessing this route 2 | import { useSelector } from "react-redux" 3 | import { Navigate } from "react-router-dom" 4 | 5 | function OpenRoute({ children }) { 6 | const { token } = useSelector((state) => state.auth) 7 | 8 | if (token === null) { 9 | return children 10 | } else { 11 | return 12 | } 13 | } 14 | 15 | export default OpenRoute -------------------------------------------------------------------------------- /src/components/core/Auth/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { Navigate } from 'react-router-dom'; 4 | 5 | const PrivateRoute = ({children}) => { 6 | 7 | const {token} = useSelector((state) => state.auth); 8 | 9 | if(token !== null) 10 | return children 11 | else 12 | return 13 | 14 | } 15 | 16 | export default PrivateRoute 17 | -------------------------------------------------------------------------------- /src/components/core/Auth/ProfileDropDown.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react" 2 | import { AiOutlineCaretDown } from "react-icons/ai" 3 | import { VscDashboard, VscSignOut } from "react-icons/vsc" 4 | import { useDispatch, useSelector } from "react-redux" 5 | import { Link, useNavigate } from "react-router-dom" 6 | 7 | import useOnClickOutside from "../../../hooks/useOnClickOutside" 8 | import { logout } from "../../../services/operations/authAPI" 9 | 10 | export default function ProfileDropdown() { 11 | const { user } = useSelector((state) => state.profile) 12 | const dispatch = useDispatch() 13 | const navigate = useNavigate() 14 | const [open, setOpen] = useState(false) 15 | const ref = useRef(null) 16 | 17 | useOnClickOutside(ref, () => setOpen(false)) 18 | 19 | if (!user) return null 20 | 21 | return ( 22 | 56 | ) 57 | } -------------------------------------------------------------------------------- /src/components/core/Auth/Template.jsx: -------------------------------------------------------------------------------- 1 | import { FcGoogle } from "react-icons/fc" 2 | import { useSelector } from "react-redux" 3 | 4 | import frameImg from "../../../assets/Images/frame.png" 5 | import LoginForm from "./LoginForm" 6 | import SignupForm from "./SignupForm" 7 | 8 | function Template({ title, description1, description2, image, formType }) { 9 | const { loading } = useSelector((state) => state.auth) 10 | 11 | return ( 12 |
13 | {loading ? ( 14 |
15 | ) : ( 16 |
17 |
18 |

19 | {title} 20 |

21 |

22 | {description1}{" "} 23 | 24 | {description2} 25 | 26 |

27 | {formType === "signup" ? : } 28 |
29 |
30 | Pattern 37 | Students 45 |
46 |
47 | )} 48 |
49 | ) 50 | } 51 | 52 | export default Template -------------------------------------------------------------------------------- /src/components/core/Catalog/CourseSlider.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import {Swiper, SwiperSlide} from "swiper/react" 4 | import "swiper/css" 5 | import "swiper/css/free-mode" 6 | import "swiper/css/pagination" 7 | import { Autoplay,FreeMode,Navigation, Pagination} from 'swiper' 8 | 9 | import Course_Card from './Course_Card' 10 | 11 | const CourseSlider = ({Courses}) => { 12 | return ( 13 | <> 14 | {Courses?.length ? ( 15 | 27 | {Courses?.map((course, i) => ( 28 | 29 | 30 | 31 | ))} 32 | 33 | ) : ( 34 |

No Course Found

35 | )} 36 | 37 | ) 38 | } 39 | 40 | export default CourseSlider 41 | -------------------------------------------------------------------------------- /src/components/core/Catalog/Course_Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import RatingStars from '../../common/RatingStars' 3 | import GetAvgRating from '../../../utils/avgRating'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | const Course_Card = ({course, Height}) => { 7 | 8 | 9 | const [avgReviewCount, setAvgReviewCount] = useState(0); 10 | 11 | useEffect(()=> { 12 | const count = GetAvgRating(course.ratingAndReviews); 13 | setAvgReviewCount(count); 14 | },[course]) 15 | 16 | 17 | 18 | return ( 19 | <> 20 | 21 |
22 |
23 | course thumnail 28 |
29 |
30 |

{course?.courseName}

31 |

32 | {course?.instructor?.firstName} {course?.instructor?.lastName} 33 |

34 |
35 | {avgReviewCount || 0} 36 | 37 | 38 | {course?.ratingAndReviews?.length} Ratings 39 | 40 |
41 |

Rs. {course?.price}

42 |
43 |
44 | 45 | 46 | ) 47 | } 48 | 49 | export default Course_Card 50 | -------------------------------------------------------------------------------- /src/components/core/Course/CourseAccordionBar.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react" 2 | import { AiOutlineDown } from "react-icons/ai" 3 | 4 | import CourseSubSectionAccordion from "./CourseSubSectionAccordion" 5 | 6 | export default function CourseAccordionBar({ course, isActive, handleActive }) { 7 | const contentEl = useRef(null) 8 | 9 | // Accordian state 10 | const [active, setActive] = useState(false) 11 | useEffect(() => { 12 | setActive(isActive?.includes(course._id)) 13 | }, [isActive]) 14 | const [sectionHeight, setSectionHeight] = useState(0) 15 | useEffect(() => { 16 | setSectionHeight(active ? contentEl.current.scrollHeight : 0) 17 | }, [active]) 18 | 19 | return ( 20 |
21 |
22 |
{ 25 | handleActive(course._id) 26 | }} 27 | > 28 |
29 | 34 | 35 | 36 |

{course?.sectionName}

37 |
38 |
39 | 40 | {`${course.subSection.length || 0} lecture(s)`} 41 | 42 |
43 |
44 |
45 |
52 |
53 | {course?.subSection?.map((subSec, i) => { 54 | return 55 | })} 56 |
57 |
58 |
59 | ) 60 | } -------------------------------------------------------------------------------- /src/components/core/Course/CourseDetailsCard.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import copy from "copy-to-clipboard" 3 | import { toast } from "react-hot-toast" 4 | import { BsFillCaretRightFill } from "react-icons/bs" 5 | import { FaShareSquare } from "react-icons/fa" 6 | import { useDispatch, useSelector } from "react-redux" 7 | import { useNavigate } from "react-router-dom" 8 | 9 | import { addToCart } from "../../../slices/cartSlice" 10 | import { ACCOUNT_TYPE } from "../../../utils/constants" 11 | 12 | 13 | function CourseDetailsCard({ course, setConfirmationModal, handleBuyCourse }) { 14 | const { user } = useSelector((state) => state.profile) 15 | const { token } = useSelector((state) => state.auth) 16 | const navigate = useNavigate() 17 | const dispatch = useDispatch() 18 | 19 | const { 20 | thumbnail: ThumbnailImage, 21 | price: CurrentPrice, 22 | _id: courseId, 23 | } = course 24 | 25 | const handleShare = () => { 26 | copy(window.location.href) 27 | toast.success("Link copied to clipboard") 28 | } 29 | 30 | const handleAddToCart = () => { 31 | if (user && user?.accountType === ACCOUNT_TYPE.INSTRUCTOR) { 32 | toast.error("You are an Instructor. You can't buy a course.") 33 | return 34 | } 35 | if (token) { 36 | dispatch(addToCart(course)) 37 | return 38 | } 39 | setConfirmationModal({ 40 | text1: "You are not logged in!", 41 | text2: "Please login to add To Cart", 42 | btn1Text: "Login", 43 | btn2Text: "Cancel", 44 | btn1Handler: () => navigate("/login"), 45 | btn2Handler: () => setConfirmationModal(null), 46 | }) 47 | } 48 | 49 | // console.log("Student already enrolled ", course?.studentsEnroled, user?._id) 50 | 51 | return ( 52 | <> 53 |
56 | {/* Course Image */} 57 | {course?.courseName} 62 | 63 |
64 |
65 | Rs. {CurrentPrice} 66 |
67 |
68 | 80 | {(!user || !course?.studentsEnrolled.includes(user?._id)) && ( 81 | 84 | )} 85 |
86 |
87 |

88 | 30-Day Money-Back Guarantee 89 |

90 |
91 | 92 |
93 |

94 | This Course Includes : 95 |

96 |
97 | {course?.instructions?.map((item, i) => { 98 | return ( 99 |

100 | 101 | {item} 102 |

103 | ) 104 | })} 105 |
106 |
107 |
108 | 114 |
115 |
116 |
117 | 118 | ) 119 | } 120 | 121 | export default CourseDetailsCard -------------------------------------------------------------------------------- /src/components/core/Course/CourseSubSectionAccordion.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react" 2 | import { AiOutlineDown } from "react-icons/ai" 3 | import { HiOutlineVideoCamera } from "react-icons/hi" 4 | 5 | function CourseSubSectionAccordion({ subSec }) { 6 | return ( 7 |
8 |
9 |
10 | 11 | 12 | 13 |

{subSec?.title}

14 |
15 |
16 |
17 | ) 18 | } 19 | 20 | export default CourseSubSectionAccordion -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/CourseInformation/ChipInput.jsx: -------------------------------------------------------------------------------- 1 | // Importing React hook for managing component state 2 | import { useEffect, useState } from "react" 3 | // Importing React icon component 4 | import { MdClose } from "react-icons/md" 5 | import { useSelector } from "react-redux" 6 | 7 | // Defining a functional component ChipInput 8 | export default function ChipInput({ 9 | // Props to be passed to the component 10 | label, 11 | name, 12 | placeholder, 13 | register, 14 | errors, 15 | setValue, 16 | getValues, 17 | }) { 18 | const { editCourse, course } = useSelector((state) => state.course) 19 | 20 | // Setting up state for managing chips array 21 | const [chips, setChips] = useState([]) 22 | 23 | useEffect(() => { 24 | if (editCourse) { 25 | // console.log(course) 26 | setChips(course?.tag) 27 | } 28 | register(name, { required: true, validate: (value) => value.length > 0 }) 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, []) 31 | 32 | useEffect(() => { 33 | setValue(name, chips) 34 | // eslint-disable-next-line react-hooks/exhaustive-deps 35 | }, [chips]) 36 | 37 | // Function to handle user input when chips are added 38 | const handleKeyDown = (event) => { 39 | // Check if user presses "Enter" or "," 40 | if (event.key === "Enter" || event.key === ",") { 41 | // Prevent the default behavior of the event 42 | event.preventDefault() 43 | // Get the input value and remove any leading/trailing spaces 44 | const chipValue = event.target.value.trim() 45 | // Check if the input value exists and is not already in the chips array 46 | if (chipValue && !chips.includes(chipValue)) { 47 | // Add the chip to the array and clear the input 48 | const newChips = [...chips, chipValue] 49 | setChips(newChips) 50 | event.target.value = "" 51 | } 52 | } 53 | } 54 | 55 | // Function to handle deletion of a chip 56 | const handleDeleteChip = (chipIndex) => { 57 | // Filter the chips array to remove the chip with the given index 58 | const newChips = chips.filter((_, index) => index !== chipIndex) 59 | setChips(newChips) 60 | } 61 | 62 | // Render the component 63 | return ( 64 |
65 | {/* Render the label for the input */} 66 | 69 | {/* Render the chips and input */} 70 |
71 | {/* Map over the chips array and render each chip */} 72 | {chips.map((chip, index) => ( 73 |
77 | {/* Render the chip value */} 78 | {chip} 79 | {/* Render the button to delete the chip */} 80 | 87 |
88 | ))} 89 | {/* Render the input for adding new chips */} 90 | 98 |
99 | {/* Render an error message if the input is required and not filled */} 100 | {errors[name] && ( 101 | 102 | {label} is required 103 | 104 | )} 105 |
106 | ) 107 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/CourseInformation/RequirementField.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { useSelector } from "react-redux" 3 | 4 | export default function RequirementsField({ 5 | name, 6 | label, 7 | register, 8 | setValue, 9 | errors, 10 | getValues, 11 | }) { 12 | const { editCourse, course } = useSelector((state) => state.course) 13 | const [requirement, setRequirement] = useState("") 14 | const [requirementsList, setRequirementsList] = useState([]) 15 | 16 | useEffect(() => { 17 | if (editCourse) { 18 | setRequirementsList(course?.instructions) 19 | } 20 | register(name, { required: true, validate: (value) => value.length > 0 }) 21 | // eslint-disable-next-line react-hooks/exhaustive-deps 22 | }, []) 23 | 24 | useEffect(() => { 25 | setValue(name, requirementsList) 26 | // eslint-disable-next-line react-hooks/exhaustive-deps 27 | }, [requirementsList]) 28 | 29 | const handleAddRequirement = () => { 30 | if (requirement) { 31 | setRequirementsList([...requirementsList, requirement]) 32 | setRequirement("") 33 | } 34 | } 35 | 36 | const handleRemoveRequirement = (index) => { 37 | const updatedRequirements = [...requirementsList] 38 | updatedRequirements.splice(index, 1) 39 | setRequirementsList(updatedRequirements) 40 | } 41 | 42 | return ( 43 |
44 | 47 |
48 | setRequirement(e.target.value)} 53 | className="form-style w-full" 54 | /> 55 | 62 |
63 | {requirementsList.length > 0 && ( 64 |
    65 | {requirementsList.map((requirement, index) => ( 66 |
  • 67 | {requirement} 68 | 75 |
  • 76 | ))} 77 |
78 | )} 79 | {errors[name] && ( 80 | 81 | {label} is required 82 | 83 | )} 84 |
85 | ) 86 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/PublishCourse/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { useForm } from "react-hook-form" 3 | import { useDispatch, useSelector } from "react-redux" 4 | import { useNavigate } from "react-router-dom" 5 | 6 | import { editCourseDetails } from "../../../../../services/operations/courseDetailsAPI" 7 | import { resetCourseState, setStep } from "../../../../../slices/courseSlice" 8 | import { COURSE_STATUS } from "../../../../../utils/constants" 9 | import IconBtn from "../../../../common/IconBtn" 10 | 11 | export default function PublishCourse() { 12 | const { register, handleSubmit, setValue, getValues } = useForm() 13 | 14 | const dispatch = useDispatch() 15 | const navigate = useNavigate() 16 | const { token } = useSelector((state) => state.auth) 17 | const { course } = useSelector((state) => state.course) 18 | const [loading, setLoading] = useState(false) 19 | 20 | useEffect(() => { 21 | if (course?.status === COURSE_STATUS.PUBLISHED) { 22 | setValue("public", true) 23 | } 24 | }, []) 25 | 26 | const goBack = () => { 27 | dispatch(setStep(2)) 28 | } 29 | 30 | const goToCourses = () => { 31 | dispatch(resetCourseState()) 32 | navigate("/dashboard/my-courses") 33 | } 34 | 35 | const handleCoursePublish = async () => { 36 | // check if form has been updated or not 37 | if ( 38 | (course?.status === COURSE_STATUS.PUBLISHED && 39 | getValues("public") === true) || 40 | (course?.status === COURSE_STATUS.DRAFT && getValues("public") === false) 41 | ) { 42 | // form has not been updated 43 | // no need to make api call 44 | goToCourses() 45 | return 46 | } 47 | const formData = new FormData() 48 | formData.append("courseId", course._id) 49 | const courseStatus = getValues("public") 50 | ? COURSE_STATUS.PUBLISHED 51 | : COURSE_STATUS.DRAFT 52 | formData.append("status", courseStatus) 53 | setLoading(true) 54 | const result = await editCourseDetails(formData, token) 55 | if (result) { 56 | goToCourses() 57 | } 58 | setLoading(false) 59 | } 60 | 61 | const onSubmit = (data) => { 62 | // console.log(data) 63 | handleCoursePublish() 64 | } 65 | 66 | return ( 67 |
68 |

69 | Publish Settings 70 |

71 |
72 | {/* Checkbox */} 73 |
74 | 85 |
86 | 87 | {/* Next Prev Button */} 88 |
89 | 97 | 98 |
99 |
100 |
101 | ) 102 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/RenderSteps.jsx: -------------------------------------------------------------------------------- 1 | import { FaCheck } from "react-icons/fa" 2 | import { useSelector } from "react-redux" 3 | 4 | import CourseBuilderForm from "./CourseBuilder/CourseBuilderForm" 5 | import CourseInformationForm from "./CourseInformation/CourseInformationForm" 6 | import PublishCourse from "./PublishCourse" 7 | 8 | 9 | export default function RenderSteps() { 10 | const { step } = useSelector((state) => state.course) 11 | 12 | const steps = [ 13 | { 14 | id: 1, 15 | title: "Course Information", 16 | }, 17 | { 18 | id: 2, 19 | title: "Course Builder", 20 | }, 21 | { 22 | id: 3, 23 | title: "Publish", 24 | }, 25 | ] 26 | 27 | return ( 28 | <> 29 |
30 | {steps.map((item) => ( 31 | <> 32 |
36 | 49 | 50 |
51 | {item.id !== steps.length && ( 52 | <> 53 |
item.id ? "border-yellow-50" : "border-richblack-500" 56 | } `} 57 | >
58 | 59 | )} 60 | 61 | ))} 62 |
63 | 64 |
65 | {steps.map((item) => ( 66 | <> 67 |
71 | 72 |

= item.id ? "text-richblack-5" : "text-richblack-500" 75 | }`} 76 | > 77 | {item.title} 78 |

79 |
80 | 81 | 82 | ))} 83 |
84 | {/* Render specific component based on current step */} 85 | {step === 1 && } 86 | {step === 2 && } 87 | {step === 3 && } 88 | 89 | ) 90 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/Upload.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react" 2 | import { useDropzone } from "react-dropzone" 3 | import { FiUploadCloud } from "react-icons/fi" 4 | import { useSelector } from "react-redux" 5 | 6 | import "video-react/dist/video-react.css" 7 | import { Player } from "video-react" 8 | 9 | export default function Upload({ 10 | name, 11 | label, 12 | register, 13 | setValue, 14 | errors, 15 | video = false, 16 | viewData = null, 17 | editData = null, 18 | }) { 19 | const { course } = useSelector((state) => state.course) 20 | const [selectedFile, setSelectedFile] = useState(null) 21 | const [previewSource, setPreviewSource] = useState( 22 | viewData ? viewData : editData ? editData : "" 23 | ) 24 | const inputRef = useRef(null) 25 | 26 | const onDrop = (acceptedFiles) => { 27 | const file = acceptedFiles[0] 28 | if (file) { 29 | previewFile(file) 30 | setSelectedFile(file) 31 | } 32 | } 33 | 34 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 35 | accept: !video 36 | ? { "image/*": [".jpeg", ".jpg", ".png"] } 37 | : { "video/*": [".mp4"] }, 38 | onDrop, 39 | }) 40 | 41 | const previewFile = (file) => { 42 | // console.log(file) 43 | const reader = new FileReader() 44 | reader.readAsDataURL(file) 45 | reader.onloadend = () => { 46 | setPreviewSource(reader.result) 47 | } 48 | } 49 | 50 | useEffect(() => { 51 | register(name, { required: true }) 52 | // eslint-disable-next-line react-hooks/exhaustive-deps 53 | }, [register]) 54 | 55 | useEffect(() => { 56 | setValue(name, selectedFile) 57 | // eslint-disable-next-line react-hooks/exhaustive-deps 58 | }, [selectedFile, setValue]) 59 | 60 | return ( 61 |
62 | 65 |
70 | {previewSource ? ( 71 |
72 | {!video ? ( 73 | Preview 78 | ) : ( 79 | 80 | )} 81 | {!viewData && ( 82 | 93 | )} 94 |
95 | ) : ( 96 |
100 | 101 |
102 | 103 |
104 |

105 | Drag and drop an {!video ? "image" : "video"}, or click to{" "} 106 | Browse a 107 | file 108 |

109 |
    110 |
  • Aspect ratio 16:9
  • 111 |
  • Recommended size 1024x576
  • 112 |
113 |
114 | )} 115 |
116 | {errors[name] && ( 117 | 118 | {label} is required 119 | 120 | )} 121 |
122 | ) 123 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/index.jsx: -------------------------------------------------------------------------------- 1 | import RenderSteps from "./RenderSteps" 2 | 3 | export default function AddCourse() { 4 | return ( 5 | <> 6 |
7 |
8 |

9 | Add Course 10 |

11 |
12 | 13 |
14 |
15 | {/* Course Upload Tips */} 16 |
17 |

⚡ Course Upload Tips

18 |
    19 |
  • Set the Course Price option or make it free.
  • 20 |
  • Standard size for the course thumbnail is 1024x576.
  • 21 |
  • Video section controls the course overview video.
  • 22 |
  • Course Builder is where you create & organize a course.
  • 23 |
  • 24 | Add Topics in the Course Builder section to create lessons, 25 | quizzes, and assignments. 26 |
  • 27 |
  • 28 | Information from the Additional Data section shows up on the 29 | course single page. 30 |
  • 31 |
  • Make Announcements to notify any important
  • 32 |
  • Notes to all enrolled students at once.
  • 33 |
34 |
35 |
36 | 37 | ) 38 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Cart/RenderCartCourses.jsx: -------------------------------------------------------------------------------- 1 | import { FaStar } from "react-icons/fa" 2 | import { RiDeleteBin6Line } from "react-icons/ri" 3 | import ReactStars from "react-rating-stars-component" 4 | import { useDispatch, useSelector } from "react-redux" 5 | 6 | import { removeFromCart } from "../../../../slices/cartSlice" 7 | 8 | export default function RenderCartCourses() { 9 | const { cart } = useSelector((state) => state.cart) 10 | const dispatch = useDispatch() 11 | return ( 12 |
13 | {cart.map((course, indx) => ( 14 |
20 |
21 | {course?.courseName} 26 |
27 |

28 | {course?.courseName} 29 |

30 |

31 | {course?.category?.name} 32 |

33 |
34 | 4.5 35 | } 42 | fullIcon={} 43 | /> 44 | 45 | {course?.ratingAndReviews?.length} Ratings 46 | 47 |
48 |
49 |
50 |
51 | 58 |

59 | ₹ {course?.price} 60 |

61 |
62 |
63 | ))} 64 |
65 | ) 66 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Cart/RenderTotalAmount.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux" 2 | import { useNavigate } from "react-router-dom" 3 | 4 | import IconBtn from "../../../common/IconBtn" 5 | import { buyCourse } from "../../../../services/operations/studentFeaturesAPI" 6 | 7 | export default function RenderTotalAmount() { 8 | const { total, cart } = useSelector((state) => state.cart) 9 | const { token } = useSelector((state) => state.auth) 10 | const { user } = useSelector((state) => state.profile) 11 | const navigate = useNavigate() 12 | const dispatch = useDispatch() 13 | 14 | const handleBuyCourse = () => { 15 | const courses = cart.map((course) => course._id) 16 | buyCourse(token, courses, user, navigate, dispatch) 17 | } 18 | 19 | return ( 20 |
21 |

Total:

22 |

₹ {total}

23 | 28 |
29 | ) 30 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Cart/index.jsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux" 2 | 3 | import RenderCartCourses from "./RenderCartCourses" 4 | import RenderTotalAmount from "./RenderTotalAmount" 5 | 6 | export default function Cart() { 7 | const { total, totalItems } = useSelector((state) => state.cart) 8 | 9 | return ( 10 | <> 11 |

Cart

12 |

13 | {totalItems} Courses in Cart 14 |

15 | {total > 0 ? ( 16 |
17 | 18 | 19 |
20 | ) : ( 21 |

22 | Your cart is empty 23 |

24 | )} 25 | 26 | ) 27 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/EditCourse/index.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { useDispatch, useSelector } from "react-redux" 3 | import { useParams } from "react-router-dom" 4 | 5 | import { 6 | fetchCourseDetails, 7 | getFullDetailsOfCourse, 8 | } from "../../../../services/operations/courseDetailsAPI" 9 | import { setCourse, setEditCourse } from "../../../../slices/courseSlice" 10 | import RenderSteps from "../AddCourse/RenderSteps" 11 | 12 | export default function EditCourse() { 13 | const dispatch = useDispatch() 14 | const { courseId } = useParams() 15 | const { course } = useSelector((state) => state.course) 16 | const [loading, setLoading] = useState(false) 17 | const { token } = useSelector((state) => state.auth) 18 | 19 | useEffect(() => { 20 | ;(async () => { 21 | setLoading(true) 22 | const result = await getFullDetailsOfCourse(courseId, token) 23 | if (result?.courseDetails) { 24 | dispatch(setEditCourse(true)) 25 | dispatch(setCourse(result?.courseDetails)) 26 | } 27 | setLoading(false) 28 | })() 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, []) 31 | 32 | if (loading) { 33 | return ( 34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | return ( 41 |
42 |

43 | Edit Course 44 |

45 |
46 | {course ? ( 47 | 48 | ) : ( 49 |

50 | Course not found 51 |

52 | )} 53 |
54 |
55 | ) 56 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/EnrolledCourses.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import ProgressBar from "@ramonak/react-progress-bar" 3 | import { BiDotsVerticalRounded } from "react-icons/bi" 4 | import { useSelector } from "react-redux" 5 | import { useNavigate } from "react-router-dom" 6 | 7 | import { getUserEnrolledCourses } from "../../../services/operations/profileAPI" 8 | 9 | export default function EnrolledCourses() { 10 | const { token } = useSelector((state) => state.auth) 11 | const navigate = useNavigate() 12 | 13 | const [enrolledCourses, setEnrolledCourses] = useState(null) 14 | const getEnrolledCourses = async () => { 15 | try { 16 | const res = await getUserEnrolledCourses(token); 17 | 18 | setEnrolledCourses(res); 19 | } catch (error) { 20 | console.log("Could not fetch enrolled courses.") 21 | } 22 | }; 23 | useEffect(() => { 24 | getEnrolledCourses(); 25 | }, []) 26 | 27 | return ( 28 | <> 29 |
Enrolled Courses
30 | {!enrolledCourses ? ( 31 |
32 |
33 |
34 | ) : !enrolledCourses.length ? ( 35 |

36 | You have not enrolled in any course yet. 37 | {/* TODO: Modify this Empty State */} 38 |

39 | ) : ( 40 |
41 | {/* Headings */} 42 |
43 |

Course Name

44 |

Duration

45 |

Progress

46 |
47 | {/* Course Names */} 48 | {enrolledCourses.map((course, i, arr) => ( 49 |
55 |
{ 58 | navigate( 59 | `/view-course/${course?._id}/section/${course.courseContent?.[0]?._id}/sub-section/${course.courseContent?.[0]?.subSection?.[0]?._id}` 60 | ) 61 | }} 62 | > 63 | course_img 68 |
69 |

{course.courseName}

70 |

71 | {course.courseDescription.length > 50 72 | ? `${course.courseDescription.slice(0, 50)}...` 73 | : course.courseDescription} 74 |

75 |
76 |
77 |
{course?.totalDuration}
78 |
79 |

Progress: {course.progressPercentage || 0}%

80 | 85 |
86 |
87 | ))} 88 |
89 | )} 90 | 91 | ) 92 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/InstructorDashboard/InstructorChart.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { Chart, registerables } from "chart.js" 3 | import { Pie } from "react-chartjs-2" 4 | 5 | Chart.register(...registerables) 6 | 7 | export default function InstructorChart({ courses }) { 8 | // State to keep track of the currently selected chart 9 | const [currChart, setCurrChart] = useState("students") 10 | 11 | // Function to generate random colors for the chart 12 | const generateRandomColors = (numColors) => { 13 | const colors = [] 14 | for (let i = 0; i < numColors; i++) { 15 | const color = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor( 16 | Math.random() * 256 17 | )}, ${Math.floor(Math.random() * 256)})` 18 | colors.push(color) 19 | } 20 | return colors 21 | } 22 | 23 | // Data for the chart displaying student information 24 | const chartDataStudents = { 25 | labels: courses.map((course) => course.courseName), 26 | datasets: [ 27 | { 28 | data: courses.map((course) => course.totalStudentsEnrolled), 29 | backgroundColor: generateRandomColors(courses.length), 30 | }, 31 | ], 32 | } 33 | 34 | // Data for the chart displaying income information 35 | const chartIncomeData = { 36 | labels: courses.map((course) => course.courseName), 37 | datasets: [ 38 | { 39 | data: courses.map((course) => course.totalAmountGenerated), 40 | backgroundColor: generateRandomColors(courses.length), 41 | }, 42 | ], 43 | } 44 | 45 | // Options for the chart 46 | const options = { 47 | maintainAspectRatio: false, 48 | } 49 | 50 | return ( 51 |
52 |

Visualize

53 |
54 | {/* Button to switch to the "students" chart */} 55 | 65 | {/* Button to switch to the "income" chart */} 66 | 76 |
77 |
78 | {/* Render the Pie chart based on the selected chart */} 79 | 83 |
84 |
85 | ) 86 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/MyCourses.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { VscAdd } from "react-icons/vsc" 3 | import { useSelector } from "react-redux" 4 | import { useNavigate } from "react-router-dom" 5 | 6 | import { fetchInstructorCourses } from "../../../services/operations/courseDetailsAPI" 7 | import IconBtn from "../../common/IconBtn" 8 | import CoursesTable from "./InstructorCourses/CoursesTable" 9 | 10 | export default function MyCourses() { 11 | const { token } = useSelector((state) => state.auth) 12 | const navigate = useNavigate() 13 | const [courses, setCourses] = useState([]) 14 | 15 | useEffect(() => { 16 | const fetchCourses = async () => { 17 | const result = await fetchInstructorCourses(token) 18 | if (result) { 19 | setCourses(result) 20 | } 21 | } 22 | fetchCourses() 23 | // eslint-disable-next-line react-hooks/exhaustive-deps 24 | }, []) 25 | 26 | return ( 27 |
28 |
29 |

My Courses

30 | navigate("/dashboard/add-course")} 33 | > 34 | 35 | 36 |
37 | {courses && } 38 |
39 | ) 40 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Settings/ChangeProfilePicture.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react" 2 | import { FiUpload } from "react-icons/fi" 3 | import { useDispatch, useSelector } from "react-redux" 4 | 5 | import { updateDisplayPicture } from "../../../../services/operations/SettingsAPI" 6 | import IconBtn from "../../../common/IconBtn" 7 | 8 | export default function ChangeProfilePicture() { 9 | const { token } = useSelector((state) => state.auth) 10 | const { user } = useSelector((state) => state.profile) 11 | const dispatch = useDispatch() 12 | 13 | const [loading, setLoading] = useState(false) 14 | const [imageFile, setImageFile] = useState(null) 15 | const [previewSource, setPreviewSource] = useState(null) 16 | 17 | const fileInputRef = useRef(null) 18 | 19 | const handleClick = () => { 20 | fileInputRef.current.click() 21 | } 22 | 23 | const handleFileChange = (e) => { 24 | const file = e.target.files[0] 25 | // console.log(file) 26 | if (file) { 27 | setImageFile(file) 28 | previewFile(file) 29 | } 30 | } 31 | 32 | const previewFile = (file) => { 33 | const reader = new FileReader() 34 | reader.readAsDataURL(file) 35 | reader.onloadend = () => { 36 | setPreviewSource(reader.result) 37 | } 38 | } 39 | 40 | const handleFileUpload = () => { 41 | try { 42 | console.log("uploading...") 43 | setLoading(true) 44 | const formData = new FormData() 45 | formData.append("displayPicture", imageFile) 46 | // console.log("formdata", formData) 47 | dispatch(updateDisplayPicture(token, formData)).then(() => { 48 | setLoading(false) 49 | }) 50 | } catch (error) { 51 | console.log("ERROR MESSAGE - ", error.message) 52 | } 53 | } 54 | 55 | useEffect(() => { 56 | if (imageFile) { 57 | previewFile(imageFile) 58 | } 59 | }, [imageFile]) 60 | return ( 61 | <> 62 |
63 |
64 | {`profile-${user?.firstName}`} 69 |
70 |

Change Profile Picture

71 |
72 | 79 | 86 | 90 | {!loading && ( 91 | 92 | )} 93 | 94 |
95 |
96 |
97 |
98 | 99 | ) 100 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Settings/DeleteAccount.jsx: -------------------------------------------------------------------------------- 1 | import { FiTrash2 } from "react-icons/fi" 2 | import { useDispatch, useSelector } from "react-redux" 3 | import { useNavigate } from "react-router-dom" 4 | 5 | import { deleteProfile } from "../../../../services/operations/SettingsAPI" 6 | 7 | export default function DeleteAccount() { 8 | const { token } = useSelector((state) => state.auth) 9 | const dispatch = useDispatch() 10 | const navigate = useNavigate() 11 | 12 | async function handleDeleteAccount() { 13 | try { 14 | dispatch(deleteProfile(token, navigate)) 15 | } catch (error) { 16 | console.log("ERROR MESSAGE - ", error.message) 17 | } 18 | } 19 | 20 | return ( 21 | <> 22 |
23 |
24 | 25 |
26 |
27 |

28 | Delete Account 29 |

30 |
31 |

Would you like to delete account?

32 |

33 | This account may contain Paid Courses. Deleting your account is 34 | permanent and will remove all the contain associated with it. 35 |

36 |
37 | 44 |
45 |
46 | 47 | ) 48 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Settings/UpdatePassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import { useForm } from "react-hook-form" 3 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai" 4 | import { useSelector } from "react-redux" 5 | import { useNavigate } from "react-router-dom" 6 | 7 | import { changePassword } from "../../../../services/operations/SettingsAPI" 8 | import IconBtn from "../../../common/IconBtn" 9 | 10 | export default function UpdatePassword() { 11 | const { token } = useSelector((state) => state.auth) 12 | const navigate = useNavigate() 13 | 14 | const [showOldPassword, setShowOldPassword] = useState(false) 15 | const [showNewPassword, setShowNewPassword] = useState(false) 16 | 17 | const { 18 | register, 19 | handleSubmit, 20 | formState: { errors }, 21 | } = useForm() 22 | 23 | const submitPasswordForm = async (data) => { 24 | // console.log("password Data - ", data) 25 | try { 26 | await changePassword(token, data) 27 | } catch (error) { 28 | console.log("ERROR MESSAGE - ", error.message) 29 | } 30 | } 31 | 32 | return ( 33 | <> 34 |
35 |
36 |

Password

37 |
38 |
39 | 42 | 50 | setShowOldPassword((prev) => !prev)} 52 | className="absolute right-3 top-[38px] z-[10] cursor-pointer" 53 | > 54 | {showOldPassword ? ( 55 | 56 | ) : ( 57 | 58 | )} 59 | 60 | {errors.oldPassword && ( 61 | 62 | Please enter your Current Password. 63 | 64 | )} 65 |
66 |
67 | 70 | 78 | setShowNewPassword((prev) => !prev)} 80 | className="absolute right-3 top-[38px] z-[10] cursor-pointer" 81 | > 82 | {showNewPassword ? ( 83 | 84 | ) : ( 85 | 86 | )} 87 | 88 | {errors.newPassword && ( 89 | 90 | Please enter your New Password. 91 | 92 | )} 93 |
94 |
95 |
96 |
97 | 105 | 106 |
107 |
108 | 109 | ) 110 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Settings/index.jsx: -------------------------------------------------------------------------------- 1 | import ChangeProfilePicture from "./ChangeProfilePicture" 2 | import DeleteAccount from "./DeleteAccount" 3 | import EditProfile from "./EditProfile" 4 | import UpdatePassword from "./UpdatePassword" 5 | 6 | export default function Settings() { 7 | return ( 8 | <> 9 |

10 | Edit Profile 11 |

12 | {/* Change Profile Picture */} 13 | 14 | {/* Profile */} 15 | 16 | {/* Password */} 17 | 18 | {/* Delete Account */} 19 | 20 | 21 | ) 22 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { VscSignOut } from "react-icons/vsc" 3 | import { useDispatch, useSelector } from "react-redux" 4 | import { useNavigate } from "react-router-dom" 5 | 6 | import { sidebarLinks } from "../../../data/dashboard-links" 7 | import { logout } from "../../../services/operations/authAPI" 8 | import ConfirmationModal from "../../common/ConfirmationModal" 9 | import SidebarLink from "./SidebarLink" 10 | 11 | export default function Sidebar() { 12 | const { user, loading: profileLoading } = useSelector( 13 | (state) => state.profile 14 | ) 15 | const { loading: authLoading } = useSelector((state) => state.auth) 16 | const dispatch = useDispatch() 17 | const navigate = useNavigate() 18 | // to keep track of confirmation modal 19 | const [confirmationModal, setConfirmationModal] = useState(null) 20 | 21 | if (profileLoading || authLoading) { 22 | return ( 23 |
24 |
25 |
26 | ) 27 | } 28 | 29 | return ( 30 | <> 31 |
32 |
33 | {sidebarLinks.map((link) => { 34 | if (link.type && user?.accountType !== link.type) return null 35 | return ( 36 | 37 | ) 38 | })} 39 |
40 |
41 |
42 | 46 | 64 |
65 |
66 | {confirmationModal && } 67 | 68 | ) 69 | } -------------------------------------------------------------------------------- /src/components/core/Dashboard/SidebarLink.jsx: -------------------------------------------------------------------------------- 1 | import * as Icons from "react-icons/vsc" 2 | import { useDispatch } from "react-redux" 3 | import { NavLink, matchPath, useLocation } from "react-router-dom" 4 | 5 | import { resetCourseState } from "../../../slices/courseSlice" 6 | 7 | export default function SidebarLink({ link, iconName }) { 8 | const Icon = Icons[iconName] 9 | const location = useLocation() 10 | const dispatch = useDispatch() 11 | 12 | const matchRoute = (route) => { 13 | return matchPath({ path: route }, location.pathname) 14 | } 15 | 16 | return ( 17 | dispatch(resetCourseState())} 20 | className={`relative px-8 py-2 text-sm font-medium ${ 21 | matchRoute(link.path) 22 | ? "bg-yellow-800 text-yellow-50" 23 | : "bg-opacity-0 text-richblack-300" 24 | } transition-all duration-200`} 25 | > 26 | 31 |
32 | {/* Icon Goes Here */} 33 | 34 | {link.name} 35 |
36 |
37 | ) 38 | } -------------------------------------------------------------------------------- /src/components/core/HomePage/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Button = ({ children, active, linkto }) => { 5 | return ( 6 | 7 |
12 | {children} 13 |
14 | 15 | ); 16 | }; 17 | 18 | export default Button; -------------------------------------------------------------------------------- /src/components/core/HomePage/CodeBlocks.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CTAButton from "./Button"; 3 | import { TypeAnimation } from "react-type-animation"; 4 | import { FaArrowRight } from "react-icons/fa"; 5 | 6 | const CodeBlocks = ({ 7 | position, 8 | heading, 9 | subheading, 10 | ctabtn1, 11 | ctabtn2, 12 | codeblock, 13 | backgroundGradient, 14 | codeColor, 15 | }) => { 16 | return ( 17 |
18 | 19 | 20 | {/* Section 1 */} 21 |
22 | {heading} 23 | 24 | {/* Sub Heading */} 25 |
26 | {subheading} 27 |
28 | 29 | {/* Button Group */} 30 |
31 | 32 |
33 | {ctabtn1.btnText} 34 | 35 |
36 |
37 | 38 | {ctabtn2.btnText} 39 | 40 |
41 |
42 | 43 | {/* Section 2 */} 44 |
45 | {backgroundGradient} 46 | {/* Indexing */} 47 |
48 |

1

49 |

2

50 |

3

51 |

4

52 |

5

53 |

6

54 |

7

55 |

8

56 |

9

57 |

10

58 |

11

59 |
60 | 61 | {/* Codes */} 62 |
65 | 75 |
76 |
77 |
78 | ); 79 | }; 80 | 81 | export default CodeBlocks; -------------------------------------------------------------------------------- /src/components/core/HomePage/CourseCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // Importing React Icons 4 | import { HiUsers } from "react-icons/hi"; 5 | import { ImTree } from "react-icons/im"; 6 | 7 | const CourseCard = ({cardData, currentCard, setCurrentCard}) => { 8 | return ( 9 |
setCurrentCard(cardData?.heading)} 16 | > 17 |
18 |
23 | {cardData?.heading} 24 |
25 | 26 |
{cardData?.description}
27 |
28 | 29 |
34 | {/* Level */} 35 |
36 | 37 |

{cardData?.level}

38 |
39 | 40 | {/* Flow Chart */} 41 |
42 | 43 |

{cardData?.lessionNumber} Lession

44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default CourseCard; -------------------------------------------------------------------------------- /src/components/core/HomePage/ExploreMore.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { HomePageExplore } from "../../../data/homepage-explore"; 3 | import CourseCard from "./CourseCard"; 4 | import HighlightText from "./HighlightText"; 5 | 6 | const tabsName = [ 7 | "Free", 8 | "New to coding", 9 | "Most popular", 10 | "Skills paths", 11 | "Career paths", 12 | ]; 13 | 14 | const ExploreMore = () => { 15 | const [currentTab, setCurrentTab] = useState(tabsName[0]); 16 | const [courses, setCourses] = useState(HomePageExplore[0].courses); 17 | const [currentCard, setCurrentCard] = useState( 18 | HomePageExplore[0].courses[0].heading 19 | ); 20 | 21 | const setMyCards = (value) => { 22 | setCurrentTab(value); 23 | const result = HomePageExplore.filter((course) => course.tag === value); 24 | setCourses(result[0].courses); 25 | setCurrentCard(result[0].courses[0].heading); 26 | }; 27 | 28 | return ( 29 |
30 | {/* Explore more section */} 31 |
32 |
33 | Unlock the 34 | 35 |

36 | Learn to Build Anything You Can Imagine 37 |

38 |
39 |
40 | 41 | {/* Tabs Section */} 42 |
43 | {tabsName.map((ele, index) => { 44 | return ( 45 |
setMyCards(ele)} 53 | > 54 | {ele} 55 |
56 | ); 57 | })} 58 |
59 |
60 | 61 | {/* Cards Group */} 62 |
63 | {courses.map((ele, index) => { 64 | return ( 65 | 71 | ); 72 | })} 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default ExploreMore; -------------------------------------------------------------------------------- /src/components/core/HomePage/HighlightText.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const HighlightText = ({text}) => { 4 | return ( 5 | 6 | {" "} 7 | {text} 8 | 9 | ); 10 | }; 11 | 12 | export default HighlightText; -------------------------------------------------------------------------------- /src/components/core/HomePage/InstructorSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CTAButton from "../../../components/core/HomePage/Button"; 3 | import { FaArrowRight } from "react-icons/fa"; 4 | import Instructor from "../../../assets/Images/Instructor.png"; 5 | import HighlightText from './HighlightText'; 6 | 7 | const InstructorSection = () => { 8 | return ( 9 |
10 |
11 |
12 | 17 |
18 |
19 |

20 | Become an 21 | 22 |

23 | 24 |

25 | Instructors from around the world teach millions of students on 26 | StudyNotion. We provide the tools and skills to teach what you 27 | love. 28 |

29 | 30 |
31 | 32 |
33 | Start Teaching Today 34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | ) 42 | } 43 | 44 | export default InstructorSection -------------------------------------------------------------------------------- /src/components/core/HomePage/LearningLanguageSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HighlightText from './HighlightText' 3 | import CTAButton from "../../../components/core/HomePage/Button"; 4 | import Know_your_progress from "../../../assets/Images/Know_your_progress.png"; 5 | import Compare_with_others from "../../../assets/Images/Compare_with_others.svg"; 6 | import Plan_your_lessons from "../../../assets/Images/Plan_your_lessons.svg"; 7 | 8 | const LearningLanguageSection = () => { 9 | return ( 10 |
11 |
12 | Your swiss knife for 13 | 14 |
15 | Using spin making learning multiple languages easy. with 20+ 16 | languages realistic voice-over, progress tracking, custom schedule 17 | and more. 18 |
19 |
20 | 25 | 30 | 35 |
36 |
37 | 38 |
39 | 40 |
Learn More
41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export default LearningLanguageSection -------------------------------------------------------------------------------- /src/components/core/HomePage/TimelineSection.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TimeLineImage from "../../../assets/Images/TimelineImage.png"; 3 | import Logo1 from "../../../assets/TimeLineLogo/Logo1.svg"; 4 | import Logo2 from "../../../assets/TimeLineLogo/Logo2.svg"; 5 | import Logo3 from "../../../assets/TimeLineLogo/Logo3.svg"; 6 | import Logo4 from "../../../assets/TimeLineLogo/Logo4.svg"; 7 | 8 | const TimeLine = [ 9 | { 10 | Logo: Logo1, 11 | Heading: "Leadership", 12 | Description: "Fully committed to the success company", 13 | }, 14 | { 15 | Logo: Logo2, 16 | Heading: "Responsibility", 17 | Description: "Students will always be our top priority", 18 | }, 19 | { 20 | Logo: Logo3, 21 | Heading: "Flexibility", 22 | Description: "The ability to switch is an important skills", 23 | }, 24 | { 25 | Logo: Logo4, 26 | Heading: "Solve the problem", 27 | Description: "Code your way to a solution", 28 | }, 29 | ]; 30 | 31 | 32 | const TimelineSection = () => { 33 | return ( 34 |
35 |
36 |
37 | {TimeLine.map((ele, i) => { 38 | return ( 39 |
40 |
41 |
42 | 43 |
44 |
45 |

{ele.Heading}

46 |

{ele.Description}

47 |
48 |
49 |
54 |
55 | ); 56 | })} 57 |
58 |
59 |
60 | {/* Section 1 */} 61 |
62 |

10

63 |

64 | Years experiences 65 |

66 |
67 | 68 | {/* Section 2 */} 69 |
70 |

250

71 |

72 | types of courses 73 |

74 |
75 |
76 |
77 | timelineImage 82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | export default TimelineSection; -------------------------------------------------------------------------------- /src/components/core/ViewCourse/CourseReviewModal.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { useForm } from "react-hook-form" 3 | import { RxCross2 } from "react-icons/rx" 4 | import ReactStars from "react-rating-stars-component" 5 | import { useSelector } from "react-redux" 6 | 7 | import { createRating } from "../../../services/operations/courseDetailsAPI" 8 | import IconBtn from "../../common/IconBtn" 9 | 10 | export default function CourseReviewModal({ setReviewModal }) { 11 | const { user } = useSelector((state) => state.profile) 12 | const { token } = useSelector((state) => state.auth) 13 | const { courseEntireData } = useSelector((state) => state.viewCourse) 14 | 15 | const { 16 | register, 17 | handleSubmit, 18 | setValue, 19 | formState: { errors }, 20 | } = useForm() 21 | 22 | useEffect(() => { 23 | setValue("courseExperience", "") 24 | setValue("courseRating", 0) 25 | // eslint-disable-next-line react-hooks/exhaustive-deps 26 | }, []) 27 | 28 | const ratingChanged = (newRating) => { 29 | // console.log(newRating) 30 | setValue("courseRating", newRating) 31 | } 32 | 33 | const onSubmit = async (data) => { 34 | await createRating( 35 | { 36 | courseId: courseEntireData._id, 37 | rating: data.courseRating, 38 | review: data.courseExperience, 39 | }, 40 | token 41 | ) 42 | setReviewModal(false) 43 | } 44 | 45 | return ( 46 |
47 |
48 | {/* Modal Header */} 49 |
50 |

Add Review

51 | 54 |
55 | {/* Modal Body */} 56 |
57 |
58 | {user?.firstName 63 |
64 |

65 | {user?.firstName} {user?.lastName} 66 |

67 |

Posting Publicly

68 |
69 |
70 |
74 | 80 |
81 | 87 |