├── .editorconfig ├── .gitignore ├── .nvmrc ├── .prettierignore ├── README.md ├── package.json ├── prettier.config.js ├── public ├── _redirects ├── favicon.ico ├── index.html ├── logo.png ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server ├── .gitignore ├── README.md ├── config │ ├── cloudinary.js │ ├── database.js │ └── razorpay.js ├── controllers │ ├── Auth.js │ ├── Category.js │ ├── ContactUs.js │ ├── Course.js │ ├── RatingandReview.js │ ├── Section.js │ ├── Subsection.js │ ├── courseProgress.js │ ├── payments.js │ ├── profile.js │ └── resetPassword.js ├── index.js ├── mail │ └── templates │ │ ├── contactFormRes.js │ │ ├── courseEnrollmentEmail.js │ │ ├── emailVerificationTemplate.js │ │ ├── passwordUpdate.js │ │ └── paymentSuccessEmail.js ├── middleware │ └── auth.js ├── models │ ├── Category.js │ ├── Course.js │ ├── CourseProgress.js │ ├── OTP.js │ ├── Profile.js │ ├── RatingandReview.js │ ├── Section.js │ ├── Subsection.js │ └── User.js ├── package.json ├── routes │ ├── Contact.js │ ├── Course.js │ ├── Payments.js │ ├── profile.js │ └── user.js └── utils │ ├── imageUploader.js │ ├── mailSender.js │ └── secToDuration.js ├── src ├── App.css ├── App.jsx ├── 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 │ ├── 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 │ │ ├── Course_Card.jsx │ │ └── Course_Slider.jsx │ │ ├── ContactUsPage │ │ ├── ContactDetails.jsx │ │ ├── ContactForm.jsx │ │ └── ContactUsForm.jsx │ │ ├── Course │ │ ├── CourseAccordionBar.jsx │ │ ├── CourseDetailsCard.jsx │ │ └── CourseSubSectionAccordion.jsx │ │ ├── Dashboard │ │ ├── AddCourse │ │ │ ├── CourseBuilder │ │ │ │ ├── CourseBuilderForm.jsx │ │ │ │ ├── NestedView.jsx │ │ │ │ └── SubSectionModal.jsx │ │ │ ├── CourseInformation │ │ │ │ ├── ChipInput.jsx │ │ │ │ ├── CourseInformationForm.jsx │ │ │ │ └── RequirementsField.jsx │ │ │ ├── PublishCourse │ │ │ │ └── index.jsx │ │ │ ├── RenderSteps.jsx │ │ │ ├── Upload.jsx │ │ │ └── index.jsx │ │ ├── Cart │ │ │ ├── RenderCartCourses.jsx │ │ │ ├── RenderTotalAmount.jsx │ │ │ └── index.jsx │ │ ├── EditCourse │ │ │ └── index.jsx │ │ ├── EnrolledCourses.jsx │ │ ├── Instructor.jsx │ │ ├── InstructorCourses │ │ │ └── CoursesTable.jsx │ │ ├── InstructorDashboard │ │ │ └── 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 │ │ └── Timeline.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.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 │ │ ├── pageAndComponntDatas.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 /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | package-lock.json 26 | pnpm-lock.yaml 27 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.18.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | build 5 | .contentlayer 6 | package-lock.json 7 | yarn.lock 8 | pnpm-lock.yaml 9 | .env 10 | .env.local 11 | .env.development 12 | .env.test 13 | .env.production 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | # StudyNotion Edtech Project 3 | ======= 4 | # StudyNotion-EdTech 5 | Full Stack EdTech platform using MERN stack 6 | >>>>>>> 751135cacc979c8ffdbb7c75b78e66c7dbfbc596 7 | 8 | -Full-Stack MERN Web 9 | -Tailwind Used 10 | 11 | Usage 12 | To run this web on your computer, follow these steps: 13 | 14 | Clone the repository to your local machine. 15 | 16 | git clone https://github.com/AyushR97j/StudyNotion-EdTech.git 17 | Install the required packages. 18 | 19 | cd StudyNotion-EdTech 20 | npm install 21 | Start the development server: 22 | 23 | npm run dev 24 | Open the project in your browser at http://localhost:3000 to view your project. 25 | 26 | -Build this project in Love Babbar's Dot Batch. 27 | 28 | Contributing 29 | Contributions are welcome! If you have any suggestions or find any issues, please feel free to open an issue or a pull request. 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studynotion-client", 3 | "description": "This is the client side of the StudyNotion App", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@ramonak/react-progress-bar": "^5.0.3", 8 | "@reduxjs/toolkit": "^1.9.5", 9 | "@testing-library/jest-dom": "^5.16.5", 10 | "@testing-library/react": "^13.4.0", 11 | "@testing-library/user-event": "^13.5.0", 12 | "axios": "^1.4.0", 13 | "bcrypt": "^5.1.0", 14 | "chart.js": "^4.3.0", 15 | "cloudinary": "^1.40.0", 16 | "cookie-parser": "^1.4.6", 17 | "copy-to-clipboard": "^3.3.3", 18 | "cors": "^2.8.5", 19 | "express-fileupload": "^1.4.0", 20 | "jsonwebtoken": "^9.0.1", 21 | "mongoose": "^7.4.1", 22 | "nodemailer": "^6.9.4", 23 | "nodemon": "^3.0.1", 24 | "otp-generator": "^4.0.1", 25 | "razorpay": "^2.9.1", 26 | "react": "^18.2.0", 27 | "react-chartjs-2": "^5.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-dropzone": "^14.2.3", 30 | "react-hook-form": "^7.43.9", 31 | "react-hot-toast": "^2.4.0", 32 | "react-icons": "^4.8.0", 33 | "react-markdown": "^8.0.7", 34 | "react-otp-input": "^3.0.0", 35 | "react-rating-stars-component": "^2.2.0", 36 | "react-redux": "^8.0.5", 37 | "react-router-dom": "^6.9.0", 38 | "react-scripts": "5.0.1", 39 | "react-super-responsive-table": "^5.2.1", 40 | "react-type-animation": "^3.0.1", 41 | "showdown": "^2.1.0", 42 | "swiper": "^9.3.1", 43 | "video-react": "^0.16.0", 44 | "web-vitals": "^2.1.4" 45 | }, 46 | "scripts": { 47 | "start": "react-scripts start", 48 | "build": "react-scripts build", 49 | "test": "react-scripts test", 50 | "eject": "react-scripts eject", 51 | "server": "cd server && npm run dev", 52 | "dev": "concurrently -n \"client,server\" -c \"bgBlue,bgYellow\" \"npm start\" \"npm run server\"" 53 | }, 54 | "eslintConfig": { 55 | "extends": [ 56 | "react-app", 57 | "react-app/jest" 58 | ] 59 | }, 60 | "browserslist": { 61 | "production": [ 62 | ">0.2%", 63 | "not dead", 64 | "not op_mini all" 65 | ], 66 | "development": [ 67 | "last 1 chrome version", 68 | "last 1 firefox version", 69 | "last 1 safari version" 70 | ] 71 | }, 72 | "devDependencies": { 73 | "@ianvs/prettier-plugin-sort-imports": "^3.7.2", 74 | "concurrently": "^8.2.0", 75 | "prettier": "^2.8.8", 76 | "prettier-plugin-tailwindcss": "^0.3.0", 77 | "tailwindcss": "^3.2.7" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | module.exports = { 3 | endOfLine: "lf", 4 | semi: false, 5 | singleQuote: false, 6 | tabWidth: 2, 7 | trailingComma: "es5", 8 | bracketSpacing: true, 9 | importOrder: [ 10 | "^(react/(.*)$)|^(react$)", 11 | "^(next/(.*)$)|^(next$)", 12 | "", 13 | "", 14 | "^types$", 15 | "^@local/(.*)$", 16 | "^@/config/(.*)$", 17 | "^@/lib/(.*)$", 18 | "^@/components/(.*)$", 19 | "^@/styles/(.*)$", 20 | "^[./]", 21 | ], 22 | importOrderSeparation: false, 23 | importOrderSortSpecifiers: true, 24 | importOrderBuiltinModulesToTop: true, 25 | importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"], 26 | importOrderMergeDuplicateImports: true, 27 | importOrderCombineTypeAndValueImports: true, 28 | plugins: ["@ianvs/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"], 29 | }; 30 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | StudyNotion 21 | 22 | 23 | 24 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/public/logo.png -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/server/README.md -------------------------------------------------------------------------------- /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 | }; 15 | -------------------------------------------------------------------------------- /server/config/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | require("dotenv").config(); 3 | 4 | const MONGODB_URL = process.env.MONGODB_URL; 5 | 6 | exports.connect = () => { 7 | mongoose 8 | .connect(MONGODB_URL, { 9 | useNewUrlparser: true, 10 | useUnifiedTopology: true, 11 | }) 12 | .then(console.log(`DB Connection Success`)) 13 | .catch((err) => { 14 | console.log(`DB Connection Failed`); 15 | console.log(err); 16 | process.exit(1); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /server/config/razorpay.js: -------------------------------------------------------------------------------- 1 | const Razorpay = require("razorpay"); 2 | 3 | // exports.instance = new Razorpay({ 4 | // key_id: process.env.RAZORPAY_KEY, 5 | // key_secret: process.env.RAZORPAY_SECRET, 6 | // }); 7 | 8 | const RAZORPAY_KEY = "rzp_test_fUIZAI25WMgGwi" 9 | 10 | const RAZORPAY_SECRET = "o7TBIxOogcPsNQa9pTzsAoYf" 11 | 12 | exports.instance = new Razorpay({ 13 | key_id: RAZORPAY_KEY, 14 | key_secret: RAZORPAY_SECRET, 15 | }); -------------------------------------------------------------------------------- /server/controllers/Category.js: -------------------------------------------------------------------------------- 1 | const Category = require("../models/Category") 2 | 3 | function getRandomInt(max) { 4 | return Math.floor(Math.random() * max) 5 | } 6 | exports.createCategory = async (req, res) => { 7 | try { 8 | const { name, description } = req.body 9 | if (!name) { 10 | return res 11 | .status(400) 12 | .json({ success: false, message: "All fields are required" }) 13 | } 14 | const CategorysDetails = await Category.create({ 15 | name: name, 16 | description: description, 17 | }) 18 | console.log(CategorysDetails) 19 | return res.status(200).json({ 20 | success: true, 21 | message: "Categorys Created Successfully", 22 | }) 23 | } catch (error) { 24 | return res.status(500).json({ 25 | success: true, 26 | message: error.message, 27 | }) 28 | } 29 | } 30 | 31 | exports.showAllCategories = async (req, res) => { 32 | try { 33 | const allCategorys = await Category.find() 34 | res.status(200).json({ 35 | success: true, 36 | data: allCategorys, 37 | }) 38 | } catch (error) { 39 | return res.status(500).json({ 40 | success: false, 41 | message: error.message, 42 | }) 43 | } 44 | } 45 | 46 | exports.categoryPageDetails = async (req, res) => { 47 | try { 48 | const { categoryId } = req.body 49 | 50 | // Get courses for the specified category 51 | const selectedCategory = await Category.findById(categoryId) 52 | .populate({ 53 | path: "courses", 54 | match: { status: "Published" }, 55 | populate: "ratingAndReviews", 56 | }) 57 | .exec() 58 | 59 | console.log("SELECTED COURSE", selectedCategory) 60 | // Handle the case when the category is not found 61 | if (!selectedCategory) { 62 | console.log("Category not found.") 63 | return res 64 | .status(404) 65 | .json({ success: false, message: "Category not found" }) 66 | } 67 | // Handle the case when there are no courses 68 | if (selectedCategory.courses.length === 0) { 69 | console.log("No courses found for the selected category.") 70 | return res.status(404).json({ 71 | success: false, 72 | message: "No courses found for the selected category.", 73 | }) 74 | } 75 | 76 | // Get courses for other categories 77 | const categoriesExceptSelected = await Category.find({ 78 | _id: { $ne: categoryId }, 79 | }) 80 | let differentCategory = await Category.findOne( 81 | categoriesExceptSelected[getRandomInt(categoriesExceptSelected.length)] 82 | ._id 83 | ) 84 | .populate({ 85 | path: "courses", 86 | match: { status: "Published" }, 87 | }) 88 | .exec() 89 | console.log() 90 | // Get top-selling courses across all categories 91 | const allCategories = await Category.find() 92 | .populate({ 93 | path: "courses", 94 | match: { status: "Published" }, 95 | }) 96 | .exec() 97 | const allCourses = allCategories.flatMap((category) => category.courses) 98 | const mostSellingCourses = allCourses 99 | .sort((a, b) => b.sold - a.sold) 100 | .slice(0, 10) 101 | 102 | res.status(200).json({ 103 | success: true, 104 | data: { 105 | selectedCategory, 106 | differentCategory, 107 | mostSellingCourses, 108 | }, 109 | }) 110 | } catch (error) { 111 | return res.status(500).json({ 112 | success: false, 113 | message: "Internal server error", 114 | error: error.message, 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /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 | } 27 | -------------------------------------------------------------------------------- /server/controllers/RatingandReview.js: -------------------------------------------------------------------------------- 1 | const RatingAndReview = require("../models/RatingandReview") 2 | const Course = require("../models/Course") 3 | const mongoose = require("mongoose") 4 | 5 | // Create a new rating and review 6 | exports.createRating = async (req, res) => { 7 | try { 8 | const userId = req.user.id 9 | const { rating, review, courseId } = req.body 10 | 11 | // Check if the user is enrolled in the course 12 | 13 | const courseDetails = await Course.findOne({ 14 | _id: courseId, 15 | studentsEnroled: { $elemMatch: { $eq: userId } }, 16 | }) 17 | 18 | if (!courseDetails) { 19 | return res.status(404).json({ 20 | success: false, 21 | message: "Student is not enrolled in this course", 22 | }) 23 | } 24 | 25 | // Check if the user has already reviewed the course 26 | const alreadyReviewed = await RatingAndReview.findOne({ 27 | user: userId, 28 | course: courseId, 29 | }) 30 | 31 | if (alreadyReviewed) { 32 | return res.status(403).json({ 33 | success: false, 34 | message: "Course already reviewed by user", 35 | }) 36 | } 37 | 38 | // Create a new rating and review 39 | const ratingReview = await RatingAndReview.create({ 40 | rating, 41 | review, 42 | course: courseId, 43 | user: userId, 44 | }) 45 | 46 | // Add the rating and review to the course 47 | await Course.findByIdAndUpdate(courseId, { 48 | $push: { 49 | ratingAndReviews: ratingReview, 50 | }, 51 | }) 52 | await courseDetails.save() 53 | 54 | return res.status(201).json({ 55 | success: true, 56 | message: "Rating and review created successfully", 57 | ratingReview, 58 | }) 59 | } catch (error) { 60 | console.error(error) 61 | return res.status(500).json({ 62 | success: false, 63 | message: "Internal server error", 64 | error: error.message, 65 | }) 66 | } 67 | } 68 | 69 | // Get the average rating for a course 70 | exports.getAverageRating = async (req, res) => { 71 | try { 72 | const courseId = req.body.courseId 73 | 74 | // Calculate the average rating using the MongoDB aggregation pipeline 75 | const result = await RatingAndReview.aggregate([ 76 | { 77 | $match: { 78 | course: new mongoose.Types.ObjectId(courseId), // Convert courseId to ObjectId 79 | }, 80 | }, 81 | { 82 | $group: { 83 | _id: null, 84 | averageRating: { $avg: "$rating" }, 85 | }, 86 | }, 87 | ]) 88 | 89 | if (result.length > 0) { 90 | return res.status(200).json({ 91 | success: true, 92 | averageRating: result[0].averageRating, 93 | }) 94 | } 95 | 96 | // If no ratings are found, return 0 as the default rating 97 | return res.status(200).json({ success: true, averageRating: 0 }) 98 | } catch (error) { 99 | console.error(error) 100 | return res.status(500).json({ 101 | success: false, 102 | message: "Failed to retrieve the rating for the course", 103 | error: error.message, 104 | }) 105 | } 106 | } 107 | 108 | // Get all rating and reviews 109 | exports.getAllRatingReview = async (req, res) => { 110 | try { 111 | const allReviews = await RatingAndReview.find({}) 112 | .sort({ rating: "desc" }) 113 | .populate({ 114 | path: "user", 115 | select: "firstName lastName email image", // Specify the fields you want to populate from the "Profile" model 116 | }) 117 | .populate({ 118 | path: "course", 119 | select: "courseName", //Specify the fields you want to populate from the "Course" model 120 | }) 121 | .exec() 122 | 123 | res.status(200).json({ 124 | success: true, 125 | data: allReviews, 126 | }) 127 | } catch (error) { 128 | console.error(error) 129 | return res.status(500).json({ 130 | success: false, 131 | message: "Failed to retrieve the rating and review for the course", 132 | error: error.message, 133 | }) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /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 | const course = await Course.findById(courseId) 65 | .populate({ 66 | path: "courseContent", 67 | populate: { 68 | path: "subSection", 69 | }, 70 | }) 71 | .exec() 72 | console.log(course) 73 | res.status(200).json({ 74 | success: true, 75 | message: section, 76 | data: course, 77 | }) 78 | } catch (error) { 79 | console.error("Error updating section:", error) 80 | res.status(500).json({ 81 | success: false, 82 | message: "Internal server error", 83 | error: error.message, 84 | }) 85 | } 86 | } 87 | 88 | // DELETE a section 89 | exports.deleteSection = async (req, res) => { 90 | try { 91 | const { sectionId, courseId } = req.body 92 | await Course.findByIdAndUpdate(courseId, { 93 | $pull: { 94 | courseContent: sectionId, 95 | }, 96 | }) 97 | const section = await Section.findById(sectionId) 98 | console.log(sectionId, courseId) 99 | if (!section) { 100 | return res.status(404).json({ 101 | success: false, 102 | message: "Section not found", 103 | }) 104 | } 105 | // Delete the associated subsections 106 | await SubSection.deleteMany({ _id: { $in: section.subSection } }) 107 | 108 | await Section.findByIdAndDelete(sectionId) 109 | 110 | // find the updated course and return it 111 | const course = await Course.findById(courseId) 112 | .populate({ 113 | path: "courseContent", 114 | populate: { 115 | path: "subSection", 116 | }, 117 | }) 118 | .exec() 119 | 120 | res.status(200).json({ 121 | success: true, 122 | message: "Section deleted", 123 | data: course, 124 | }) 125 | } catch (error) { 126 | console.error("Error deleting section:", error) 127 | res.status(500).json({ 128 | success: false, 129 | message: "Internal server error", 130 | error: error.message, 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /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 | // } 100 | -------------------------------------------------------------------------------- /server/controllers/resetPassword.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User") 2 | const mailSender = require("../utils/mailSender") 3 | const bcrypt = require("bcrypt") 4 | const crypto = require("crypto") 5 | exports.resetPasswordToken = async (req, res) => { 6 | try { 7 | const email = req.body.email 8 | const user = await User.findOne({ email: email }) 9 | if (!user) { 10 | return res.json({ 11 | success: false, 12 | message: `This Email: ${email} is not Registered With Us Enter a Valid Email `, 13 | }) 14 | } 15 | const token = crypto.randomBytes(20).toString("hex") 16 | 17 | const updatedDetails = await User.findOneAndUpdate( 18 | { email: email }, 19 | { 20 | token: token, 21 | resetPasswordExpires: Date.now() + 3600000, 22 | }, 23 | { new: true } 24 | ) 25 | console.log("DETAILS", updatedDetails) 26 | 27 | // const url = `http://localhost:3000/update-password/${token}` 28 | const url = `https://studynotion-edtech-project.vercel.app/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 | } 91 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | // Importing necessary modules and packages 2 | const express = require("express"); 3 | const app = express(); 4 | const userRoutes = require("./routes/user"); 5 | const profileRoutes = require("./routes/profile"); 6 | const courseRoutes = require("./routes/Course"); 7 | const paymentRoutes = require("./routes/Payments"); 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 | // Loading environment variables from .env file 17 | dotenv.config(); 18 | 19 | // Setting up port number 20 | const PORT = process.env.PORT || 4000; 21 | 22 | 23 | 24 | // Connecting to database 25 | database.connect(); 26 | 27 | // Middlewares 28 | app.use(express.json()); 29 | app.use(cookieParser()); 30 | app.use( 31 | cors({ 32 | origin: "*", 33 | credentials: true, 34 | }) 35 | ); 36 | app.use( 37 | fileUpload({ 38 | useTempFiles: true, 39 | tempFileDir: "/tmp/", 40 | }) 41 | ); 42 | 43 | // Connecting to cloudinary 44 | cloudinaryConnect(); 45 | 46 | // Setting up routes 47 | app.use("/api/v1/auth", userRoutes); 48 | app.use("/api/v1/profile", profileRoutes); 49 | app.use("/api/v1/course", courseRoutes); 50 | app.use("/api/v1/payment", paymentRoutes); 51 | app.use("/api/v1/reach", contactUsRoute); 52 | 53 | // Testing the server 54 | app.get("/", (req, res) => { 55 | return res.json({ 56 | success: true, 57 | message: "Your server is up and running ...", 58 | }); 59 | }); 60 | 61 | // Listening to the server 62 | app.listen(PORT, () => { 63 | console.log(`App is listening at ${PORT}`); 64 | }); 65 | 66 | // End of code. 67 | -------------------------------------------------------------------------------- /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 | } 98 | -------------------------------------------------------------------------------- /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 | }; 88 | -------------------------------------------------------------------------------- /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; 88 | -------------------------------------------------------------------------------- /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 | }; 76 | -------------------------------------------------------------------------------- /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 | } 86 | -------------------------------------------------------------------------------- /server/middleware/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 | }; 99 | -------------------------------------------------------------------------------- /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); 20 | -------------------------------------------------------------------------------- /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 | studentsEnroled: [ 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: { type: Date, default: Date.now }, 57 | }) 58 | 59 | // Export the Courses model 60 | module.exports = mongoose.model("Course", coursesSchema) 61 | -------------------------------------------------------------------------------- /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) 21 | -------------------------------------------------------------------------------- /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; 54 | -------------------------------------------------------------------------------- /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/RatingandReview.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); 28 | -------------------------------------------------------------------------------- /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); 19 | -------------------------------------------------------------------------------- /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); 11 | -------------------------------------------------------------------------------- /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 | }, 64 | courseProgress: [ 65 | { 66 | type: mongoose.Schema.Types.ObjectId, 67 | ref: "courseProgress", 68 | }, 69 | ], 70 | 71 | // Add timestamps for when the document is created and last modified 72 | }, 73 | { timestamps: true } 74 | ) 75 | 76 | // Export the Mongoose model for the user schema, using the name "user" 77 | module.exports = mongoose.model("user", userSchema) 78 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studynotion-backend", 3 | "version": "1.0.0", 4 | "description": "studynotion-backend", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js" 9 | }, 10 | "keywords": [ 11 | "studynotion-backend" 12 | ], 13 | "author": "Saikat Mukherjee", 14 | "license": "ISC", 15 | "dependencies": { 16 | "bcrypt": "^5.1.0", 17 | "bcryptjs": "^2.4.3", 18 | "cloudinary": "^1.36.4", 19 | "cookie-parser": "^1.4.6", 20 | "cors": "^2.8.5", 21 | "crypto-random-string": "^5.0.0", 22 | "dotenv": "^16.0.3", 23 | "express": "^4.18.2", 24 | "express-fileupload": "^1.4.0", 25 | "jsonwebtoken": "^9.0.0", 26 | "mongoose": "^7.0.3", 27 | "node-schedule": "^2.1.1", 28 | "nodemailer": "^6.9.1", 29 | "nodemon": "^2.0.22", 30 | "otp-generator": "^4.0.1", 31 | "razorpay": "^2.8.6" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 8 | -------------------------------------------------------------------------------- /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 | // Tags Controllers Import 19 | 20 | // Categories Controllers Import 21 | const { 22 | showAllCategories, 23 | createCategory, 24 | categoryPageDetails, 25 | } = require("../controllers/Category") 26 | 27 | // Sections Controllers Import 28 | const { 29 | createSection, 30 | updateSection, 31 | deleteSection, 32 | } = require("../controllers/Section") 33 | 34 | // Sub-Sections Controllers Import 35 | const { 36 | createSubSection, 37 | updateSubSection, 38 | deleteSubSection, 39 | } = require("../controllers/Subsection") 40 | 41 | // Rating Controllers Import 42 | const { 43 | createRating, 44 | getAverageRating, 45 | getAllRatingReview, 46 | } = require("../controllers/RatingandReview") 47 | const { 48 | updateCourseProgress, 49 | getProgressPercentage, 50 | } = require("../controllers/courseProgress") 51 | // Importing Middlewares 52 | const { auth, isInstructor, isStudent, isAdmin } = require("../middleware/auth") 53 | 54 | // ******************************************************************************************************** 55 | // Course routes 56 | // ******************************************************************************************************** 57 | 58 | // Courses can Only be Created by Instructors 59 | router.post("/createCourse", auth, isInstructor, createCourse) 60 | // Edit Course routes 61 | router.post("/editCourse", auth, isInstructor, editCourse) 62 | //Add a Section to a Course 63 | router.post("/addSection", auth, isInstructor, createSection) 64 | // Update a Section 65 | router.post("/updateSection", auth, isInstructor, updateSection) 66 | // Delete a Section 67 | router.post("/deleteSection", auth, isInstructor, deleteSection) 68 | // Edit Sub Section 69 | router.post("/updateSubSection", auth, isInstructor, updateSubSection) 70 | // Delete Sub Section 71 | router.post("/deleteSubSection", auth, isInstructor, deleteSubSection) 72 | // Add a Sub Section to a Section 73 | router.post("/addSubSection", auth, isInstructor, createSubSection) 74 | // Get all Courses Under a Specific Instructor 75 | router.get("/getInstructorCourses", auth, isInstructor, getInstructorCourses) 76 | // Get all Registered Courses 77 | router.get("/getAllCourses", getAllCourses) 78 | // Get Details for a Specific Courses 79 | router.post("/getCourseDetails", getCourseDetails) 80 | // Get Details for a Specific Courses 81 | router.post("/getFullCourseDetails", auth, getFullCourseDetails) 82 | // To Update Course Progress 83 | router.post("/updateCourseProgress", auth, isStudent, updateCourseProgress) 84 | // To get Course Progress 85 | // router.post("/getProgressPercentage", auth, isStudent, getProgressPercentage) 86 | // Delete a Course 87 | router.delete("/deleteCourse", deleteCourse) 88 | 89 | // ******************************************************************************************************** 90 | // Category routes (Only by Admin) 91 | // ******************************************************************************************************** 92 | // Category can Only be Created by Admin 93 | // TODO: Put IsAdmin Middleware here 94 | router.post("/createCategory", auth, isAdmin, createCategory) 95 | router.get("/showAllCategories", showAllCategories) 96 | router.post("/getCategoryPageDetails", categoryPageDetails) 97 | 98 | // ******************************************************************************************************** 99 | // Rating and Review 100 | // ******************************************************************************************************** 101 | router.post("/createRating", auth, isStudent, createRating) 102 | router.get("/getAverageRating", getAverageRating) 103 | router.get("/getReviews", getAllRatingReview) 104 | 105 | module.exports = router 106 | -------------------------------------------------------------------------------- /server/routes/Payments.js: -------------------------------------------------------------------------------- 1 | // Import the required modules 2 | const express = require("express") 3 | const router = express.Router() 4 | const { 5 | capturePayment, 6 | // verifySignature, 7 | verifyPayment, 8 | sendPaymentSuccessEmail, 9 | } = require("../controllers/payments") 10 | const { auth, isInstructor, isStudent, isAdmin } = require("../middleware/auth") 11 | router.post("/capturePayment", auth, isStudent, capturePayment) 12 | router.post("/verifyPayment", auth, isStudent, verifyPayment) 13 | router.post( 14 | "/sendPaymentSuccessEmail", 15 | auth, 16 | isStudent, 17 | sendPaymentSuccessEmail 18 | ) 19 | // router.post("/verifySignature", verifySignature) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /server/routes/profile.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const { auth, isInstructor } = require("../middleware/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 26 | -------------------------------------------------------------------------------- /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("../middleware/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 49 | -------------------------------------------------------------------------------- /server/utils/imageUploader.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require("cloudinary").v2 2 | 3 | exports.uploadImageToCloudinary = async (file, folder, height, quality) => { 4 | const options = { folder } 5 | if (height) { 6 | options.height = height 7 | } 8 | if (quality) { 9 | options.quality = quality 10 | } 11 | options.resource_type = "auto" 12 | console.log("OPTIONS", options) 13 | return await cloudinary.uploader.upload(file.tempFilePath, options) 14 | } 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 | secure: false, 12 | }) 13 | 14 | let info = await transporter.sendMail({ 15 | from: `"Studynotion | CodeHelp" <${process.env.MAIL_USER}>`, // sender address 16 | to: `${email}`, // list of receivers 17 | subject: `${title}`, // Subject line 18 | html: `${body}`, // html body 19 | }) 20 | console.log(info.response) 21 | return info 22 | } catch (error) { 23 | console.log(error.message) 24 | return error.message 25 | } 26 | } 27 | 28 | module.exports = mailSender 29 | -------------------------------------------------------------------------------- /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 | } 19 | -------------------------------------------------------------------------------- /src/assets/Images/Compare_with_others.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/Compare_with_others.png -------------------------------------------------------------------------------- /src/assets/Images/FoundingStory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/FoundingStory.png -------------------------------------------------------------------------------- /src/assets/Images/Instructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/Instructor.png -------------------------------------------------------------------------------- /src/assets/Images/Know_your_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/Know_your_progress.png -------------------------------------------------------------------------------- /src/assets/Images/Plan_your_lessons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/Plan_your_lessons.png -------------------------------------------------------------------------------- /src/assets/Images/TimelineImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/TimelineImage.png -------------------------------------------------------------------------------- /src/assets/Images/aboutus1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/aboutus1.webp -------------------------------------------------------------------------------- /src/assets/Images/aboutus2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/aboutus2.webp -------------------------------------------------------------------------------- /src/assets/Images/aboutus3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/aboutus3.webp -------------------------------------------------------------------------------- /src/assets/Images/banner.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/banner.mp4 -------------------------------------------------------------------------------- /src/assets/Images/boxoffice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/boxoffice.png -------------------------------------------------------------------------------- /src/assets/Images/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/frame.png -------------------------------------------------------------------------------- /src/assets/Images/login.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/login.webp -------------------------------------------------------------------------------- /src/assets/Images/signup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Images/signup.webp -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Full-Dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Logo/Logo-Full-Dark.png -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Full-Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Logo/Logo-Full-Light.png -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Small-Dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Logo/Logo-Small-Dark.png -------------------------------------------------------------------------------- /src/assets/Logo/Logo-Small-Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/src/assets/Logo/Logo-Small-Light.png -------------------------------------------------------------------------------- /src/assets/Logo/rzp_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyushR97j/EduNest-EdTech/d7a5d31d18cd1fe48da9c233991f5d0868115021/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/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 | } 29 | -------------------------------------------------------------------------------- /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 | } 30 | -------------------------------------------------------------------------------- /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 39 | -------------------------------------------------------------------------------- /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 108 | -------------------------------------------------------------------------------- /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 | } 25 | -------------------------------------------------------------------------------- /src/components/core/AboutPage/ContactFormSection.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ContactUsForm from "../ContactUsPage/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; 19 | -------------------------------------------------------------------------------- /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 | highliteText: "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; 95 | -------------------------------------------------------------------------------- /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; 35 | -------------------------------------------------------------------------------- /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 | 51 | 80 | 86 |
87 | ) 88 | } 89 | 90 | export default LoginForm 91 | -------------------------------------------------------------------------------- /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 16 | -------------------------------------------------------------------------------- /src/components/core/Auth/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | // This will prevent non-authenticated users from accessing this route 2 | import { useSelector } from "react-redux" 3 | import { Navigate } from "react-router-dom" 4 | 5 | function PrivateRoute({ 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 PrivateRoute 16 | -------------------------------------------------------------------------------- /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 | } 58 | -------------------------------------------------------------------------------- /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 53 | -------------------------------------------------------------------------------- /src/components/core/Catalog/Course_Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | // Icons 3 | import { FaRegStar, FaStar } from "react-icons/fa" 4 | import ReactStars from "react-rating-stars-component" 5 | import { Link } from "react-router-dom" 6 | 7 | import GetAvgRating from "../../../utils/avgRating" 8 | import RatingStars from "../../Common/RatingStars" 9 | 10 | function Course_Card({ course, Height }) { 11 | // const avgReviewCount = GetAvgRating(course.ratingAndReviews) 12 | // console.log(course.ratingAndReviews) 13 | const [avgReviewCount, setAvgReviewCount] = useState(0) 14 | useEffect(() => { 15 | const count = GetAvgRating(course.ratingAndReviews) 16 | setAvgReviewCount(count) 17 | }, [course]) 18 | // console.log("count............", avgReviewCount) 19 | 20 | return ( 21 | <> 22 | 23 |
24 |
25 | course thumnail 30 |
31 |
32 |

{course?.courseName}

33 |

34 | {course?.instructor?.firstName} {course?.instructor?.lastName} 35 |

36 |
37 | {avgReviewCount || 0} 38 | {/* } 45 | fullIcon={} 46 | /> */} 47 | 48 | 49 | {course?.ratingAndReviews?.length} Ratings 50 | 51 |
52 |

Rs. {course?.price}

53 |
54 |
55 | 56 | 57 | ) 58 | } 59 | 60 | export default Course_Card 61 | -------------------------------------------------------------------------------- /src/components/core/Catalog/Course_Slider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react" 2 | // Import Swiper React components 3 | import { Swiper, SwiperSlide } from "swiper/react" 4 | 5 | // Import Swiper styles 6 | import "swiper/css" 7 | import "swiper/css/free-mode" 8 | import "swiper/css/pagination" 9 | // import "../../.." 10 | // Import required modules 11 | import { FreeMode, Pagination } from "swiper" 12 | 13 | // import { getAllCourses } from "../../services/operations/courseDetailsAPI" 14 | import Course_Card from "./Course_Card" 15 | 16 | function Course_Slider({ Courses }) { 17 | return ( 18 | <> 19 | {Courses?.length ? ( 20 | 32 | {Courses?.map((course, i) => ( 33 | 34 | 35 | 36 | ))} 37 | 38 | ) : ( 39 |

No Course Found

40 | )} 41 | 42 | ) 43 | } 44 | 45 | export default Course_Slider 46 | -------------------------------------------------------------------------------- /src/components/core/ContactUsPage/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 54 | -------------------------------------------------------------------------------- /src/components/core/ContactUsPage/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; 22 | -------------------------------------------------------------------------------- /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 | } 61 | -------------------------------------------------------------------------------- /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 21 | -------------------------------------------------------------------------------- /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 | } 108 | -------------------------------------------------------------------------------- /src/components/core/Dashboard/AddCourse/CourseInformation/RequirementsField.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 | } 87 | -------------------------------------------------------------------------------- /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 | } 103 | -------------------------------------------------------------------------------- /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 | export default function RenderSteps() { 9 | const { step } = useSelector((state) => state.course) 10 | 11 | const steps = [ 12 | { 13 | id: 1, 14 | title: "Course Information", 15 | }, 16 | { 17 | id: 2, 18 | title: "Course Builder", 19 | }, 20 | { 21 | id: 3, 22 | title: "Publish", 23 | }, 24 | ] 25 | 26 | return ( 27 | <> 28 |
29 | {steps.map((item) => ( 30 | <> 31 |
35 | 48 | 49 |
50 | {item.id !== steps.length && ( 51 | <> 52 |
item.id ? "border-yellow-50" : "border-richblack-500" 55 | } `} 56 | >
57 | 58 | )} 59 | 60 | ))} 61 |
62 | 63 |
64 | {steps.map((item) => ( 65 | <> 66 |
70 | 71 |

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

78 |
79 | 80 | 81 | ))} 82 |
83 | {/* Render specific component based on current step */} 84 | {step === 1 && } 85 | {step === 2 && } 86 | {step === 3 && } 87 | 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 | } 124 | -------------------------------------------------------------------------------- /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 | } 39 | -------------------------------------------------------------------------------- /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 | } 67 | -------------------------------------------------------------------------------- /src/components/core/Dashboard/Cart/RenderTotalAmount.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux" 2 | import { useNavigate } from "react-router-dom" 3 | 4 | import { BuyCourse } from "../../../../services/operations/studentFeaturesAPI" 5 | import IconBtn from "../../../Common/IconBtn" 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 | } 31 | -------------------------------------------------------------------------------- /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 | const { paymentLoading } = useSelector((state) => state.course) 9 | 10 | if (paymentLoading) 11 | return ( 12 |
13 |
14 |
15 | ) 16 | 17 | return ( 18 | <> 19 |

Cart

20 |

21 | {totalItems} Courses in Cart 22 |

23 | {total > 0 ? ( 24 |
25 | 26 | 27 |
28 | ) : ( 29 |

30 | Your cart is empty 31 |

32 | )} 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/core/Dashboard/EditCourse/index.jsx: -------------------------------------------------------------------------------- 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 | } 57 | -------------------------------------------------------------------------------- /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 | 15 | useEffect(() => { 16 | ;(async () => { 17 | try { 18 | const res = await getUserEnrolledCourses(token) // Getting all the published and the drafted courses 19 | 20 | // Filtering the published course out 21 | const filterPublishCourse = res.filter((ele) => ele.status !== "Draft") 22 | // console.log( 23 | // "Viewing all the couse that is Published", 24 | // filterPublishCourse 25 | // ) 26 | 27 | setEnrolledCourses(filterPublishCourse) 28 | } catch (error) { 29 | console.log("Could not fetch enrolled courses.") 30 | } 31 | })() 32 | // eslint-disable-next-line react-hooks/exhaustive-deps 33 | }, []) 34 | 35 | return ( 36 | <> 37 |
Enrolled Courses
38 | {!enrolledCourses ? ( 39 |
40 |
41 |
42 | ) : !enrolledCourses.length ? ( 43 |

44 | You have not enrolled in any course yet. 45 | {/* TODO: Modify this Empty State */} 46 |

47 | ) : ( 48 |
49 | {/* Headings */} 50 |
51 |

Course Name

52 |

Duration

53 |

Progress

54 |
55 | {/* Course Names */} 56 | {enrolledCourses.map((course, i, arr) => ( 57 |
63 |
{ 66 | navigate( 67 | `/view-course/${course?._id}/section/${course.courseContent?.[0]?._id}/sub-section/${course.courseContent?.[0]?.subSection?.[0]?._id}` 68 | ) 69 | }} 70 | > 71 | course_img 76 |
77 |

{course.courseName}

78 |

79 | {course.courseDescription.length > 50 80 | ? `${course.courseDescription.slice(0, 50)}...` 81 | : course.courseDescription} 82 |

83 |
84 |
85 |
{course?.totalDuration}
86 |
87 |

Progress: {course.progressPercentage || 0}%

88 | 93 |
94 |
95 | ))} 96 |
97 | )} 98 | 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /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 | } 87 | -------------------------------------------------------------------------------- /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 | } 41 | -------------------------------------------------------------------------------- /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 | } 101 | -------------------------------------------------------------------------------- /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 | } 49 | -------------------------------------------------------------------------------- /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 | } 23 | -------------------------------------------------------------------------------- /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 | } 70 | -------------------------------------------------------------------------------- /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 | } 39 | -------------------------------------------------------------------------------- /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; 19 | -------------------------------------------------------------------------------- /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; 82 | -------------------------------------------------------------------------------- /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; 51 | -------------------------------------------------------------------------------- /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; 79 | -------------------------------------------------------------------------------- /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; 13 | -------------------------------------------------------------------------------- /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/Timeline.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; 89 | -------------------------------------------------------------------------------- /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 |