├── Server
├── .gitignore
├── vercel.json
├── config
│ ├── razorpay.js
│ ├── cloudinary.js
│ └── database.js
├── models
│ ├── Section.js
│ ├── Category.js
│ ├── SubSection.js
│ ├── Profile.js
│ ├── CourseProgress.js
│ ├── RatingAndReview.js
│ ├── OTP.js
│ ├── Course.js
│ └── User.js
├── utils
│ ├── imageUploader.js
│ ├── secToDuration.js
│ └── mailSender.js
├── routes
│ ├── Payments.js
│ ├── Profile.js
│ ├── User.js
│ └── Course.js
├── package.json
├── index.js
├── controllers
│ ├── courseProgress.js
│ ├── ResetPassword.js
│ └── SubSection.js
├── mail
│ └── templates
│ │ ├── emailVerificationTemplate.js
│ │ ├── passwordUpdate.js
│ │ ├── paymentSuccessEmail.js
│ │ ├── courseEnrollmentEmail.js
│ │ └── contactFormRes.js
└── middlewares
│ └── auth.js
├── src
├── index.css
├── assets
│ ├── Images
│ │ ├── Photo.jpg
│ │ ├── frame.png
│ │ ├── banner.mp4
│ │ ├── login.webp
│ │ ├── signup.webp
│ │ ├── Instructor.png
│ │ ├── aboutus1.webp
│ │ ├── aboutus2.webp
│ │ ├── aboutus3.webp
│ │ ├── boxoffice.png
│ │ ├── FoundingStory.png
│ │ ├── TimelineImage.png
│ │ ├── Know_your_progress.png
│ │ ├── Plan_your_lessons.png
│ │ └── Compare_with_others.png
│ ├── Logo
│ │ ├── rzp_logo.png
│ │ ├── Logo-Full-Dark.png
│ │ ├── Logo-Full-Light.png
│ │ ├── Logo-Small-Dark.png
│ │ └── Logo-Small-Light.png
│ └── TimeLineLogo
│ │ ├── Logo4.svg
│ │ ├── Logo3.svg
│ │ ├── Logo1.svg
│ │ └── Logo2.svg
├── utils
│ ├── dateFormatter.js
│ ├── constants.js
│ ├── avgRating.js
│ └── secToDurationFrontend.js
├── pages
│ ├── Error.jsx
│ ├── Login.jsx
│ ├── Signup.jsx
│ ├── Dashboard.js
│ ├── ViewCourse.js
│ ├── ForgotPassword.js
│ ├── ContactPage.js
│ └── VerifyEmail.js
├── data
│ ├── navbar-links.js
│ ├── dashboard-links.js
│ └── footer-links.js
├── components
│ ├── core
│ │ ├── HomePage
│ │ │ ├── HiglightText2.js
│ │ │ ├── HighlightText3.js
│ │ │ ├── HighlightText.js
│ │ │ ├── Border.css
│ │ │ ├── Button.js
│ │ │ ├── CourseCard.js
│ │ │ ├── InstructorSection.jsx
│ │ │ ├── LearningLanguageSection.jsx
│ │ │ ├── CodeBlocks.js
│ │ │ ├── ExploreMore.js
│ │ │ └── TimelineSection.jsx
│ │ ├── Auth
│ │ │ ├── PrivateRoute.jsx
│ │ │ ├── OpenRoute.jsx
│ │ │ ├── Template.jsx
│ │ │ ├── ProfileDropDown.js
│ │ │ └── LoginForm.jsx
│ │ ├── AboutPage
│ │ │ ├── ContactFormSection.jsx
│ │ │ ├── Quote.jsx
│ │ │ ├── Stats.jsx
│ │ │ └── LearningGrid.jsx
│ │ ├── Dashboard
│ │ │ ├── Settings
│ │ │ │ ├── index.js
│ │ │ │ ├── DeleteAccount.js
│ │ │ │ ├── ChangeProfilePicture.js
│ │ │ │ └── UpdatePassword.js
│ │ │ ├── Cart
│ │ │ │ ├── index.jsx
│ │ │ │ ├── RenderTotalAmount.jsx
│ │ │ │ └── RenderCartCourses.jsx
│ │ │ ├── MyCourses.jsx
│ │ │ ├── SidebarLink.js
│ │ │ ├── Add Course
│ │ │ │ ├── index.js
│ │ │ │ ├── CourseInformation
│ │ │ │ │ ├── RequirementField.js
│ │ │ │ │ └── ChipInput.js
│ │ │ │ ├── RenderSteps.js
│ │ │ │ ├── PublishCourse
│ │ │ │ │ └── index.js
│ │ │ │ └── Upload.js
│ │ │ ├── EditCourse
│ │ │ │ └── index.js
│ │ │ ├── Sidebar.js
│ │ │ ├── InstructorDashboard
│ │ │ │ └── InstructorChart.jsx
│ │ │ └── EnrolledCourses.js
│ │ ├── Catalog
│ │ │ ├── CourseSlider.jsx
│ │ │ └── Course_Card.jsx
│ │ ├── ViewCourse
│ │ │ └── CourseReviewModal.js
│ │ └── Course
│ │ │ └── CourseDetailsCard.js
│ ├── ContactPage
│ │ └── ContactForm.css
│ └── common
│ │ ├── loader.css
│ │ ├── IconBtn.jsx
│ │ ├── Tab.jsx
│ │ ├── ConfirmationModal.js
│ │ ├── RatingStars.jsx
│ │ └── ReviewSlider.js
├── services
│ ├── apiconnector.js
│ ├── formatDate.js
│ ├── operations
│ │ ├── pageAndComponentData.js
│ │ ├── profileAPI.js
│ │ └── SettingsAPI.js
│ └── apis.js
├── reducer
│ └── index.js
├── slices
│ ├── profileSlice.js
│ ├── authSlice.js
│ ├── courseSlice.js
│ ├── viewCourseSlice.js
│ └── cartSlice.js
├── index.js
├── hooks
│ └── useOnClickOutside.js
└── App.css
├── public
├── robots.txt
├── favicon.ico
├── manifest.json
└── index.html
├── .gitignore
├── package.json
└── tailwind.config.js
/Server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/Images/Photo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/Photo.jpg
--------------------------------------------------------------------------------
/src/assets/Images/frame.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/frame.png
--------------------------------------------------------------------------------
/src/assets/Images/banner.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/banner.mp4
--------------------------------------------------------------------------------
/src/assets/Images/login.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/login.webp
--------------------------------------------------------------------------------
/src/assets/Images/signup.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/signup.webp
--------------------------------------------------------------------------------
/src/assets/Logo/rzp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Logo/rzp_logo.png
--------------------------------------------------------------------------------
/src/assets/Images/Instructor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/Instructor.png
--------------------------------------------------------------------------------
/src/assets/Images/aboutus1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/aboutus1.webp
--------------------------------------------------------------------------------
/src/assets/Images/aboutus2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/aboutus2.webp
--------------------------------------------------------------------------------
/src/assets/Images/aboutus3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/aboutus3.webp
--------------------------------------------------------------------------------
/src/assets/Images/boxoffice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/boxoffice.png
--------------------------------------------------------------------------------
/src/assets/Images/FoundingStory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/FoundingStory.png
--------------------------------------------------------------------------------
/src/assets/Images/TimelineImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/TimelineImage.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Full-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Logo/Logo-Full-Dark.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Full-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Logo/Logo-Full-Light.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Small-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Logo/Logo-Small-Dark.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Small-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Logo/Logo-Small-Light.png
--------------------------------------------------------------------------------
/src/assets/Images/Know_your_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/Know_your_progress.png
--------------------------------------------------------------------------------
/src/assets/Images/Plan_your_lessons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/Plan_your_lessons.png
--------------------------------------------------------------------------------
/src/assets/Images/Compare_with_others.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yashsarode45/StudyNotion-Mega-Project/HEAD/src/assets/Images/Compare_with_others.png
--------------------------------------------------------------------------------
/Server/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [{"src": "./index.js", "use":
4 | "@vercel/node" }],
5 | "routes": [{ "src": "/(.*)", "dest": "/" }]
6 |
7 | }
--------------------------------------------------------------------------------
/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 | })
--------------------------------------------------------------------------------
/src/utils/dateFormatter.js:
--------------------------------------------------------------------------------
1 | export const formattedDate = (date) => {
2 | return new Date(date).toLocaleDateString("en-US", {
3 | month: "long",
4 | day: "numeric",
5 | year: "numeric",
6 | })
7 | }
--------------------------------------------------------------------------------
/src/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const ACCOUNT_TYPE = {
2 | STUDENT: "Student",
3 | INSTRUCTOR: "Instructor",
4 | ADMIN: "Admin",
5 | }
6 |
7 | export const COURSE_STATUS = {
8 | DRAFT: "Draft",
9 | PUBLISHED: "Published",
10 | }
--------------------------------------------------------------------------------
/src/pages/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Error = () => {
4 | return (
5 |
6 | Error - 404 Not found
7 |
8 | )
9 | }
10 |
11 | export default Error
12 |
--------------------------------------------------------------------------------
/src/data/navbar-links.js:
--------------------------------------------------------------------------------
1 | export const NavbarLinks = [
2 | {
3 | title: "Home",
4 | path: "/",
5 | },
6 | {
7 | title: "Catalog",
8 | // path: '/catalog',
9 | },
10 | {
11 | title: "About Us",
12 | path: "/about",
13 | },
14 | {
15 | title: "Contact Us",
16 | path: "/contact",
17 | },
18 | ];
19 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/HiglightText2.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const HiglightText2 = ({text}) => {
4 | return (
5 |
7 | {" "}
8 | {text}
9 |
10 |
11 | )
12 | }
13 |
14 | export default HiglightText2
--------------------------------------------------------------------------------
/src/components/core/HomePage/HighlightText3.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const HighlightText3 = ({text}) => {
4 | return (
5 |
7 | {" "}
8 | {text}
9 |
10 |
11 | )
12 | }
13 |
14 | export default HighlightText3
15 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/HighlightText.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const HighlightText = ({text}) => {
4 | return (
5 |
7 | {" "}
8 | {text}
9 |
10 |
11 | )
12 | }
13 |
14 | export default HighlightText
15 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/Border.css:
--------------------------------------------------------------------------------
1 | .code-border {
2 | background: linear-gradient(111.93deg,rgba(14,26,45,.24) -1.4%,rgba(17,30,50,.38) 104.96%);
3 | border: 2px solid;
4 | border-image-slice: 1;
5 | border-image-source: linear-gradient(to right bottom,#ffffff38,#ffffff00);
6 | }
7 |
8 | .homepage_bg {
9 | background: url("../../../assets/Images/bghome.svg");
10 | }
11 |
--------------------------------------------------------------------------------
/Server/models/Section.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const sectionSchema = new mongoose.Schema({
4 |
5 | sectionName: {
6 | type:String,
7 | },
8 | subSection: [
9 | {
10 | type:mongoose.Schema.Types.ObjectId,
11 | ref:"SubSection",
12 | }
13 | ],
14 |
15 |
16 | });
17 |
18 | module.exports = mongoose.model("Section", sectionSchema);
--------------------------------------------------------------------------------
/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) options.height = height;
6 | if (quality) {
7 | options.quality=quality;
8 | }
9 |
10 | options.resource_type ="auto";
11 |
12 | return await cloudinary.uploader.upload(file.tempFilePath, options)
13 | }
--------------------------------------------------------------------------------
/src/services/apiconnector.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | export const axiosInstance = axios.create({});
4 |
5 | export const apiConnector = (method, url, bodyData, headers, params) => {
6 | return axiosInstance({
7 | method:`${method}`,
8 | url:`${url}`,
9 | data: bodyData ? bodyData : null,
10 | headers: headers ? headers: null,
11 | params: params ? params : null,
12 | });
13 | }
--------------------------------------------------------------------------------
/Server/models/Category.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const categorySchema = new mongoose.Schema({
4 | name:{
5 | type:String,
6 | required: true,
7 | },
8 | description: {
9 | type:String,
10 | },
11 | course: [{
12 | type:mongoose.Schema.Types.ObjectId,
13 | ref:"Course",
14 | }],
15 | });
16 |
17 | module.exports = mongoose.model("Category", categorySchema);
--------------------------------------------------------------------------------
/Server/models/SubSection.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const subSectionSchema = new mongoose.Schema({
4 | title:{
5 | type:String,
6 | },
7 | timeDuration: {
8 | type: String,
9 | },
10 | description: {
11 | type:String,
12 | },
13 | videoUrl:{
14 | type:String,
15 | },
16 |
17 |
18 | })
19 |
20 | module.exports = mongoose.model("SubSection", subSectionSchema);
--------------------------------------------------------------------------------
/src/components/core/Auth/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { Navigate } from 'react-router-dom';
4 |
5 | const PrivateRoute = ({children}) => {
6 |
7 | const {token} = useSelector((state) => state.auth);
8 |
9 | if(token !== null)
10 | return children
11 | else
12 | return
13 |
14 | }
15 |
16 | export default PrivateRoute
17 |
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/src/utils/avgRating.js:
--------------------------------------------------------------------------------
1 | export default function GetAvgRating(ratingArr) {
2 | if (ratingArr?.length === 0) return 0
3 | const totalReviewCount = ratingArr?.reduce((acc, curr) => {
4 | acc += curr.rating
5 | return acc
6 | }, 0)
7 |
8 | const multiplier = Math.pow(10, 1)
9 | const avgReviewCount =
10 | Math.round((totalReviewCount / ratingArr?.length) * multiplier) / multiplier
11 |
12 | return avgReviewCount
13 | }
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/src/pages/Login.jsx:
--------------------------------------------------------------------------------
1 | import loginImg from "../assets/Images/login.webp"
2 | import Template from "../components/core/Auth/Template"
3 |
4 | function Login() {
5 | return (
6 |
13 | )
14 | }
15 |
16 | export default Login
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/Server/models/Profile.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const profileSchema = new mongoose.Schema({
4 | gender:{
5 | type:String,
6 | },
7 | dateOfBirth:{
8 | type:String,
9 |
10 | },
11 | about:{
12 | type:String,
13 | trim:true,
14 | },
15 | contactNumber:{
16 | type:String,
17 |
18 | },
19 |
20 |
21 | })
22 |
23 | module.exports = mongoose.model("Profile", profileSchema);
--------------------------------------------------------------------------------
/Server/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 | require('dotenv').config()
3 |
4 | exports.connectDB = () => {
5 | mongoose.connect(process.env.MONGODB_URL, {
6 | useUnifiedTopology:true,
7 | useNewUrlParser: true
8 | })
9 | .then(()=>{
10 | console.log("DB connection successfull!")
11 | })
12 | .catch( (error) => {
13 | console.log("DB Connection Failed");
14 | console.error(error);
15 | process.exit(1);
16 | } )
17 | }
--------------------------------------------------------------------------------
/src/pages/Signup.jsx:
--------------------------------------------------------------------------------
1 | import signupImg from "../assets/Images/signup.webp"
2 | import Template from "../components/core/Auth/Template"
3 |
4 | function Signup() {
5 | return (
6 |
13 | )
14 | }
15 |
16 | export default Signup
--------------------------------------------------------------------------------
/src/utils/secToDurationFrontend.js:
--------------------------------------------------------------------------------
1 | function convertSecondsToDuration(totalSeconds) {
2 | const hours = Math.floor(totalSeconds / 3600)
3 | const minutes = Math.floor((totalSeconds % 3600) / 60)
4 | const seconds = Math.floor((totalSeconds % 3600) % 60)
5 |
6 | if (hours > 0) {
7 | return `${hours}h ${minutes}m`
8 | } else if (minutes > 0) {
9 | return `${minutes}m ${seconds}s`
10 | } else {
11 | return `${seconds}s`
12 | }
13 | }
14 |
15 | export default convertSecondsToDuration
--------------------------------------------------------------------------------
/Server/models/CourseProgress.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const courseProgressSchema = 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 | type:mongoose.Schema.Types.ObjectId,
14 | ref: "SubSection",
15 | }],
16 |
17 |
18 | })
19 |
20 | module.exports = mongoose.model("CourseProgress", courseProgressSchema);
--------------------------------------------------------------------------------
/Server/routes/Payments.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | const { capturePayment, verifyPayment, sendPaymentSuccessEmail } = require("../controllers/Payments")
6 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth")
7 | router.post("/capturePayment", auth, isStudent, capturePayment)
8 | router.post("/verifyPayment",auth, isStudent, verifyPayment)
9 | router.post("/sendPaymentSuccessEmail", auth, isStudent, sendPaymentSuccessEmail);
10 |
11 | module.exports = router
--------------------------------------------------------------------------------
/src/reducer/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "@reduxjs/toolkit";
2 |
3 | import authReducer from "../slices/authSlice"
4 | import profileReducer from "../slices/profileSlice";
5 | import cartReducer from "../slices/cartSlice"
6 | import courseReducer from "../slices/courseSlice"
7 | import viewCourseReducer from "../slices/viewCourseSlice"
8 |
9 | const rootReducer = combineReducers({
10 | auth:authReducer,
11 | cart:cartReducer,
12 | profile:profileReducer,
13 | course:courseReducer,
14 | viewCourse:viewCourseReducer,
15 | })
16 |
17 | export default rootReducer
--------------------------------------------------------------------------------
/src/services/formatDate.js:
--------------------------------------------------------------------------------
1 | // Returns formatted date and time
2 | export const formatDate = (dateString) => {
3 | const options = { year: "numeric", month: "long", day: "numeric" }
4 | const date = new Date(dateString)
5 | const formattedDate = date.toLocaleDateString("en-US", options)
6 |
7 | const hour = date.getHours()
8 | const minutes = date.getMinutes()
9 | const period = hour >= 12 ? "PM" : "AM"
10 | const formattedTime = `${hour % 12}:${minutes
11 | .toString()
12 | .padStart(2, "0")} ${period}`
13 |
14 | return `${formattedDate} | ${formattedTime}`
15 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Link} from "react-router-dom"
3 |
4 | const Button = ({children, active, linkto}) => {
5 | return (
6 |
7 |
8 |
12 | {children}
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | export default Button
20 |
--------------------------------------------------------------------------------
/Server/models/RatingAndReview.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const ratingAndReviewSchema = new mongoose.Schema({
4 | user:{
5 | type:mongoose.Schema.Types.ObjectId,
6 | required:true,
7 | ref: "User",
8 | },
9 | rating: {
10 | type:Number,
11 | required:true,
12 | },
13 | review:{
14 | type:String,
15 | required:true,
16 | },
17 | course: {
18 | type: mongoose.Schema.Types.ObjectId,
19 | required: true,
20 | ref: "Course",
21 | index: true,
22 | },
23 | });
24 |
25 | module.exports = mongoose.model("RatingAndReview", ratingAndReviewSchema)
--------------------------------------------------------------------------------
/src/components/core/AboutPage/ContactFormSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ContactUsForm from '../../ContactPage/ContactUsForm'
3 |
4 | const ContactFormSection = () => {
5 | return (
6 |
7 |
8 | Get in Touch
9 |
10 |
11 | We'd love to here for you, Please fill out this form.
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default ContactFormSection
21 |
--------------------------------------------------------------------------------
/src/components/ContactPage/ContactForm.css:
--------------------------------------------------------------------------------
1 | .form-style{
2 | --tw-bg-opacity: 1;
3 | --tw-text-opacity: 1;
4 | --tw-shadow: 0 1px 0 0;
5 | --tw-shadow-colored: 0 1px 0 0 var(--tw-shadow-color);
6 | --tw-shadow-color: hsla(0,0%,100%,.5);
7 | --tw-shadow: var(--tw-shadow-colored);
8 | background-color: rgb(44 51 63/var(--tw-bg-opacity));
9 | border-radius: 0.5rem;
10 | box-shadow: 0 0 #0000,0 0 #0000,var(--tw-shadow);
11 | box-shadow: var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);
12 | color: rgb(241 242 255/var(--tw-text-opacity));
13 | font-size: 16px;
14 | line-height: 24px;
15 | padding: 0.75rem;
16 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/index.js:
--------------------------------------------------------------------------------
1 | import ChangeProfilePicture from "./ChangeProfilePicture"
2 | import DeleteAccount from "./DeleteAccount"
3 | import EditProfile from "./EditProfile"
4 | import UpdatePassword from "./UpdatePassword"
5 |
6 | export default function Settings() {
7 | return (
8 | <>
9 |
10 | Edit Profile
11 |
12 | {/* Change Profile Picture */}
13 |
14 | {/* Profile */}
15 |
16 | {/* Password */}
17 |
18 | {/* Delete Account */}
19 |
20 | >
21 | )
22 | }
--------------------------------------------------------------------------------
/src/slices/profileSlice.js:
--------------------------------------------------------------------------------
1 | import {createSlice} from "@reduxjs/toolkit"
2 |
3 | const initialState = {
4 | user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null,
5 | loading: false,
6 | };
7 |
8 | const profileSlice = createSlice({
9 | name:"profile",
10 | initialState: initialState,
11 | reducers: {
12 | setUser(state, value) {
13 | // console.log("User in slice", value.payload)
14 | state.user = value.payload;
15 | },
16 | setLoading(state, value) {
17 | state.loading = value.payload;
18 | },
19 | },
20 | });
21 |
22 | export const {setUser, setLoading} = profileSlice.actions;
23 | export default profileSlice.reducer;
--------------------------------------------------------------------------------
/Server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "dev": "nodemon index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcrypt": "^5.1.0",
15 | "cloudinary": "^1.40.0",
16 | "cookie-parser": "^1.4.6",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.3.1",
19 | "express": "^4.18.2",
20 | "express-fileupload": "^1.4.0",
21 | "jsonwebtoken": "^9.0.1",
22 | "mongoose": "^7.4.1",
23 | "nodemailer": "^6.9.4",
24 | "nodemon": "^3.0.1",
25 | "otp-generator": "^4.0.1",
26 | "razorpay": "^2.9.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/common/loader.css:
--------------------------------------------------------------------------------
1 | .loader {
2 | width: 16px;
3 | height: 16px;
4 | border-radius: 50%;
5 | display: block;
6 | margin:15px auto;
7 | position: relative;
8 | background: #b4b3b3;
9 | box-shadow: -24px 0 #b4b3b3, 24px 0 #b4b3b3;
10 | box-sizing: border-box;
11 | animation: shadowPulse 2s linear infinite;
12 | }
13 |
14 | @keyframes shadowPulse {
15 | 33% {
16 | background: #b4b3b3;
17 | box-shadow: -24px 0 #0e92ff, 24px 0 #b4b3b3;
18 | }
19 | 66% {
20 | background: #0e92ff;
21 | box-shadow: -24px 0 #b4b3b3, 24px 0 #b4b3b3;
22 | }
23 | 100% {
24 | background: #b4b3b3;
25 | box-shadow: -24px 0 #b4b3b3, 24px 0 #0e92ff;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 | import { BrowserRouter } from "react-router-dom";
6 | import { Toaster } from "react-hot-toast";
7 | import { Provider } from "react-redux";
8 | import rootReducer from "./reducer";
9 | import {configureStore} from "@reduxjs/toolkit"
10 |
11 |
12 | const store = configureStore({
13 | reducer:rootReducer,
14 | });
15 |
16 | const root = ReactDOM.createRoot(document.getElementById("root"));
17 | root.render(
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/src/components/core/AboutPage/Quote.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from '../HomePage/HighlightText'
3 | import HiglightText2 from '../HomePage/HiglightText2'
4 | import HighlightText3 from '../HomePage/HighlightText3'
5 | const Quote = () => {
6 | return (
7 |
8 | We are passionate about revolutionizing the way we learn. Our innovative platform
9 |
10 |
11 | {","}
12 |
13 | , and community to create an
14 |
15 |
16 | )
17 | }
18 |
19 | export default Quote
20 |
--------------------------------------------------------------------------------
/src/slices/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | signupData: null,
5 | token: localStorage.getItem("token") ? JSON.parse(localStorage.getItem("token")) : null,
6 | loading:false
7 | }
8 |
9 | const authSlice = createSlice({
10 | name:"auth",
11 | initialState:initialState,
12 | reducers:{
13 | setSignupData: (state,value) =>{
14 | state.signupData = value.payload;
15 | },
16 | setLoading(state, value) {
17 | state.loading = value.payload;
18 | },
19 | setToken(state, value) {
20 | state.token = value.payload;
21 | },
22 | }
23 | })
24 |
25 | export const { setSignupData, setLoading, setToken } = authSlice.actions;
26 |
27 | export default authSlice.reducer;
--------------------------------------------------------------------------------
/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 |
19 | {children ? (
20 | <>
21 | {text}
22 | {children}
23 | >
24 | ) : (
25 | text
26 | )}
27 |
28 | )
29 | }
--------------------------------------------------------------------------------
/src/components/common/Tab.jsx:
--------------------------------------------------------------------------------
1 | export default function Tab({ tabData, field, setField }) {
2 | return (
3 |
9 | {tabData.map((tab) => (
10 | setField(tab.type)}
13 | className={`${
14 | field === tab.type
15 | ? "bg-richblack-900 text-richblack-5"
16 | : "bg-transparent text-richblack-200"
17 | } py-2 px-5 rounded-full transition-all duration-200`}
18 | >
19 | {tab?.tabName}
20 |
21 | ))}
22 |
23 | );
24 | }
--------------------------------------------------------------------------------
/Server/utils/mailSender.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 | require("dotenv").config();
3 |
4 | const mailSender = async (email, title, body) =>{
5 | try {
6 | let transporter = nodemailer.createTransport({
7 | host: process.env.MAIL_HOST,
8 | auth: {
9 | user: process.env.MAIL_USER,
10 | pass: process.env.MAIL_PASS,
11 | }
12 | })
13 |
14 | let info = await transporter.sendMail({
15 | from: "Yash Sarode - StudyNotion" , // sender address
16 | to: `${email}`,
17 | subject: `${title}`,
18 | html: `${body}`, // plain text body
19 | });
20 |
21 | return info;
22 |
23 | } catch (error) {
24 | console.log("Error in mailSender", error.message);
25 | }
26 | }
27 |
28 | module.exports = mailSender;
--------------------------------------------------------------------------------
/src/services/operations/pageAndComponentData.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {toast} from "react-hot-toast"
3 | import { apiConnector } from '../apiconnector';
4 | import { catalogData } from '../apis';
5 |
6 | export const getCatalogaPageData = async(categoryId) => {
7 | const toastId = toast.loading("Loading...");
8 | let result = [];
9 | try{
10 | const response = await apiConnector("POST", catalogData.CATALOGPAGEDATA_API,
11 | {categoryId: categoryId,});
12 | // console.log("Catalog page data response", response)
13 | if(!response?.data?.success)
14 | throw new Error("Could not Fetch Category page data");
15 |
16 | result = response?.data;
17 |
18 | }
19 | catch(error) {
20 | console.log("CATALOG PAGE DATA API ERROR....", error);
21 | toast.error(error.message);
22 | result = error.response?.data;
23 | }
24 | toast.dismiss(toastId);
25 | return result;
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/pages/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from "react-redux"
3 | import { Outlet } from "react-router-dom"
4 | import Sidebar from '../components/core/Dashboard/Sidebar'
5 | const Dashboard = () => {
6 | const {loading:authLoading} = useSelector((state)=>state.auth)
7 | const {loading:profileLoading} = useSelector((state)=>state.profile)
8 |
9 | if (profileLoading || authLoading) {
10 | return (
11 |
14 | )
15 | }
16 |
17 | return (
18 |
26 | )
27 | }
28 |
29 | export default Dashboard
--------------------------------------------------------------------------------
/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 |
7 |
8 | export default function Cart() {
9 |
10 | const {total, totalItems} = useSelector((state)=>state.cart);
11 |
12 |
13 | return (
14 | <>
15 | Cart
16 |
17 | {totalItems} Courses in Cart
18 |
19 | {total > 0 ? (
20 |
21 |
22 |
23 |
24 | ) : (
25 |
26 | Your cart is empty
27 |
28 | )}
29 | >
30 | )
31 | }
--------------------------------------------------------------------------------
/src/slices/courseSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit"
2 |
3 | const initialState = {
4 | step: 1,
5 | course: null,
6 | editCourse: false,
7 | paymentLoading: false,
8 | }
9 |
10 | const courseSlice = createSlice({
11 | name: "course",
12 | initialState,
13 | reducers: {
14 | setStep: (state, action) => {
15 | state.step = action.payload
16 | },
17 | setCourse: (state, action) => {
18 | state.course = action.payload
19 | },
20 | setEditCourse: (state, action) => {
21 | state.editCourse = action.payload
22 | },
23 | setPaymentLoading: (state, action) => {
24 | state.paymentLoading = action.payload
25 | },
26 | resetCourseState: (state) => {
27 | state.step = 1
28 | state.course = null
29 | state.editCourse = false
30 | },
31 | },
32 | })
33 |
34 | export const {
35 | setStep,
36 | setCourse,
37 | setEditCourse,
38 | setPaymentLoading,
39 | resetCourseState,
40 | } = courseSlice.actions
41 |
42 | export default courseSlice.reducer
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/data/dashboard-links.js:
--------------------------------------------------------------------------------
1 | import { ACCOUNT_TYPE } from "../utils/constants";
2 | export const sidebarLinks = [
3 | {
4 | id: 1,
5 | name: "My Profile",
6 | path: "/dashboard/my-profile",
7 | icon: "VscAccount",
8 | },
9 | {
10 | id: 2,
11 | name: "Dashboard",
12 | path: "/dashboard/instructor",
13 | type: ACCOUNT_TYPE.INSTRUCTOR,
14 | icon: "VscDashboard",
15 | },
16 | {
17 | id: 3,
18 | name: "My Courses",
19 | path: "/dashboard/my-courses",
20 | type: ACCOUNT_TYPE.INSTRUCTOR,
21 | icon: "VscVm",
22 | },
23 | {
24 | id: 4,
25 | name: "Add Course",
26 | path: "/dashboard/add-course",
27 | type: ACCOUNT_TYPE.INSTRUCTOR,
28 | icon: "VscAdd",
29 | },
30 | {
31 | id: 5,
32 | name: "Enrolled Courses",
33 | path: "/dashboard/enrolled-courses",
34 | type: ACCOUNT_TYPE.STUDENT,
35 | icon: "VscMortarBoard",
36 | },
37 | {
38 | id: 6,
39 | name: "Purchase History",
40 | path: "/dashboard/purchase-history",
41 | type: ACCOUNT_TYPE.STUDENT,
42 | icon: "VscHistory",
43 | },
44 | ];
45 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/common/ConfirmationModal.js:
--------------------------------------------------------------------------------
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 |
22 | {modalData?.btn2Text}
23 |
24 |
25 |
26 |
27 | )
28 | }
--------------------------------------------------------------------------------
/Server/routes/Profile.js:
--------------------------------------------------------------------------------
1 | const express = require("express")
2 | const router = express.Router()
3 |
4 | const {
5 | deleteAccount,
6 | updateProfile,
7 | getAllUserDetails,
8 | updateDisplayPicture,
9 | getEnrolledCourses,
10 | instructorDashboard,
11 | } = require("../controllers/Profile")
12 |
13 |
14 | // Importing Middlewares
15 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth")
16 | // ********************************************************************************************************
17 | // Profile routes
18 | // ********************************************************************************************************
19 | // Delet User Account
20 | router.delete("/deleteProfile",auth, deleteAccount)
21 | router.put("/updateProfile", auth, updateProfile)
22 | router.get("/getUserDetails", auth, getAllUserDetails)
23 | // Get Enrolled Courses
24 | router.get("/getEnrolledCourses", auth, getEnrolledCourses)
25 | router.put("/updateDisplayPicture", auth, updateDisplayPicture)
26 | router.get("/instructorDashboard", auth, isInstructor, instructorDashboard)
27 |
28 | module.exports = router
--------------------------------------------------------------------------------
/Server/models/OTP.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const mailSender = require("../utils/mailSender");
3 | const emailTemplate = require("../mail/templates/emailVerificationTemplate");
4 |
5 | const OTPSchema = new mongoose.Schema({
6 | email:{
7 | type:String,
8 | required: true,
9 | },
10 | otp: {
11 | type:String,
12 | required:true,
13 | },
14 | createdAt: {
15 | type:Date,
16 | default:Date.now(),
17 | expires: 5*60,
18 | }
19 | });
20 |
21 | async function sendVerificationOTP(email, otp) {
22 | try {
23 | const mailResponse = await mailSender(email,
24 | "Verification Email",
25 | emailTemplate(otp))
26 | console.log("Email sent Successfully: ", mailResponse.response);
27 | } catch (error) {
28 | console.log("error occured while sending mails: ", error);
29 | throw error;
30 | }
31 | }
32 |
33 | OTPSchema.pre("save", async function (next) {
34 | console.log("Mail in pre hook", this.email)
35 | await sendVerificationOTP(this.email, this.otp);
36 | next();
37 | })
38 |
39 | module.exports = mongoose.model("OTP", OTPSchema);
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/hooks/useOnClickOutside.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 |
3 | // This hook detects clicks outside of the specified component and calls the provided handler function.
4 | export default function useOnClickOutside(ref, handler) {
5 | useEffect(() => {
6 | // Define the listener function to be called on click/touch events
7 | const listener = (event) => {
8 | // If the click/touch event originated inside the ref element, do nothing
9 | if (!ref.current || ref.current.contains(event.target)) {
10 | return;
11 | }
12 | // Otherwise, call the provided handler function
13 | handler(event);
14 | };
15 |
16 | // Add event listeners for mousedown and touchstart events on the document
17 | document.addEventListener("mousedown", listener);
18 | document.addEventListener("touchstart", listener);
19 |
20 | // Cleanup function to remove the event listeners when the component unmounts or when the ref/handler dependencies change
21 | return () => {
22 | document.removeEventListener("mousedown", listener);
23 | document.removeEventListener("touchstart", listener);
24 | };
25 | }, [ref, handler]); // Only run this effect when the ref or handler function changes
26 | }
--------------------------------------------------------------------------------
/src/components/common/RatingStars.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import {
3 | TiStarFullOutline,
4 | TiStarHalfOutline,
5 | TiStarOutline,
6 | } from "react-icons/ti"
7 |
8 | function RatingStars({ Review_Count, Star_Size }) {
9 | const [starCount, SetStarCount] = useState({
10 | full: 0,
11 | half: 0,
12 | empty: 0,
13 | })
14 |
15 | useEffect(() => {
16 | const wholeStars = Math.floor(Review_Count) || 0
17 | SetStarCount({
18 | full: wholeStars,
19 | half: Number.isInteger(Review_Count) ? 0 : 1,
20 | empty: Number.isInteger(Review_Count) ? 5 - wholeStars : 4 - wholeStars,
21 | })
22 | }, [Review_Count])
23 | return (
24 |
25 | {[...new Array(starCount.full)].map((_, i) => {
26 | return
27 | })}
28 | {[...new Array(starCount.half)].map((_, i) => {
29 | return
30 | })}
31 | {[...new Array(starCount.empty)].map((_, i) => {
32 | return
33 | })}
34 |
35 | )
36 | }
37 |
38 | export default RatingStars
--------------------------------------------------------------------------------
/src/slices/viewCourseSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit"
2 |
3 | const initialState = {
4 | courseSectionData: [],
5 | courseEntireData: [],
6 | completedLectures: [],
7 | totalNoOfLectures: 0,
8 | }
9 |
10 | const viewCourseSlice = createSlice({
11 | name: "viewCourse",
12 | initialState,
13 | reducers: {
14 | setCourseSectionData: (state, action) => {
15 | // console.log("Course Section Data in slice is", action.payload)
16 | state.courseSectionData = action.payload
17 | },
18 | setEntireCourseData: (state, action) => {
19 | state.courseEntireData = action.payload
20 | },
21 | setTotalNoOfLectures: (state, action) => {
22 | state.totalNoOfLectures = action.payload
23 | },
24 | setCompletedLectures: (state, action) => {
25 | state.completedLectures = action.payload
26 | },
27 | updateCompletedLectures: (state, action) => {
28 | state.completedLectures = [...state.completedLectures, action.payload]
29 | },
30 | },
31 | })
32 |
33 | export const {
34 | setCourseSectionData,
35 | setEntireCourseData,
36 | setTotalNoOfLectures,
37 | setCompletedLectures,
38 | updateCompletedLectures,
39 | } = viewCourseSlice.actions
40 |
41 | export default viewCourseSlice.reducer
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Cart/RenderTotalAmount.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector, useDispatch } from 'react-redux'
3 | import { useNavigate } from "react-router-dom"
4 | import IconBtn from '../../../common/IconBtn';
5 | import { buyCourse } from "../../../../services/operations/studentFeaturesAPI"
6 |
7 | const RenderTotalAmount = () => {
8 |
9 | const {total, cart} = useSelector((state) => state.cart);
10 |
11 | const { token } = useSelector((state) => state.auth)
12 | const { user } = useSelector((state) => state.profile)
13 | const navigate = useNavigate()
14 | const dispatch = useDispatch()
15 |
16 | const handleBuyCourse = () => {
17 | const courses = cart.map((course) => course._id);
18 | buyCourse(token, courses, user, navigate, dispatch)
19 | }
20 | return (
21 |
22 |
Total:
23 |
₹ {total}
24 |
29 |
30 | )
31 | }
32 |
33 | export default RenderTotalAmount
34 |
--------------------------------------------------------------------------------
/src/components/core/Dashboard/MyCourses.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useEffect, useState } from "react"
3 | import { VscAdd } from "react-icons/vsc"
4 | import { useSelector } from "react-redux"
5 | import { useNavigate } from "react-router-dom"
6 |
7 | import { fetchInstructorCourses } from "../../../services/operations/courseDetailsAPI"
8 | import IconBtn from "../../common/IconBtn"
9 | import CoursesTable from "./InstructorCourses/CoursesTable"
10 |
11 | const MyCourses = () => {
12 | const {token} = useSelector((state)=> state.auth);
13 | const navigate = useNavigate();
14 | const [courses, setCourses] = useState([]);
15 |
16 | useEffect(() => {
17 | const fetchCourses = async () => {
18 | const result = await fetchInstructorCourses(token)
19 | if(result) setCourses(result)
20 | }
21 |
22 | fetchCourses()
23 | }, [])
24 |
25 |
26 | return (
27 |
28 |
29 |
My Courses
30 | navigate("/dashboard/add-course")}>
32 |
33 |
34 |
35 | {courses &&
}
36 |
37 | )
38 | }
39 |
40 | export default MyCourses
--------------------------------------------------------------------------------
/Server/models/Course.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const courseSchema = new mongoose.Schema({
4 | courseName:{
5 | type:String,
6 | },
7 | description:{
8 | type:String,
9 | },
10 | instructor:{
11 | type:mongoose.Schema.Types.ObjectId,
12 | ref: "User",
13 | required: true,
14 | },
15 | whatWillYouLearn:{
16 | type:String,
17 | trim:true
18 | },
19 | courseContent:[{
20 | type:mongoose.Schema.Types.ObjectId,
21 | ref: "Section",
22 | }],
23 | ratingAndReviews:[{
24 | type:mongoose.Schema.Types.ObjectId,
25 | ref:"RatingAndReview",
26 | }],
27 | price:{
28 | type:String,
29 | },
30 | thumbnail:{
31 | type:String,
32 | },
33 | tags:{
34 | type:String
35 | },
36 | category:{
37 | type:mongoose.Schema.Types.ObjectId,
38 | ref:"Category"
39 | },
40 | studentsEnrolled: [{
41 | type:mongoose.Schema.Types.ObjectId,
42 | required:true,
43 | ref:"User",
44 | }],
45 | instructions: {
46 | type: String,
47 | },
48 | status: {
49 | type: String,
50 | enum: ["Draft", "Published"],
51 | },
52 | createdAt: {
53 | type:Date,
54 | default:Date.now
55 | },
56 | })
57 |
58 | module.exports = mongoose.model("Course", courseSchema);
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/core/Dashboard/SidebarLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import * as Icons from "react-icons/vsc"
3 | import { useDispatch } from "react-redux"
4 | import { NavLink, matchPath, useLocation } from "react-router-dom"
5 |
6 | import { resetCourseState } from "../../../slices/courseSlice"
7 |
8 | const SidebarLink = ({link, iconName}) => {
9 | const Icon = Icons[iconName];
10 | const location = useLocation();
11 | const dispatch = useDispatch();
12 |
13 | const matchRoute = (route) => {
14 | return matchPath({ path: route }, location.pathname)
15 | }
16 | return (
17 | dispatch(resetCourseState())}
21 | className={`relative px-8 py-2 text-sm font-medium ${
22 | matchRoute(link.path)
23 | ? "bg-yellow-800 text-yellow-50"
24 | : "bg-opacity-0 text-richblack-300"
25 | } transition-all duration-200`}
26 | >
27 | {/* Yellow bar */}
28 |
33 |
34 | {/* Icon Goes Here */}
35 |
36 | {link.name}
37 |
38 |
39 | )
40 | }
41 |
42 | export default SidebarLink
43 |
--------------------------------------------------------------------------------
/Server/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const userSchema = new mongoose.Schema({
4 | firstName:{
5 | type:String,
6 | required:true,
7 | trim:true
8 | },
9 | lastName:{
10 | type:String,
11 | required:true,
12 | trim:true
13 | },
14 | email:{
15 | type:String,
16 | required:true,
17 | trim:true
18 | },
19 | password:{
20 | type:String,
21 | required:true,
22 | trim:true
23 | },
24 | accountType:{
25 | type:String,
26 | enum:["Student","Instructor","Admin"],
27 | required:true,
28 | },
29 | additionalDetails:{
30 | type:mongoose.Schema.Types.ObjectId,
31 | ref:"Profile",
32 | required:true, //watch for this required or not
33 | },
34 | courses:[{
35 | type:mongoose.Schema.Types.ObjectId,
36 | ref:"Course",
37 | }],
38 | token:{
39 | type: String
40 | },
41 | resetPasswordExpires:{
42 | type: Date
43 | },
44 | image:{
45 | type:String,
46 | required:true
47 | },
48 | courseProgress:[{
49 | type:mongoose.Schema.Types.ObjectId,
50 | ref:"CourseProgress"
51 | }],
52 | active: {
53 | type: Boolean,
54 | default: true,
55 | },
56 | approved: {
57 | type: Boolean,
58 | default: true,
59 | },
60 | })
61 |
62 | module.exports = mongoose.model("User", userSchema);
--------------------------------------------------------------------------------
/src/components/core/HomePage/CourseCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BsFillPeopleFill } from 'react-icons/bs';
3 | import { TbHierarchy3 } from 'react-icons/tb';
4 |
5 | const CourseCard = ({cardData,currentCard,}) => {
6 | return (
7 |
10 |
11 |
12 |
14 | {cardData.heading}
15 |
16 |
{cardData.description}
17 |
18 |
19 |
21 |
22 |
23 | {cardData.level}
24 |
25 |
26 |
27 | {cardData.lessionNumber} Lessons
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default CourseCard
--------------------------------------------------------------------------------
/src/components/core/AboutPage/Stats.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | const Stats = [
5 | {count: "5K", label: "Active Students"},
6 | {count: "10+", label: "Mentors"},
7 | {count: "200+", label: "Courses"},
8 | {count: "50+", label: "Awards"},
9 | ];
10 |
11 | const StatsComponent = () => {
12 | return (
13 |
14 |
15 |
16 |
17 | { Stats.map( (data, index) => {
18 | return (
19 |
20 |
21 |
22 | {data.count}
23 |
24 |
25 | {data.label}
26 |
27 |
28 |
29 | )
30 | } )}
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default StatsComponent
40 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
20 | StudyNotion Edtech
21 |
22 |
23 |
24 | You need to enable JavaScript to run this app.
25 |
26 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/components/core/Catalog/CourseSlider.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {Swiper, SwiperSlide} from "swiper/react"
4 | import "swiper/css"
5 | import "swiper/css/free-mode"
6 | import "swiper/css/pagination"
7 | import 'swiper/css/navigation';
8 | import { Autoplay,FreeMode,Navigation, Pagination} from 'swiper/modules'
9 |
10 | import Course_Card from './Course_Card'
11 |
12 | const CourseSlider = ({Courses}) => {
13 | return (
14 | <>
15 | {
16 | Courses?.length ? (
17 |
30 | {
31 | Courses?.map((course, index)=> (
32 |
33 |
34 |
35 | ))
36 | }
37 |
38 | ) : (
39 | No Course Found
40 | )
41 |
42 | }
43 | >
44 | )
45 | }
46 |
47 | export default CourseSlider
48 |
--------------------------------------------------------------------------------
/Server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 |
4 | const userRoutes = require("./routes/User");
5 | const profileRoutes = require("./routes/Profile");
6 | const paymentRoutes = require("./routes/Payments");
7 | const courseRoutes = require("./routes/Course");
8 |
9 | const database = require("./config/database");
10 | const cookieParser = require("cookie-parser");
11 | const cors = require("cors");
12 | const {cloudinaryConnect } = require("./config/cloudinary");
13 | const fileUpload = require("express-fileupload");
14 | const dotenv = require("dotenv");
15 |
16 | dotenv.config();
17 | const PORT = process.env.PORT || 4000;
18 |
19 | //database connect
20 | database.connectDB();
21 | //middlewares
22 | app.use(express.json());
23 | app.use(cookieParser());
24 | app.use(
25 | cors({
26 | origin: "*",
27 | credentials: true,
28 | })
29 | )
30 | // app.use((req, res, next) => {
31 | // res.header('Access-Control-Allow-Origin', '*');
32 | // next();
33 | // });
34 | app.use(
35 | fileUpload({
36 | useTempFiles:true,
37 | tempFileDir:"/tmp",
38 | })
39 | )
40 | //cloudinary connection
41 | cloudinaryConnect();
42 |
43 | //routes
44 | app.use("/api/v1/auth", userRoutes);
45 | app.use("/api/v1/profile", profileRoutes);
46 | app.use("/api/v1/course", courseRoutes);
47 | app.use("/api/v1/payment", paymentRoutes);
48 |
49 |
50 | //def route
51 |
52 | app.get("/", (req, res) => {
53 | return res.json({
54 | success:true,
55 | message:'Your server is up and running....'
56 | });
57 | });
58 |
59 | app.listen(PORT, () => {
60 | console.log(`App is running at ${PORT}`)
61 | })
62 |
63 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/InstructorSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Instructor from "../../../assets/Images/Instructor.png"
3 | import HighlightText from './HighlightText'
4 | import CTAButton from "../HomePage/Button"
5 | import { FaArrowRight } from 'react-icons/fa'
6 |
7 | const InstructorSection = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 | Become an
23 |
24 |
25 |
26 |
27 | Instructors from around the world teach millions of students on StudyNotion. We provide the tools and skills to teach what you love.
28 |
29 |
30 |
31 |
32 |
33 | Start Learning Today
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default InstructorSection
48 |
--------------------------------------------------------------------------------
/Server/controllers/courseProgress.js:
--------------------------------------------------------------------------------
1 | const CourseProgress = require("../models/CourseProgress");
2 | const SubSection = require("../models/SubSection");
3 |
4 | exports.updateCourseProgress = async (req,res) => {
5 | const {courseId, subSectionId} = req.body;
6 | const userId = req.user.id;
7 |
8 | try {
9 | const subSection = await SubSection.findById(subSectionId);
10 |
11 | if(!subSection){
12 | return res.status(404).json({
13 | error:"Invalid SubSection"
14 | })
15 | }
16 |
17 | let courseProgress = await CourseProgress.findOne({
18 | courseID:courseId,
19 | userId:userId
20 | })
21 |
22 | if (!courseProgress) {
23 | return res.status(404).json({
24 | error:"Course Progress does not exist"
25 | })
26 | }
27 | else{
28 | if (courseProgress.completedVideos.includes(subSectionId)) {
29 | return res.status(200).json({
30 | success:false,
31 | message:"Video already completed"
32 | })
33 | }
34 |
35 | courseProgress.completedVideos.push(subSectionId);
36 | console.log("Copurse Progress Push Done");
37 | }
38 | await courseProgress.save();
39 | console.log("Course Progress Save call Done");
40 | return res.status(200).json({
41 | success:true,
42 | message:"Course Progress Updated Successfully",
43 | })
44 | } catch (error) {
45 | console.error(error);
46 | return res.status(400).json({error:"Internal Server Error"});
47 | }
48 |
49 |
50 |
51 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Add Course/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import RenderSteps from './RenderSteps'
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 |
40 |
41 |
--------------------------------------------------------------------------------
/Server/routes/User.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | // Import the required controllers and middleware functions
6 | const {
7 | login,
8 | signUp,
9 | sendOtp,
10 | changePassword,
11 | } = require("../controllers/Auth")
12 | const {
13 | resetPasswordToken,
14 | resetPassword,
15 | } = require("../controllers/ResetPassword")
16 |
17 | const { auth } = require("../middlewares/auth")
18 |
19 | // Routes for Login, Signup, and Authentication
20 |
21 | // ********************************************************************************************************
22 | // Authentication routes
23 | // ********************************************************************************************************
24 |
25 | // Route for user login
26 | router.post("/login", login)
27 |
28 | // Route for user signup
29 | router.post("/signup", signUp)
30 |
31 | // Route for sending OTP to the user's email
32 | router.post("/sendotp", sendOtp)
33 |
34 | // Route for Changing the password
35 | router.post("/changepassword", auth, changePassword)
36 |
37 | // ********************************************************************************************************
38 | // Reset Password
39 | // ********************************************************************************************************
40 |
41 | // Route for generating a reset password token
42 | router.post("/reset-password-token", resetPasswordToken)
43 |
44 | // Route for resetting user's password after verification
45 | router.post("/reset-password", resetPassword)
46 |
47 | // Export the router for use in the main application
48 | module.exports = router
--------------------------------------------------------------------------------
/src/components/core/Dashboard/EditCourse/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import { useParams } from "react-router-dom"
4 |
5 | import {
6 | fetchCourseDetails,
7 | getFullDetailsOfCourse,
8 | } from "../../../../services/operations/courseDetailsAPI"
9 | import { setCourse, setEditCourse } from "../../../../slices/courseSlice"
10 | import RenderSteps from "../Add Course/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 |
37 | )
38 | }
39 |
40 | return (
41 |
42 |
43 | Edit Course
44 |
45 |
46 | {course ? (
47 |
48 | ) : (
49 |
50 | Course not found
51 |
52 | )}
53 |
54 |
55 | )
56 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/DeleteAccount.js:
--------------------------------------------------------------------------------
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 |
42 | I want to delete my account.
43 |
44 |
45 |
46 | >
47 | )
48 | }
--------------------------------------------------------------------------------
/src/components/core/Catalog/Course_Card.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import RatingStars from '../../common/RatingStars'
3 | import GetAvgRating from '../../../utils/avgRating';
4 | import { Link } from 'react-router-dom';
5 |
6 | const Course_Card = ({course, Height}) => {
7 |
8 |
9 | const [avgReviewCount, setAvgReviewCount] = useState(0);
10 |
11 | useEffect(()=> {
12 | const count = GetAvgRating(course.ratingAndReviews);
13 | setAvgReviewCount(count);
14 | },[course])
15 |
16 |
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
{course?.courseName}
31 |
{course?.instructor?.firstName} {course?.instructor?.lastName}
32 |
33 | {avgReviewCount || 0}
34 |
35 | {course?.ratingAndReviews?.length} Ratings
36 |
37 |
{course?.price}
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default Course_Card
48 |
--------------------------------------------------------------------------------
/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 |
37 |
45 |
46 |
47 | )}
48 |
49 | )
50 | }
51 |
52 | export default Template
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tailwind-css-starter-pack",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ramonak/react-progress-bar": "^5.0.3",
7 | "@reduxjs/toolkit": "^1.9.5",
8 | "axios": "^1.4.0",
9 | "chart.js": "^4.3.3",
10 | "concurrently": "^8.2.0",
11 | "copy-to-clipboard": "^3.3.3",
12 | "react": "^18.2.0",
13 | "react-chartjs-2": "^5.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-dropzone": "^14.2.3",
16 | "react-hook-form": "^7.45.4",
17 | "react-hot-toast": "^2.4.1",
18 | "react-icons": "^4.10.1",
19 | "react-otp-input": "^3.0.4",
20 | "react-rating-stars-component": "^2.2.0",
21 | "react-redux": "^8.1.2",
22 | "react-router-dom": "^6.14.2",
23 | "react-scripts": "5.0.1",
24 | "react-super-responsive-table": "^5.2.1",
25 | "react-type-animation": "^3.1.0",
26 | "swiper": "^10.1.0",
27 | "video-react": "^0.16.0",
28 | "web-vitals": "^2.1.4"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/thepranaygupta/react-tailwind-css-starter-pack.git"
33 | },
34 | "author": "Pranay Gupta",
35 | "bugs": {
36 | "url": "https://github.com/thepranaygupta/react-tailwind-css-starter-pack/issues"
37 | },
38 | "scripts": {
39 | "start": "react-scripts start",
40 | "build": "react-scripts build",
41 | "eject": "react-scripts eject",
42 | "server": "cd server && npm run dev",
43 | "dev": "concurrently -n \"client,server\" -c \"bgBlue,bgYellow\" \"npm start\" \"npm run server\""
44 | },
45 | "eslintConfig": {
46 | "extends": [
47 | "react-app",
48 | "react-app/jest"
49 | ]
50 | },
51 | "browserslist": {
52 | "production": [
53 | ">0.2%",
54 | "not dead",
55 | "not op_mini all"
56 | ],
57 | "development": [
58 | "last 1 chrome version",
59 | "last 1 firefox version",
60 | "last 1 safari version"
61 | ]
62 | },
63 | "devDependencies": {
64 | "tailwindcss": "^3.2.7"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/LearningLanguageSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from './HighlightText'
3 | import know_your_progress from "../../../assets/Images/Know_your_progress.png"
4 | import compare_with_others from "../../../assets/Images/Compare_with_others.png"
5 | import plan_your_lesson from "../../../assets/Images/Plan_your_lessons.png"
6 | import CTAButton from "../HomePage/Button"
7 |
8 | const LearningLanguageSection = () => {
9 | return (
10 |
11 |
12 |
13 |
14 | Your Swiss Knife for
15 |
16 |
17 |
18 |
19 | Using spin making learning multiple languages easy. with 20+ languages realistic voice-over, progress tracking, custom schedule and more.
20 |
21 |
22 |
39 |
40 |
41 |
42 |
43 | Learn more
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 | }
52 |
53 | export default LearningLanguageSection
54 |
--------------------------------------------------------------------------------
/src/data/footer-links.js:
--------------------------------------------------------------------------------
1 | export const FooterLink2 = [
2 | {
3 | title: "Subjects",
4 | links: [
5 | { title: "Al", link: "/al" },
6 | { title: "Cloud Computing", link: "/cloud-computing" },
7 | { title: "Code Foundations", link: "/code-foundations" },
8 | { title: "Computer Science", link: "/computer-science" },
9 | { title: "Cybersecurity", link: "/cybersecurity" },
10 | { title: "Data Analytics", link: "/data-analytics" },
11 | { title: "Data Science", link: "/data-science" },
12 | { title: "Data Visualization", link: "/data-visualization" },
13 | { title: "Developer Tools", link: "/developer-tools" },
14 | { title: "DevOps", link: "/devops" },
15 | { title: "Game Development", link: "/game-development" },
16 | { title: "IT", link: "/it" },
17 | { title: "Machine Learning", link: "/machine-learning" },
18 | { title: "Math", link: "/math" },
19 | { title: "Mobile Development", link: "/mobile-development" },
20 | { title: "Web Design", link: "/web-design" },
21 | { title: "Web Development", link: "/web-development" },
22 | ],
23 | },
24 | {
25 | title: "Languages",
26 | links: [
27 | { title: "Bash", link: "/bash" },
28 | { title: "C++", link: "/c++" },
29 | { title: "C#", link: "/csharp" },
30 | { title: "Go", link: "/go" },
31 | { title: "HTML & CSS", link: "/html-css" },
32 | { title: "Java", link: "/java" },
33 | { title: "JavaScript", link: "/javascript" },
34 | { title: "Kotlin", link: "/kotlin" },
35 | { title: "PHP", link: "/php" },
36 | { title: "Python", link: "/python" },
37 | { title: "R", link: "/r" },
38 | { title: "Ruby", link: "/ruby" },
39 | { title: "SQL", link: "/sql" },
40 | { title: "Swift", link: "/swift" },
41 | ],
42 | },
43 | {
44 | title: "Career building",
45 | links: [
46 | {title: "Career paths", link: "/career-paths"},
47 | {title: "Career services", link: "/career-services"},
48 | {title: "Interview prep", link: "/interview-prep"},
49 | {title: "Professional certification", link: "/professional-certification"},
50 | {title: "-", link: "/hi"},
51 | {title: "Full Catalog", link: "/full-catalog"},
52 | {title: "Beta Content", link: "/beta-content"}
53 | ]
54 | }
55 | ];
56 |
--------------------------------------------------------------------------------
/src/components/core/Auth/ProfileDropDown.js:
--------------------------------------------------------------------------------
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 | setOpen(true)}>
23 |
24 |
29 |
30 |
31 | {open && (
32 | e.stopPropagation()}
34 | className="absolute top-[118%] right-0 z-[1000] divide-y-[1px] divide-richblack-700 overflow-hidden rounded-md border-[1px] border-richblack-700 bg-richblack-800"
35 | ref={ref}
36 | >
37 |
setOpen(false)}>
38 |
39 |
40 | Dashboard
41 |
42 |
43 |
{
45 | dispatch(logout(navigate))
46 | setOpen(false)
47 | }}
48 | className="flex w-full items-center gap-x-1 py-[10px] px-[12px] text-sm text-richblack-100 hover:bg-richblack-700 hover:text-richblack-25"
49 | >
50 |
51 | Logout
52 |
53 |
54 | )}
55 |
56 | )
57 | }
--------------------------------------------------------------------------------
/Server/mail/templates/emailVerificationTemplate.js:
--------------------------------------------------------------------------------
1 | const otpTemplate = (otp) => {
2 | return `
3 |
4 |
5 |
6 |
7 | OTP Verification Email
8 |
64 |
65 |
66 |
67 |
68 |
69 |
71 |
OTP Verification Email
72 |
73 |
Dear User,
74 |
Thank you for registering with StudyNotion. To complete your registration, please use the following OTP
75 | (One-Time Password) to verify your account:
76 |
${otp}
77 |
This OTP is valid for 5 minutes. If you did not request this verification, please disregard this email.
78 | Once your account is verified, you will have access to our platform and its features.
79 |
80 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
82 |
83 |
84 |
85 | `;
86 | };
87 | module.exports = otpTemplate;
--------------------------------------------------------------------------------
/Server/mail/templates/passwordUpdate.js:
--------------------------------------------------------------------------------
1 | exports.passwordUpdated = (email, name) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Password Update Confirmation
8 |
53 |
54 |
55 |
56 |
57 |
58 |
60 |
Password Update Confirmation
61 |
62 |
Hey ${name},
63 |
Your password has been successfully updated for the email ${email} .
64 |
65 |
If you did not request this password change, please contact us immediately to secure your account.
66 |
67 |
If you have any questions or need further assistance, please feel free to reach out to us
68 | at
69 |
info@studynotion.com . We are here to help!
70 |
71 |
72 |
73 |
74 | `;
75 | };
--------------------------------------------------------------------------------
/src/pages/ViewCourse.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { Outlet, useLocation, useParams } from 'react-router-dom';
4 | import { getFullDetailsOfCourse } from '../services/operations/courseDetailsAPI';
5 | import { setCompletedLectures, setCourseSectionData, setEntireCourseData, setTotalNoOfLectures } from '../slices/viewCourseSlice';
6 | import VideoDetailsSidebar from '../components/core/ViewCourse/VideoDetailsSidebar';
7 | import CourseReviewModal from '../components/core/ViewCourse/CourseReviewModal';
8 |
9 | const ViewCourse = () => {
10 | const [reviewModal, setReviewModal] = useState(false)
11 | const {courseId} = useParams();
12 | const {token} = useSelector((state)=> state.auth);
13 | const dispatch = useDispatch();
14 | const location = useLocation()
15 | const {courseSectionData, courseEntireData, completedLectures} = useSelector((state)=>state.viewCourse);
16 |
17 | // useEffect(() => {
18 | // dispatch(setCourseSectionData([]));
19 |
20 | // dispatch(setEntireCourseData([]));
21 |
22 | // dispatch(setCompletedLectures(0))
23 |
24 | // }, [])
25 |
26 |
27 | useEffect(() => {
28 | const setCourseSpecificDetails = async () => {
29 | // console.log("In video details page", courseId)
30 | const courseData = await getFullDetailsOfCourse(courseId, token);
31 | dispatch(setCourseSectionData(courseData.courseDetails.courseContent));
32 |
33 | dispatch(setEntireCourseData(courseData.courseDetails));
34 |
35 | dispatch(setCompletedLectures(courseData.completedVideos));
36 |
37 | let lectures = 0;
38 | courseData.courseDetails.courseContent.forEach((sec) => {
39 | lectures += sec.subSection.length
40 | } )
41 | dispatch(setTotalNoOfLectures(lectures))
42 | }
43 |
44 | setCourseSpecificDetails()
45 | },[courseId])
46 |
47 | return (
48 | <>
49 |
50 |
51 |
56 | {reviewModal && (
)}
57 |
58 |
59 | >
60 | )
61 | }
62 |
63 | export default ViewCourse
--------------------------------------------------------------------------------
/src/slices/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { toast } from "react-hot-toast";
3 |
4 | const initialState = {
5 | cart: localStorage.getItem("cart") ? JSON.parse(localStorage.getItem("cart")):[],
6 | total: localStorage.getItem("total") ? JSON.parse(localStorage.getItem("total")):0,
7 | totalItems: localStorage.getItem("totalItems") ? JSON.parse(localStorage.getItem("totalItems")):0,
8 | }
9 |
10 | const cartSlice = createSlice({
11 | name:"cart",
12 | initialState:initialState,
13 | reducers: {
14 | addToCart : (state,action)=>{
15 | const course = action.payload;
16 | const index = state.cart.findIndex((item)=> item._id === course._id)
17 |
18 | if(index>=0){
19 | toast.error("Course present in cart");
20 | return
21 | }
22 |
23 | state.cart.push(course);
24 | state.totalItems++;
25 | state.total += JSON.parse(course.price);
26 |
27 | localStorage.setItem("cart", JSON.stringify(state.cart))
28 | localStorage.setItem("total", JSON.stringify(state.total))
29 | localStorage.setItem("totalItems", JSON.stringify(state.totalItems))
30 | // show toast
31 | toast.success("Course added to cart")
32 |
33 | },
34 | removeFromCart: (state, action) => {
35 | const courseId = action.payload
36 | const index = state.cart.findIndex((item) => item._id === courseId)
37 |
38 | if (index >= 0) {
39 | // If the course is found in the cart, remove it
40 | state.totalItems--
41 | state.total -= state.cart[index].price
42 | state.cart.splice(index, 1)
43 | // Update to localstorage
44 | localStorage.setItem("cart", JSON.stringify(state.cart))
45 | localStorage.setItem("total", JSON.stringify(state.total))
46 | localStorage.setItem("totalItems", JSON.stringify(state.totalItems))
47 | // show toast
48 | toast.success("Course removed from cart")
49 | }
50 | },
51 | resetCart: (state) => {
52 | state.cart = []
53 | state.total = 0
54 | state.totalItems = 0
55 | // Update to localstorage
56 | localStorage.removeItem("cart")
57 | localStorage.removeItem("total")
58 | localStorage.removeItem("totalItems")
59 | }
60 | }
61 | })
62 |
63 | export const { addToCart, removeFromCart, resetCart } = cartSlice.actions
64 |
65 | export default cartSlice.reducer
--------------------------------------------------------------------------------
/Server/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken")
2 |
3 |
4 | exports.auth = async (req,res, next) => {
5 |
6 | try {
7 |
8 | const token = req.body.token || req.cookies.token || req.get("Authorization")?.replace("Bearer ", "");
9 |
10 | if(!token) {
11 | return res.status(401).json({
12 | success:false,
13 | message:'TOken is missing',
14 | });
15 | }
16 | try {
17 | const payload = jwt.verify(token,process.env.JWT_SECRET);
18 | req.user = payload;
19 | } catch (error) {
20 | return res.status(401).json({
21 | success:false,
22 | message:"Invaild token."
23 | })
24 | }
25 | next();
26 | } catch (error) {
27 | console.log(error)
28 | return res.status(401).json({
29 | success:false,
30 | message:"Error in validating token"
31 | })
32 | }
33 | }
34 |
35 | exports.isStudent = async(req,res,next) => {
36 | try{
37 | if(req.user.accountType !== "Student") {
38 | return res.status(401).json({
39 | success:false,
40 | message:'This is a protected route for Students only',
41 | });
42 | }
43 | next();
44 | }
45 | catch(error) {
46 | return res.status(500).json({
47 | success:false,
48 | message:'User role cannot be verified, please try again'
49 | })
50 | }
51 | }
52 | exports.isInstructor = async(req,res,next) => {
53 | try{
54 | if(req.user.accountType !== "Instructor") {
55 | return res.status(401).json({
56 | success:false,
57 | message:'This is a protected route for Instructor only',
58 | });
59 | }
60 | next();
61 | }
62 | catch(error) {
63 | return res.status(500).json({
64 | success:false,
65 | message:'User role cannot be verified, please try again'
66 | })
67 | }
68 | }
69 |
70 | exports.isAdmin = async (req, res, next) => {
71 | try{
72 | if(req.user.accountType !== "Admin") {
73 | return res.status(401).json({
74 | success:false,
75 | message:'This is a protected route for Admin only',
76 | });
77 | }
78 | next();
79 | }
80 | catch(error) {
81 | return res.status(500).json({
82 | success:false,
83 | message:'User role cannot be verified, please try again'
84 | })
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/CodeBlocks.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CTAButton from "../HomePage/Button"
3 | import HighlightText from './HighlightText'
4 | import {FaArrowRight} from "react-icons/fa"
5 | import { TypeAnimation } from 'react-type-animation'
6 | import "./Border.css";
7 | const CodeBlocks = ({
8 | position, heading, subheading, ctabtn1, ctabtn2, codeblock, backgroudGradient, codeColor
9 | }) => {
10 | return (
11 |
12 |
13 | {/*Section 1*/}
14 |
15 | {heading}
16 |
17 | {subheading}
18 |
19 |
20 |
21 |
22 |
23 | {ctabtn1.btnText}
24 |
25 |
26 |
27 |
28 |
29 | {ctabtn2.btnText}
30 |
31 |
32 |
33 |
34 |
35 |
36 | {/*Section 2*/}
37 |
38 | {/*HW -> BG gradient*/}
39 |
41 |
43 |
1
44 |
2
45 |
3
46 |
4
47 |
5
48 |
6
49 |
7
50 |
8
51 |
9
52 |
10
53 |
11
54 |
55 |
56 |
58 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default CodeBlocks
81 |
--------------------------------------------------------------------------------
/src/services/apis.js:
--------------------------------------------------------------------------------
1 | const BASE_URL = process.env.REACT_APP_BASE_URL
2 |
3 | // AUTH ENDPOINTS
4 | export const endpoints = {
5 | SENDOTP_API: BASE_URL + "/auth/sendotp",
6 | SIGNUP_API: BASE_URL + "/auth/signup",
7 | LOGIN_API: BASE_URL + "/auth/login",
8 | RESETPASSTOKEN_API: BASE_URL + "/auth/reset-password-token",
9 | RESETPASSWORD_API: BASE_URL + "/auth/reset-password",
10 | }
11 |
12 | // PROFILE ENDPOINTS
13 | export const profileEndpoints = {
14 | GET_USER_DETAILS_API: BASE_URL + "/profile/getUserDetails",
15 | GET_USER_ENROLLED_COURSES_API: BASE_URL + "/profile/getEnrolledCourses",
16 | GET_INSTRUCTOR_DATA_API: BASE_URL + "/profile/instructorDashboard",
17 | }
18 |
19 | // STUDENTS ENDPOINTS
20 | export const studentEndpoints = {
21 | COURSE_PAYMENT_API: BASE_URL + "/payment/capturePayment",
22 | COURSE_VERIFY_API: BASE_URL + "/payment/verifyPayment",
23 | SEND_PAYMENT_SUCCESS_EMAIL_API: BASE_URL + "/payment/sendPaymentSuccessEmail",
24 | }
25 |
26 | // COURSE ENDPOINTS
27 | export const courseEndpoints = {
28 | GET_ALL_COURSE_API: BASE_URL + "/course/getAllCourses",
29 | COURSE_DETAILS_API: BASE_URL + "/course/getCourseDetails",
30 | EDIT_COURSE_API: BASE_URL + "/course/editCourse",
31 | COURSE_CATEGORIES_API: BASE_URL + "/course/showAllCategories",
32 | CREATE_COURSE_API: BASE_URL + "/course/createCourse",
33 | CREATE_SECTION_API: BASE_URL + "/course/addSection",
34 | CREATE_SUBSECTION_API: BASE_URL + "/course/addSubSection",
35 | UPDATE_SECTION_API: BASE_URL + "/course/updateSection",
36 | UPDATE_SUBSECTION_API: BASE_URL + "/course/updateSubSection",
37 | GET_ALL_INSTRUCTOR_COURSES_API: BASE_URL + "/course/getInstructorCourses",
38 | DELETE_SECTION_API: BASE_URL + "/course/deleteSection",
39 | DELETE_SUBSECTION_API: BASE_URL + "/course/deleteSubSection",
40 | DELETE_COURSE_API: BASE_URL + "/course/deleteCourse",
41 | GET_FULL_COURSE_DETAILS_AUTHENTICATED:
42 | BASE_URL + "/course/getFullCourseDetails",
43 | LECTURE_COMPLETION_API: BASE_URL + "/course/updateCourseProgress",
44 | CREATE_RATING_API: BASE_URL + "/course/createRating",
45 | }
46 |
47 | // RATINGS AND REVIEWS
48 | export const ratingsEndpoints = {
49 | REVIEWS_DETAILS_API: BASE_URL + "/course/getReviews",
50 | }
51 |
52 | // CATAGORIES API
53 | export const categories = {
54 | CATEGORIES_API: BASE_URL + "/course/showAllCategories",
55 | }
56 |
57 | // CATALOG PAGE DATA
58 | export const catalogData = {
59 | CATALOGPAGEDATA_API: BASE_URL + "/course/getCategoryPageDetails",
60 | }
61 | // CONTACT-US API
62 | export const contactusEndpoint = {
63 | CONTACT_US_API: BASE_URL + "/reach/contact",
64 | }
65 |
66 | // SETTINGS PAGE API
67 | export const settingsEndpoints = {
68 | UPDATE_DISPLAY_PICTURE_API: BASE_URL + "/profile/updateDisplayPicture",
69 | UPDATE_PROFILE_API: BASE_URL + "/profile/updateProfile",
70 | CHANGE_PASSWORD_API: BASE_URL + "/auth/changepassword",
71 | DELETE_PROFILE_API: BASE_URL + "/profile/deleteProfile",
72 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useLocation, useNavigate, } from 'react-router-dom'
3 | import { useDispatch, useSelector } from "react-redux"
4 | import { VscSignOut } from "react-icons/vsc"
5 | import SidebarLink from './SidebarLink'
6 | import { sidebarLinks } from "../../../data/dashboard-links"
7 | import { logout } from "../../../services/operations/authAPI"
8 | import ConfirmationModal from '../../common/ConfirmationModal'
9 | import { useState } from 'react'
10 | const Sidebar = () => {
11 |
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 |
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 |
43 |
{
44 | setConfirmationModal({
45 | text1: "Are you sure?",
46 | text2: "You will be logged out of your account.",
47 | btn1Text: "Logout",
48 | btn2Text: "Cancel",
49 | btn1Handler: ()=> dispatch(logout(navigate)),
50 | btn2Handler: ()=> setConfirmationModal(null),
51 | })
52 | }}
53 | className="px-8 py-2 text-sm font-medium text-richblack-300">
54 |
55 |
56 | Logout
57 |
58 |
59 |
60 |
61 | {confirmationModal && }
62 | >
63 | )
64 | }
65 |
66 | export default Sidebar
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Add Course/CourseInformation/RequirementField.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useState } from 'react'
4 | const RequirementField = ({
5 | name,
6 | label,
7 | register,
8 | setValue,
9 | errors,
10 | getValues,
11 | }) => {
12 | const {course, editCourse} = useSelector((state)=> state.course)
13 | const [requirement, setRequirement] = useState("")
14 | const [requirementsList, setRequirementsList] = useState([])
15 |
16 | useEffect(() => {
17 | if(editCourse){
18 | // console.log("In requirements field, 1st render, editCourse=true course is",course)
19 | setRequirementsList(JSON.parse(course?.instructions));
20 | }
21 | register(name, {required:true, validate: (value)=> value.length > 0 })
22 |
23 | }, [])
24 |
25 | useEffect(() => {
26 | setValue(name, requirementsList)
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 |
45 | {label} *
46 |
47 |
48 | setRequirement(e.target.value)}
53 | className="form-style w-full"
54 | />
55 |
60 | Add
61 |
62 |
63 |
64 |
65 | {requirementsList.length > 0 && (
66 |
67 | {requirementsList.map((requirement, index) => (
68 |
69 | {requirement}
70 | handleRemoveRequirement(index)}
74 | >
75 | clear
76 |
77 |
78 | ))}
79 |
80 | )}
81 | {errors[name] && (
82 |
83 | {label} is required
84 |
85 | )}
86 |
87 | )
88 | }
89 |
90 | export default RequirementField
--------------------------------------------------------------------------------
/src/pages/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { Link } from 'react-router-dom'
4 | import { BiArrowBack } from "react-icons/bi"
5 | import { getPasswordResetToken } from '../services/operations/authAPI'
6 | const ForgotPassword = () => {
7 |
8 | const {loading} = useSelector((state)=> state.auth);
9 | const [email, setEmail] = useState("");
10 | const [emailSent, setEmailSent] = useState(false)
11 | const dispatch = useDispatch();
12 | const handleOnSubmit =(e)=>{
13 | e.preventDefault();
14 | dispatch(getPasswordResetToken(email, setEmailSent))
15 | }
16 | return (
17 |
18 | {loading ? (
19 |
20 | ) : (
21 |
22 |
23 | {!emailSent ? "Reset your password" : "Check email"}
24 |
25 |
26 | {!emailSent
27 | ? "Have no fear. We'll email you instructions to reset your password. If you dont have access to your email we can try account recovery"
28 | : `We have sent the reset email to ${email}`}
29 |
30 |
57 |
58 |
59 |
60 | Back To Login
61 |
62 |
63 |
64 |
65 | )}
66 |
67 | )
68 | }
69 |
70 | export default ForgotPassword
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/services/operations/profileAPI.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-hot-toast"
2 |
3 | import { setLoading, setUser } from "../../slices/profileSlice"
4 | import { apiConnector } from "../apiconnector"
5 | import { profileEndpoints } from "../apis"
6 | import { logout } from "./authAPI"
7 |
8 | const { GET_USER_DETAILS_API,
9 | GET_USER_ENROLLED_COURSES_API,
10 | GET_INSTRUCTOR_DATA_API
11 | } = profileEndpoints
12 |
13 | export function getUserDetails(token, navigate) {
14 | return async (dispatch) => {
15 | const toastId = toast.loading("Loading...")
16 | dispatch(setLoading(true))
17 | try {
18 | const response = await apiConnector("GET", GET_USER_DETAILS_API, null, {
19 | Authorization: `Bearer ${token}`,
20 | })
21 | console.log("GET_USER_DETAILS API RESPONSE............", response)
22 |
23 | if (!response.data.success) {
24 | throw new Error(response.data.message)
25 | }
26 | const userImage = response.data.data.image
27 | ? response.data.data.image
28 | : `https://api.dicebear.com/5.x/initials/svg?seed=${response.data.data.firstName} ${response.data.data.lastName}`
29 | dispatch(setUser({ ...response.data.data, image: userImage }))
30 | } catch (error) {
31 | dispatch(logout(navigate))
32 | console.log("GET_USER_DETAILS API ERROR............", error)
33 | toast.error("Could Not Get User Details")
34 | }
35 | toast.dismiss(toastId)
36 | dispatch(setLoading(false))
37 | }
38 | }
39 |
40 | export async function getUserEnrolledCourses(token) {
41 | const toastId = toast.loading("Loading...")
42 | let result = []
43 | try {
44 | console.log("BEFORE Calling BACKEND API FOR ENROLLED COURSES");
45 | const response = await apiConnector(
46 | "GET",
47 | GET_USER_ENROLLED_COURSES_API,
48 | null,
49 | {
50 | Authorization: `Bearer ${token}`,
51 | }
52 | )
53 | console.log("AFTER Calling BACKEND API FOR ENROLLED COURSES");
54 | // console.log(
55 | // "GET_USER_ENROLLED_COURSES_API API RESPONSE............",
56 | // response
57 | // )
58 |
59 | if (!response.data.success) {
60 | throw new Error(response.data.message)
61 | }
62 | result = response.data.data
63 | } catch (error) {
64 | console.log("GET_USER_ENROLLED_COURSES_API API ERROR............", error)
65 | toast.error("Could Not Get Enrolled Courses")
66 | }
67 | toast.dismiss(toastId)
68 | return result
69 | }
70 |
71 | export async function getInstructorData(token) {
72 | const toastId = toast.loading("Loading...")
73 | let result = []
74 | try {
75 |
76 | const response = await apiConnector("GET", GET_INSTRUCTOR_DATA_API, null,
77 | {
78 | Authorization: `Bearer ${token}`
79 | } )
80 | console.log("GET_INSTRUCTOR_DATA_API response....", response)
81 | result= response?.data?.courses
82 | } catch (error) {
83 | console.log("GET_INSTRUCTOR_DATA_API API ERROR............", error)
84 | toast.error("Could Not Get Instructor Data")
85 | }
86 | toast.dismiss(toastId)
87 | return result
88 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Add Course/CourseInformation/ChipInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useEffect } from 'react';
3 | import { useState } from 'react';
4 | import { useSelector } from 'react-redux'
5 | import { GrFormClose } from "react-icons/gr"
6 | const ChipInput = ({
7 | label, name,
8 | placeholder,
9 | register,
10 | errors,
11 | setValue,
12 | getValues,
13 | }) => {
14 | const {course, editCourse} = useSelector((state)=>state.course);
15 | const [chips, setChips] = useState([])
16 |
17 | useEffect(() => {
18 | if(editCourse) {
19 | setChips(JSON.parse(course?.tags))
20 | }
21 | register(name, {required:true, validate: (value)=> value.length > 0})
22 |
23 | }, [])
24 |
25 | useEffect(() => {
26 | setValue(name, chips)
27 | }, [chips])
28 |
29 | const handleKeyDown = (e) => {
30 | if (e.key === "Enter" || e.key === ",") {
31 | e.preventDefault();
32 | const chipValue = e.target.value.trim();
33 |
34 | if(chipValue && !chips.includes(chipValue)){
35 | setChips([...chips, chipValue])
36 | e.target.value = ""
37 | }
38 | }
39 | }
40 |
41 | const handleDeleteChip = (chipIndex) => {
42 | const newChips = chips.filter((curr, ind) => ind!==chipIndex )
43 | setChips(newChips)
44 | }
45 | return (
46 |
47 | {/* Render the label for the input */}
48 |
49 | {label} *
50 |
51 | {/* Render the chips and input */}
52 |
53 | {/* Map over the chips array and render each chip */}
54 |
55 | {chips.map((chip, index) => (
56 |
60 |
61 | {/* Render the chip value */}
62 | {chip}
63 | {/* Render the button to delete the chip */}
64 | handleDeleteChip(index)}
68 | >
69 |
70 |
71 |
72 |
73 | ))}
74 |
75 |
76 | {/* Render the input for adding new chips */}
77 |
85 |
86 | {/* Render an error message if the input is required and not filled */}
87 | {errors[name] && (
88 |
89 | {label} is required
90 |
91 | )}
92 |
93 | )
94 | }
95 |
96 | export default ChipInput
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Cart/RenderCartCourses.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import {GiNinjaStar} from "react-icons/gi"
4 | import {RiDeleteBin6Line} from "react-icons/ri"
5 | import { removeFromCart } from '../../../../slices/cartSlice'
6 | import ReactStars from "react-rating-stars-component";
7 | import GetAvgRating from '../../../../utils/avgRating';
8 | import { FaStar } from "react-icons/fa"
9 | const RenderCartCourses = () => {
10 |
11 | const {cart} = useSelector((state) => state.cart);
12 | const dispatch = useDispatch();
13 |
14 | const getRating = (course) => {
15 | const count = GetAvgRating(course?.courseDetails.ratingAndReviews);
16 | return count;
17 | }
18 |
19 | return (
20 |
21 | {cart.map((course, indx) => (
22 |
28 |
29 |
34 |
35 |
36 | {course?.courseName}
37 |
38 |
39 | {course?.category?.name}
40 |
41 |
42 | {/* {getRating(course)} */}
43 | 4.5
44 | }
51 | fullIcon={ }
52 | />
53 |
54 | {course?.ratingAndReviews?.length} Ratings
55 |
56 |
57 |
58 |
59 |
60 |
dispatch(removeFromCart(course._id))}
62 | className="flex items-center gap-x-1 rounded-md border border-richblack-600 bg-richblack-700 py-3 px-[12px] text-pink-200"
63 | >
64 |
65 | Remove
66 |
67 |
68 | ₹ {course?.price}
69 |
70 |
71 |
72 | ))}
73 |
74 | )
75 | }
76 |
77 | export default RenderCartCourses
78 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/ExploreMore.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {HomePageExplore} from '../../../data/homepage-explore'
3 | import HighlightText from './HighlightText';
4 | import { useState } from 'react';
5 | import CourseCard from './CourseCard';
6 | const tabsName = [
7 | "Free",
8 | "New to coding",
9 | "Most popular",
10 | "Skills paths",
11 | "Career paths",
12 | ];
13 |
14 |
15 | const ExploreMore = () => {
16 | const [currentTab, setCurrentTab] = useState(HomePageExplore[0].tag)
17 | const [courses, setCourses] = useState(HomePageExplore[0].courses);
18 | const [currentCard, setCurrentCard] = useState(HomePageExplore[0].courses[0].heading);
19 |
20 | const setMyCourse = (value) =>{
21 | setCurrentTab(value)
22 | const result= HomePageExplore.filter((course)=>course.tag === value)
23 | setCourses(result[0].courses)
24 | setCurrentCard(result[0].courses[0].heading)
25 | }
26 |
27 | return (
28 |
29 |
30 | {/* Heading text */}
31 |
32 | Unlock the
33 |
34 |
35 |
36 | {/* Sub-Heading text */}
37 |
38 | Learn to build anything you can imagine
39 |
40 |
41 | {/*Tabs div */}
42 |
45 | {
46 | tabsName.map( (element, index) => {
47 | return (
48 |
setMyCourse(element)}
56 | >
57 | {element}
58 |
59 | )
60 | })
61 | }
62 |
63 |
64 | {/* Gap Div */}
65 |
66 |
67 | {/* course card ka group */}
68 |
71 | {
72 | courses.map( (element, index) => {
73 | return (
74 | {setCurrentCard(element.heading)}}
79 | />
80 | )
81 | } )
82 | }
83 |
84 |
85 | )
86 | }
87 |
88 | export default ExploreMore
--------------------------------------------------------------------------------
/src/components/core/Dashboard/InstructorDashboard/InstructorChart.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useState } from "react"
3 |
4 | import { Pie } from "react-chartjs-2"
5 | import {ArcElement,Chart} from 'chart.js'
6 |
7 | Chart.register(ArcElement)
8 |
9 |
10 | const InstructorChart = ({courses}) => {
11 | // State to keep track of the currently selected chart
12 | const [currChart, setCurrChart] = useState("students")
13 |
14 | // Function to generate random colors for the chart
15 | const generateRandomColors = (numColors) => {
16 | const colors = []
17 | for (let i = 0; i < numColors; i++) {
18 | const color = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
19 | Math.random() * 256
20 | )}, ${Math.floor(Math.random() * 256)})`
21 | colors.push(color)
22 | }
23 | return colors
24 | }
25 |
26 | // Data for the chart displaying student information
27 | const chartDataStudents = {
28 | labels: courses.map((course) => course.courseName),
29 | datasets: [
30 | {
31 | data: courses.map((course) => course.totalStudentsEnrolled),
32 | backgroundColor: generateRandomColors(courses.length),
33 | },
34 | ],
35 | }
36 | // console.log("chartDataStudents", chartDataStudents)
37 |
38 | // Data for the chart displaying income information
39 | const chartIncomeData = {
40 | labels: courses.map((course) => course.courseName),
41 | datasets: [
42 | {
43 | data: courses.map((course) => course.totalAmountGenerated),
44 | backgroundColor: generateRandomColors(courses.length),
45 | },
46 | ],
47 | }
48 | // console.log("chartIncomeData",chartIncomeData)
49 | // Options for the chart
50 | const options = {
51 | maintainAspectRatio: false,
52 | }
53 |
54 | return (
55 |
56 |
Visualize
57 |
58 | {/* Button to switch to the "students" chart */}
59 | setCurrChart("students")}
61 | className={`rounded-sm p-1 px-3 transition-all duration-200 ${
62 | currChart === "students"
63 | ? "bg-richblack-700 text-yellow-50"
64 | : "text-yellow-400"
65 | }`}
66 | >
67 | Students
68 |
69 | {/* Button to switch to the "income" chart */}
70 | setCurrChart("income")}
72 | className={`rounded-sm p-1 px-3 transition-all duration-200 ${
73 | currChart === "income"
74 | ? "bg-richblack-700 text-yellow-50"
75 | : "text-yellow-400"
76 | }`}
77 | >
78 | Income
79 |
80 |
81 |
82 | {/* Render the Pie chart based on the selected chart */}
83 |
87 |
88 |
89 | )
90 | }
91 |
92 | export default InstructorChart
--------------------------------------------------------------------------------
/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 |
93 | )
94 | }
95 |
96 | export default LoginForm
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Add Course/RenderSteps.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { FaCheck } from "react-icons/fa"
4 |
5 | import CourseBuilderForm from "./CourseBuilder/CourseBuilderForm"
6 | import CourseInformationForm from "./CourseInformation/CourseInformationForm.js"
7 | import PublishCourse from "./PublishCourse"
8 | const RenderSteps = () => {
9 | const {step} = useSelector((state)=> state.course)
10 |
11 | const steps = [
12 | {id:1,
13 | title: "Course Information"},
14 | {
15 | id: 2,
16 | title: "Course Builder",
17 | },
18 | {
19 | id: 3,
20 | title: "Publish",
21 | }
22 | ]
23 | return (
24 | <>
25 |
26 | {steps.map((item)=> (
27 | <>
28 | {/* Step Circle */}
29 |
30 | item.id ? ' bg-yellow-50' :'text-yellow-50'}`}
36 | >
37 | {step > item.id ? (
38 |
39 | ) :
40 | (item.id)}
41 |
42 |
43 | {/* Dotted Line */}
44 | {item.id !== steps.length && (
45 | <>
46 |
item.id ? "border-yellow-50" : "border-richblack-500"}`}
49 | >
50 | >
51 | )}
52 | >
53 | ))}
54 |
55 |
56 | {/* Steps titles */}
57 |
58 | {steps.map((item) => (
59 | <>
60 |
64 |
65 |
= item.id ? "text-richblack-5" : "text-richblack-500"
68 | }`}
69 | >
70 | {item.title}
71 |
72 |
73 |
74 | >
75 | ))}
76 |
77 |
78 | {/* Render specific component based on current step */}
79 | {step === 1 && }
80 | {step === 2 && }
81 | {step === 3 && }
82 |
83 | >
84 | )
85 | }
86 |
87 | export default RenderSteps
--------------------------------------------------------------------------------
/src/pages/ContactPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BsFillChatRightDotsFill } from 'react-icons/bs';
3 | import { BsGlobeAmericas } from 'react-icons/bs';
4 | import { IoIosCall } from 'react-icons/io';
5 | import ContactUsForm from '../components/ContactPage/ContactUsForm';
6 | import Footer from '../components/common/Footer';
7 | import ReviewSlider from "../components/common/ReviewSlider"
8 | const ContactPage = () => {
9 | const contactData = [
10 | {
11 | title:"Chat on us",
12 | desc:"Our friendly team is here to help.",
13 | address:"info@studynotion.com"
14 | },
15 | {
16 | title:"Visit us",
17 | desc:"Come and say hello at our office HQ.",
18 | address:"Akshya Nagar 1st Block 1st Cross, Rammurthy nagar, Bangalore-560016"
19 | },
20 | {
21 | title:"Call us",
22 | desc:"Mon - Fri From 8am to 5pm",
23 | address:"+123 456 7869"
24 | },
25 | ]
26 | return (
27 |
28 |
29 |
30 |
31 | {contactData.map((data,index)=>(
32 |
33 |
34 | {(index===0 && )}
35 | {(index===1 && )}
36 | {(index===2 && )}
37 |
{data.title}
38 |
39 |
{data.desc}
40 |
{data.address}
41 |
42 |
43 | ))}
44 |
45 |
46 |
47 |
48 |
49 |
Got a Idea? We've got the skills. Let's team up
50 |
Tell us more about yourself and what you're got in mind.
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Reviews From Other Learners
61 |
62 |
63 |
64 |
65 |
66 | )
67 | }
68 |
69 | export default ContactPage
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | /* write your css here */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | @layer utilities {
7 | .gradient-custom {
8 | background: linear-gradient(123.77deg, #8a2be2 -6.46%, orange 59.04%, #f8f8ff 124.53%);
9 | }
10 | }
11 |
12 | html,body{
13 | overflow-x: hidden;
14 | }
15 |
16 | .spinner {
17 | width: 56px;
18 | height: 56px;
19 | border-radius: 50%;
20 | border: 9px solid #f1f2ff;
21 | animation: spinner-bulqg1 0.8s infinite linear alternate,
22 | spinner-oaa3wk 1.6s infinite linear;
23 | }
24 |
25 | @keyframes spinner-bulqg1 {
26 | 0% {
27 | clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%);
28 | }
29 |
30 | 12.5% {
31 | clip-path: polygon(
32 | 50% 50%,
33 | 0 0,
34 | 50% 0%,
35 | 100% 0%,
36 | 100% 0%,
37 | 100% 0%,
38 | 100% 0%
39 | );
40 | }
41 |
42 | 25% {
43 | clip-path: polygon(
44 | 50% 50%,
45 | 0 0,
46 | 50% 0%,
47 | 100% 0%,
48 | 100% 100%,
49 | 100% 100%,
50 | 100% 100%
51 | );
52 | }
53 |
54 | 50% {
55 | clip-path: polygon(
56 | 50% 50%,
57 | 0 0,
58 | 50% 0%,
59 | 100% 0%,
60 | 100% 100%,
61 | 50% 100%,
62 | 0% 100%
63 | );
64 | }
65 |
66 | 62.5% {
67 | clip-path: polygon(
68 | 50% 50%,
69 | 100% 0,
70 | 100% 0%,
71 | 100% 0%,
72 | 100% 100%,
73 | 50% 100%,
74 | 0% 100%
75 | );
76 | }
77 |
78 | 75% {
79 | clip-path: polygon(
80 | 50% 50%,
81 | 100% 100%,
82 | 100% 100%,
83 | 100% 100%,
84 | 100% 100%,
85 | 50% 100%,
86 | 0% 100%
87 | );
88 | }
89 |
90 | 100% {
91 | clip-path: polygon(
92 | 50% 50%,
93 | 50% 100%,
94 | 50% 100%,
95 | 50% 100%,
96 | 50% 100%,
97 | 50% 100%,
98 | 0% 100%
99 | );
100 | }
101 | }
102 |
103 | @keyframes spinner-oaa3wk {
104 | 0% {
105 | transform: scaleY(1) rotate(0deg);
106 | }
107 |
108 | 49.99% {
109 | transform: scaleY(1) rotate(135deg);
110 | }
111 |
112 | 50% {
113 | transform: scaleY(-1) rotate(0deg);
114 | }
115 |
116 | 100% {
117 | transform: scaleY(-1) rotate(-135deg);
118 | }
119 | }
120 |
121 | .lable-style {
122 | @apply text-[14px] text-richblack-5;
123 | }
124 | .form-style {
125 | @apply rounded-lg bg-richblack-700 p-3 text-[16px] leading-[24px] text-richblack-5 shadow-[0_1px_0_0] shadow-white/50 placeholder:text-richblack-400 focus:outline-none;
126 | }
127 |
128 | .section_heading {
129 | --tw-text-opacity: 1;
130 | color: rgb(241 242 255/var(--tw-text-opacity));
131 | font-size: 1.5rem;
132 | font-weight: 700;
133 | line-height: 2rem;
134 | }
135 |
136 | @media (min-width: 1024px){
137 | .section_heading {
138 | font-size: 2.25rem;
139 | line-height: 2.5rem;
140 | }
141 | }
142 |
143 | .yellowButton {
144 | --tw-bg-opacity: 1;
145 | --tw-text-opacity: 1;
146 | background-color: rgb(255 214 10/var(--tw-bg-opacity));
147 | border-radius: 0.375rem;
148 | color: rgb(0 8 20/var(--tw-text-opacity));
149 | cursor: pointer;
150 | font-weight: 600;
151 | padding: 8px 20px;
152 | }
153 |
154 | .blackButton {
155 | --tw-bg-opacity: 1;
156 | background-color: rgb(22 29 41/var(--tw-bg-opacity));
157 | border-radius: 0.375rem;
158 | cursor: pointer;
159 | font-weight: 600;
160 | padding: 8px 20px;
161 | }
162 |
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/ChangeProfilePicture.js:
--------------------------------------------------------------------------------
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 |
69 |
70 |
Change Profile Picture
71 |
72 |
79 |
84 | Select
85 |
86 |
90 | {!loading && (
91 |
92 | )}
93 |
94 |
95 |
96 |
97 |
98 | >
99 | )
100 | }
--------------------------------------------------------------------------------
/Server/controllers/ResetPassword.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/User")
2 | const crypto = require('crypto')
3 | const mailSender = require('../utils/mailSender');
4 | const bcrypt = require('bcrypt')
5 |
6 | exports.resetPasswordToken = async (req,res) => {
7 |
8 | try {
9 | const {email} = req.body;
10 |
11 | if(!email){
12 | return res.status(400).json({
13 | success:false,
14 | message:"Email is empty"
15 | })
16 | }
17 |
18 | const existingUser = await User.findOne({email})
19 |
20 | if(!existingUser){
21 | return res.status(400).json({
22 | success:false,
23 | message:"Email doesn't exist"
24 | })
25 | }
26 |
27 | const token = crypto.randomUUID()
28 |
29 | const updatedUser = await User.findOneAndUpdate({email},
30 | {
31 | token:token,
32 | resetPasswordExpires: Date.now() + 5*60*1000
33 | },
34 | {new:true})
35 |
36 | const url = `http://localhost:3000/update-password/${token}`
37 |
38 | await mailSender(email, "Password Reset Link", `Password reset link: ${url}`);
39 |
40 | return res.status(200).json({
41 | success:true,
42 | message:'Reset link sent'
43 | })
44 | } catch (error) {
45 | console.log(error);
46 | return res.status(500).json({
47 | success:false,
48 | message:'Something went wrong while sending reset pwd mail'
49 | })
50 | }
51 | }
52 |
53 | exports.resetPassword = async (req,res) => {
54 |
55 | try {
56 | const {token, password, confirmPassword} = req.body;
57 |
58 | if(!token||!password||!confirmPassword){
59 | return res.status(400).json({
60 | success:false,
61 | message:"Enter all details"
62 | })
63 | }
64 |
65 | const existingUser = await User.findOne({token:token});
66 | if(!existingUser) {
67 | return res.json({
68 | success:false,
69 | message:'Token is invalid',
70 | });
71 | }
72 |
73 | if(existingUser.resetPasswordExpires {
13 | const {register, handleSubmit, setValue, getValues} = useForm()
14 |
15 | const dispatch = useDispatch()
16 | const navigate = useNavigate()
17 | const {token} = useSelector((state)=> state.auth)
18 | const {course} = useSelector((state)=> state.course)
19 | const [loading, setLoading] = useState(false)
20 |
21 | useEffect(() => {
22 | if(course?.status=== COURSE_STATUS.PUBLISHED){
23 | setValue("public", true)
24 | }
25 | }, [])
26 |
27 | const goBack = () => {
28 | dispatch(setStep(2))
29 | }
30 |
31 | const goToCourses = () => {
32 | dispatch(resetCourseState())
33 | navigate("/dashboard/my-courses")
34 | }
35 |
36 | const handleCoursePublish = async ()=> {
37 | if(
38 | (course?.status === COURSE_STATUS.PUBLISHED && getValues("public") === true) ||
39 | (course?.status === COURSE_STATUS.DRAFT && getValues("public")===false)
40 | ){
41 | goToCourses();
42 | return
43 | }
44 |
45 | const formData = new FormData()
46 | formData.append("courseId", course._id)
47 | const courseStatus = getValues("public")
48 | ? COURSE_STATUS.PUBLISHED
49 | : COURSE_STATUS.DRAFT
50 | formData.append("status", courseStatus)
51 | setLoading(true)
52 | const result = await editCourseDetails(formData, token)
53 | if (result) {
54 | goToCourses()
55 | }
56 | setLoading(false)
57 | }
58 |
59 | const onSubmit = (data) => {
60 | // console.log(data)
61 | handleCoursePublish()
62 | }
63 | return (
64 |
65 |
66 | Publish Settings
67 |
68 |
97 |
98 | )
99 | }
100 |
101 | export default PublishCourse
--------------------------------------------------------------------------------
/src/components/core/Dashboard/EnrolledCourses.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { getUserEnrolledCourses } from '../../../services/operations/profileAPI';
4 | import ProgressBar from '@ramonak/react-progress-bar';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | const EnrolledCourses = () => {
8 |
9 | const {token} = useSelector((state) => state.auth);
10 | const navigate = useNavigate()
11 | const [enrolledCourses, setEnrolledCourses] = useState(null);
12 |
13 |
14 | const getEnrolledCourses = async() => {
15 | try{
16 | const response = await getUserEnrolledCourses(token);
17 | setEnrolledCourses(response);
18 | }
19 | catch(error) {
20 | console.log("Unable to Fetch Enrolled Courses");
21 | }
22 | }
23 |
24 | useEffect(()=> {
25 | getEnrolledCourses();
26 | },[]);
27 |
28 |
29 | return (
30 | <>
31 | Enrolled Courses
32 | {!enrolledCourses ? (
33 |
36 | ) : !enrolledCourses.length ? (
37 |
38 | You have not enrolled in any course yet.
39 | {/* TODO: Modify this Empty State */}
40 |
41 | ) : (
42 |
43 | {/* Headings */}
44 |
45 |
Course Name
46 |
Duration
47 |
Progress
48 |
49 | {/* Course Names */}
50 | {enrolledCourses.map((course, i, arr) => (
51 |
57 |
{
60 | navigate(
61 | `/view-course/${course?._id}/section/${course.courseContent?.[0]?._id}/sub-section/${course.courseContent?.[0]?.subSection?.[0]?._id}`
62 | )
63 | }}
64 | >
65 |
70 |
71 |
{course.courseName}
72 |
73 | {course.description.length > 50
74 | ? `${course.description.slice(0, 50)}...`
75 | : course.description}
76 |
77 |
78 |
79 |
{course?.totalDuration}
80 |
81 |
Progress: {course.progressPercentage || 0}%
82 |
87 |
88 |
89 | ))}
90 |
91 | )}
92 | >
93 | )
94 | }
95 |
96 | export default EnrolledCourses
97 |
--------------------------------------------------------------------------------
/src/components/core/HomePage/TimelineSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
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 | import timelineImage from "../../../assets/Images/TimelineImage.png"
8 |
9 | const timeline = [
10 | {
11 | Logo: Logo1,
12 | heading: "Leadership",
13 | Description:"Fully committed to the success company",
14 | },
15 | {
16 | Logo: Logo2,
17 | heading: "Leadership",
18 | Description:"Fully committed to the success company",
19 | },
20 | {
21 | Logo: Logo3,
22 | heading: "Leadership",
23 | Description:"Fully committed to the success company",
24 | },
25 | {
26 | Logo: Logo4,
27 | heading: "Leadership",
28 | Description:"Fully committed to the success company",
29 | },
30 | ];
31 |
32 | const TimelineSection = () => {
33 | return (
34 |
35 |
36 |
37 |
38 | {
39 | timeline.map( (element, index) => {
40 | return (
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
{element.heading}
49 |
{element.Description}
50 |
51 |
52 |
53 |
55 |
56 |
57 |
58 | )
59 | } )
60 | }
61 |
62 |
63 |
64 |
68 |
69 |
72 |
73 |
10
74 |
Years of Experience
75 |
76 |
77 |
78 |
250
79 |
TYpe of Courses
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | )
89 | }
90 |
91 | export default TimelineSection
92 |
--------------------------------------------------------------------------------
/src/pages/VerifyEmail.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import OtpInput from "react-otp-input";
3 | import { Link } from "react-router-dom";
4 | import { BiArrowBack } from "react-icons/bi";
5 | import { RxCountdownTimer } from "react-icons/rx";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { sendOtp, signUp } from "../services/operations/authAPI";
8 | import { useNavigate } from "react-router-dom";
9 |
10 | const VerifyEmail = () => {
11 | const [otp, setOtp] = useState('');
12 | const {loading, signupData} = useSelector((state)=> state.auth);
13 | const dispatch= useDispatch();
14 | const navigate = useNavigate();
15 |
16 | useEffect(() => {
17 | if(!signupData) navigate('/signup')
18 | }, [])
19 |
20 | const handleVerifyAndSignup = (e) => {
21 | e.preventDefault();
22 | const {
23 | accountType,
24 | firstName,
25 | lastName,
26 | email,
27 | password,
28 | confirmPassword,
29 | } = signupData
30 | dispatch(signUp(accountType,
31 | firstName,
32 | lastName,
33 | email,
34 | password,
35 | confirmPassword,
36 | otp, navigate))
37 | }
38 |
39 | return (
40 |
41 | {loading ? (
42 |
45 | ) : (
46 |
47 |
48 | Verify Email
49 |
50 |
51 | A verification code has been sent to you. Enter the code below
52 |
53 |
80 |
81 |
82 |
83 | Back To Signup
84 |
85 |
86 |
dispatch(sendOtp(signupData.email))}
89 | >
90 |
91 | Resend it
92 |
93 |
94 |
95 | )}
96 |
97 | )
98 | }
99 |
100 | export default VerifyEmail
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
4 | theme: {
5 | fontFamily: {
6 | inter: ["Inter", "sans-serif"],
7 | "edu-sa": ["Edu SA Beginner", "cursive"],
8 | mono: ["Roboto Mono", "monospace"],
9 | },
10 | colors: {
11 | white: "#fff",
12 | black: "#000",
13 | transparent: "#ffffff00",
14 | richblack: {
15 | 5: "#F1F2FF",
16 | 25: "#DBDDEA",
17 | 50: "#C5C7D4",
18 | 100: "#AFB2BF",
19 | 200: "#999DAA",
20 | 300: "#838894",
21 | 400: "#6E727F",
22 | 500: "#585D69",
23 | 600: "#424854",
24 | 700: "#2C333F",
25 | 800: "#161D29",
26 | 900: "#000814",
27 | },
28 | richblue: {
29 | 5: "#ECF5FF",
30 | 25: "#C6D6E1",
31 | 50: "#A0B7C3",
32 | 100: "#7A98A6",
33 | 200: "#537988",
34 | 300: "#2D5A6A",
35 | 400: "#073B4C",
36 | 500: "#063544",
37 | 600: "#042E3B",
38 | 700: "#032833",
39 | 800: "#01212A",
40 | 900: "#001B22",
41 | },
42 | blue: {
43 | 5: "#EAF5FF",
44 | 25: "#B4DAEC",
45 | 50: "#7EC0D9",
46 | 100: "#47A5C5",
47 | 200: "#118AB2",
48 | 300: "#0F7A9D",
49 | 400: "#0C6A87",
50 | 500: "#0A5A72",
51 | 600: "#074B5D",
52 | 700: "#053B48",
53 | 800: "#022B32",
54 | 900: "#001B1D",
55 | },
56 | caribbeangreen: {
57 | 5: "#C1FFFD",
58 | 25: "#83F1DE",
59 | 50: "#44E4BF",
60 | 100: "#06D6A0",
61 | 200: "#05BF8E",
62 | 300: "#05A77B",
63 | 400: "#049069",
64 | 500: "#037957",
65 | 600: "#026144",
66 | 700: "#014A32",
67 | 800: "#01321F",
68 | 900: "#001B0D",
69 | },
70 | brown: {
71 | 5: "#FFF4C4",
72 | 25: "#FFE395",
73 | 50: "#FFD166",
74 | 100: "#E7BC5B",
75 | 200: "#CFA64F",
76 | 300: "#B89144",
77 | 400: "#A07C39",
78 | 500: "#88662D",
79 | 600: "#705122",
80 | 700: "#593C17",
81 | 800: "#41260B",
82 | 900: "#291100",
83 | },
84 | pink: {
85 | 5: "#FFF1F1",
86 | 25: "#FBC7D1",
87 | 50: "#F79CB0",
88 | 100: "#F37290",
89 | 200: "#EF476F",
90 | 300: "#D43D63",
91 | 400: "#BA3356",
92 | 500: "#9F294A",
93 | 600: "#841E3E",
94 | 700: "#691432",
95 | 800: "#4F0A25",
96 | 900: "#340019",
97 | },
98 | yellow: {
99 | 5: "#FFF970",
100 | 25: "#FFE83D",
101 | 50: "#FFD60A",
102 | 100: "#E7C009",
103 | 200: "#CFAB08",
104 | 300: "#B69507",
105 | 400: "#9E8006",
106 | 500: "#866A04",
107 | 600: "#6E5503",
108 | 700: "#553F02",
109 | 800: "#3D2A01",
110 | 900: "#251400",
111 | },
112 | "pure-greys": {
113 | 5: "#F9F9F9",
114 | 25: "#E2E2E2",
115 | 50: "#CCCCCC",
116 | 100: "#B5B5B5",
117 | 200: "#9E9E9E",
118 | 300: "#888888",
119 | 400: "#717171",
120 | 500: "#5B5B5B",
121 | 600: "#444444",
122 | 700: "#2D2D2D",
123 | 800: "#171717",
124 | 900: "#141414",
125 | },
126 | },
127 | extend: {
128 | maxWidth: {
129 | maxContent: "1260px",
130 | maxContentTab: "650px"
131 | },
132 | boxShadow: {
133 | 'custom': '0 1.5px rgba(255, 255, 255, 0.25)',
134 | },
135 | },
136 | },
137 | plugins: [],
138 | };
--------------------------------------------------------------------------------
/src/components/core/AboutPage/LearningGrid.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from '../HomePage/HighlightText';
3 | import CTAButton from "../../core/HomePage/Button";
4 |
5 | const LearningGridArray = [
6 | {
7 | order: -1,
8 | heading: "World-Class Learning for",
9 | highlightText: "Anyone, Anywhere",
10 | description:
11 | "Studynotion partners with more than 275+ leading universities and companies to bring flexible, affordable, job-relevant online learning to individuals and organizations worldwide.",
12 | BtnText: "Learn More",
13 | BtnLink: "/",
14 | },
15 | {
16 | order: 1,
17 | heading: "Curriculum Based on Industry Needs",
18 | description:
19 | "Save time and money! The Belajar curriculum is made to be easier to understand and in line with industry needs.",
20 | },
21 | {
22 | order: 2,
23 | heading: "Our Learning Methods",
24 | description:
25 | "Studynotion partners with more than 275+ leading universities and companies to bring",
26 | },
27 | {
28 | order: 3,
29 | heading: "Certification",
30 | description:
31 | "Studynotion partners with more than 275+ leading universities and companies to bring",
32 | },
33 | {
34 | order: 4,
35 | heading: `Rating "Auto-grading"`,
36 | description:
37 | "Studynotion partners with more than 275+ leading universities and companies to bring",
38 | },
39 | {
40 | order: 5,
41 | heading: "Ready to Work",
42 | description:
43 | "Studynotion partners with more than 275+ leading universities and companies to bring",
44 | },
45 | ];
46 |
47 |
48 | const LearningGrid = () => {
49 | return (
50 |
51 | {
52 | LearningGridArray.map( (card, index) => {
53 | return (
54 |
64 | {
65 | card.order < 0
66 | ? (
67 |
68 |
69 | {card.heading}
70 |
71 |
72 |
73 | {card.description}
74 |
75 |
76 |
77 | {card.BtnText}
78 |
79 |
80 |
81 | )
82 | : (
83 |
84 | {card.heading}
85 |
86 |
87 | {card.description}
88 |
89 |
)
90 | }
91 |
92 |
93 | )
94 | } )
95 | }
96 |
97 | )
98 | }
99 |
100 | export default LearningGrid
101 |
--------------------------------------------------------------------------------
/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 | showAllCourses,
11 | getCourseDetails,
12 | getFullCourseDetails,
13 | editCourse,
14 | getInstructorCourses,
15 | deleteCourse,
16 | } = require("../controllers/Course")
17 |
18 | const {
19 | updateCourseProgress
20 | } = require("../controllers/courseProgress");
21 |
22 | // Categories Controllers Import
23 | const {
24 | showAllCategories,
25 | createCategory,
26 | categoryPageDetails,
27 | } = require("../controllers/Categories")
28 |
29 | // Sections Controllers Import
30 | const {
31 | createSection,
32 | updateSection,
33 | deleteSection,
34 | } = require("../controllers/Section")
35 |
36 | // Sub-Sections Controllers Import
37 | const {
38 | createSubSection,
39 | updateSubSection,
40 | deleteSubSection,
41 | } = require("../controllers/SubSection")
42 |
43 | // Rating Controllers Import
44 | const {
45 | createRating,
46 | getAverageRating,
47 | getAllRating,
48 | } = require("../controllers/RatingAndReview")
49 |
50 | // Importing Middlewares
51 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth")
52 |
53 | // ********************************************************************************************************
54 | // Course routes
55 | // ********************************************************************************************************
56 |
57 | // Courses can Only be Created by Instructors
58 | router.post("/createCourse", auth, isInstructor, createCourse)
59 | //Add a Section to a Course
60 | router.post("/addSection", auth, isInstructor, createSection)
61 | // Update a Section
62 | router.post("/updateSection", auth, isInstructor, updateSection)
63 | // Delete a Section
64 | router.post("/deleteSection", auth, isInstructor, deleteSection)
65 | // Edit Sub Section
66 | router.post("/updateSubSection", auth, isInstructor, updateSubSection)
67 | // Delete Sub Section
68 | router.post("/deleteSubSection", auth, isInstructor, deleteSubSection)
69 | // Add a Sub Section to a Section
70 | router.post("/addSubSection", auth, isInstructor, createSubSection)
71 | // Get all Registered Courses
72 | router.get("/getAllCourses", showAllCourses)
73 | // Get Details for a Specific Courses
74 | router.post("/getCourseDetails", getCourseDetails)
75 | // Get Details for a Specific Courses
76 | router.post("/getFullCourseDetails", auth, getFullCourseDetails)
77 | // Edit Course routes
78 | router.post("/editCourse", auth, isInstructor, editCourse)
79 | // Get all Courses Under a Specific Instructor
80 | router.get("/getInstructorCourses", auth, isInstructor, getInstructorCourses)
81 | // Delete a Course
82 | router.delete("/deleteCourse", deleteCourse)
83 | router.post("/updateCourseProgress", auth, isStudent, updateCourseProgress);
84 |
85 | // ********************************************************************************************************
86 | // Category routes (Only by Admin)
87 | // ********************************************************************************************************
88 | // Category can Only be Created by Admin
89 | // TODO: Put IsAdmin Middleware here
90 | router.post("/createCategory", auth, isAdmin, createCategory)
91 | router.get("/showAllCategories", showAllCategories)
92 | router.post("/getCategoryPageDetails", categoryPageDetails)
93 |
94 | // ********************************************************************************************************
95 | // Rating and Review
96 | // ********************************************************************************************************
97 | router.post("/createRating", auth, isStudent, createRating)
98 | router.get("/getAverageRating", getAverageRating)
99 | router.get("/getReviews", getAllRating)
100 |
101 | module.exports = router
--------------------------------------------------------------------------------
/src/services/operations/SettingsAPI.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-hot-toast"
2 |
3 | import { setUser } from "../../slices/profileSlice"
4 | import { apiConnector } from "../apiconnector"
5 | import { settingsEndpoints } from "../apis"
6 | import { logout } from "./authAPI"
7 |
8 | const {
9 | UPDATE_DISPLAY_PICTURE_API,
10 | UPDATE_PROFILE_API,
11 | CHANGE_PASSWORD_API,
12 | DELETE_PROFILE_API,
13 | } = settingsEndpoints
14 |
15 | export function updateDisplayPicture(token, formData) {
16 | return async (dispatch) => {
17 | const toastId = toast.loading("Loading...")
18 | try {
19 | const response = await apiConnector(
20 | "PUT",
21 | UPDATE_DISPLAY_PICTURE_API,
22 | formData,
23 | {
24 | "Content-Type": "multipart/form-data",
25 | Authorization: `Bearer ${token}`,
26 | }
27 | )
28 | console.log(
29 | "UPDATE_DISPLAY_PICTURE_API API RESPONSE............",
30 | response
31 | )
32 |
33 | if (!response.data.success) {
34 | throw new Error(response.data.message)
35 | }
36 | toast.success("Display Picture Updated Successfully")
37 | dispatch(setUser(response.data.data))
38 | } catch (error) {
39 | console.log("UPDATE_DISPLAY_PICTURE_API API ERROR............", error)
40 | toast.error("Could Not Update Display Picture")
41 | }
42 | toast.dismiss(toastId)
43 | }
44 | }
45 |
46 | export function updateProfile(token, formData) {
47 | return async (dispatch) => {
48 | const toastId = toast.loading("Loading...")
49 | try {
50 | const response = await apiConnector("PUT", UPDATE_PROFILE_API, formData, {
51 | Authorization: `Bearer ${token}`,
52 | })
53 | console.log("UPDATE_PROFILE_API API RESPONSE............", response)
54 |
55 | if (!response.data.success) {
56 | throw new Error(response.data.message)
57 | }
58 | const userImage = response.data.updatedUserDetails.image
59 | ? response.data.updatedUserDetails.image
60 | : `https://api.dicebear.com/5.x/initials/svg?seed=${response.data.updatedUserDetails.firstName} ${response.data.updatedUserDetails.lastName}`
61 | dispatch(
62 | setUser({ ...response.data.updatedUserDetails, image: userImage })
63 | )
64 | toast.success("Profile Updated Successfully")
65 | } catch (error) {
66 | console.log("UPDATE_PROFILE_API API ERROR............", error)
67 | toast.error("Could Not Update Profile")
68 | }
69 | toast.dismiss(toastId)
70 | }
71 | }
72 |
73 | export async function changePassword(token, formData) {
74 | const toastId = toast.loading("Loading...")
75 | try {
76 | const response = await apiConnector("POST", CHANGE_PASSWORD_API, formData, {
77 | Authorization: `Bearer ${token}`,
78 | })
79 | console.log("CHANGE_PASSWORD_API API RESPONSE............", response)
80 |
81 | if (!response.data.success) {
82 | throw new Error(response.data.message)
83 | }
84 | toast.success("Password Changed Successfully")
85 | } catch (error) {
86 | console.log("CHANGE_PASSWORD_API API ERROR............", error)
87 | toast.error(error.response.data.message)
88 | }
89 | toast.dismiss(toastId)
90 | }
91 |
92 | export function deleteProfile(token, navigate) {
93 | return async (dispatch) => {
94 | const toastId = toast.loading("Loading...")
95 | try {
96 | const response = await apiConnector("DELETE", DELETE_PROFILE_API, null, {
97 | Authorization: `Bearer ${token}`,
98 | })
99 | console.log("DELETE_PROFILE_API API RESPONSE............", response)
100 |
101 | if (!response.data.success) {
102 | throw new Error(response.data.message)
103 | }
104 | toast.success("Profile Deleted Successfully")
105 | dispatch(logout(navigate))
106 | } catch (error) {
107 | console.log("DELETE_PROFILE_API API ERROR............", error)
108 | toast.error("Could Not Delete Profile")
109 | }
110 | toast.dismiss(toastId)
111 | }
112 | }
--------------------------------------------------------------------------------
/src/components/common/ReviewSlider.js:
--------------------------------------------------------------------------------
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/navigation';
10 | import "swiper/css/pagination"
11 | import "../../App.css"
12 | // Icons
13 | import { FaStar } from "react-icons/fa"
14 | // Import required modules
15 | import { Autoplay, FreeMode, Pagination, Navigation } from "swiper/modules"
16 |
17 | // Get apiFunction and the endpoint
18 | import { apiConnector } from "../../services/apiconnector"
19 | import { ratingsEndpoints } from "../../services/apis"
20 |
21 | function ReviewSlider() {
22 | const [reviews, setReviews] = useState([])
23 | const truncateWords = 15
24 |
25 | useEffect(() => {
26 | ;(async () => {
27 | const { data } = await apiConnector(
28 | "GET",
29 | ratingsEndpoints.REVIEWS_DETAILS_API
30 | )
31 | if (data?.success) {
32 | setReviews(data?.data)
33 | }
34 | })()
35 | }, [])
36 |
37 | // console.log(reviews)
38 |
39 | return (
40 |
41 |
42 |
55 | {reviews.map((review, i) => {
56 | return (
57 |
58 |
59 |
60 |
69 |
70 |
{`${review?.user?.firstName} ${review?.user?.lastName}`}
71 |
72 | {review?.course?.courseName}
73 |
74 |
75 |
76 |
77 | {review?.review.split(" ").length > truncateWords
78 | ? `${review?.review
79 | .split(" ")
80 | .slice(0, truncateWords)
81 | .join(" ")} ...`
82 | : `${review?.review}`}
83 |
84 |
85 |
86 | {review.rating.toFixed(1)}
87 |
88 | }
95 | fullIcon={ }
96 | />
97 |
98 |
99 |
100 | )
101 | })}
102 | {/* Slide 1 */}
103 |
104 |
105 |
106 | )
107 | }
108 |
109 | export default ReviewSlider
110 |
--------------------------------------------------------------------------------
/src/components/core/ViewCourse/CourseReviewModal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useForm } from 'react-hook-form';
3 | import { useSelector } from 'react-redux'
4 | import IconBtn from '../../common/IconBtn';
5 | import { createRating } from '../../../services/operations/courseDetailsAPI';
6 | import ReactStars from "react-rating-stars-component";
7 | import { RxCross2 } from "react-icons/rx"
8 | const CourseReviewModal = ({setReviewModal}) => {
9 | const {user} = useSelector((state) => state.profile )
10 | const {token} = useSelector((state)=> state.auth)
11 | const {courseEntireData} = useSelector((state)=> state.viewCourse);
12 |
13 | const {
14 | register,
15 | setValue,
16 | getValues,
17 | handleSubmit,
18 | formState:{errors}
19 | } = useForm()
20 |
21 | useEffect(()=>{
22 | setValue("courseRating",0);
23 | setValue("courseExperience","")
24 | },[])
25 |
26 | const ratingChanged = (newRating) => {
27 | setValue("courseRating", newRating)
28 | }
29 |
30 | const onSubmit = async (data)=>{
31 | await createRating({
32 | courseId: courseEntireData._id,
33 | rating: data.courseRating,
34 | review: data.courseExperience
35 | }, token)
36 | setReviewModal(false)
37 | }
38 | return (
39 |
40 |
41 | {/* Modal Header */}
42 |
43 |
Add Review
44 |
setReviewModal(false)}>
45 |
46 |
47 |
48 | {/* Modal Body */}
49 |
50 |
51 |
56 |
57 |
58 | {user?.firstName} {user?.lastName}
59 |
60 |
Posting Publicly
61 |
62 |
63 |
102 |
103 |
104 |
105 | )
106 | }
107 |
108 | export default CourseReviewModal
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Add Course/Upload.js:
--------------------------------------------------------------------------------
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 |
63 | {label} {!viewData && * }
64 |
65 |
70 | {previewSource ? (
71 |
72 | {!video ? (
73 |
78 | ) : (
79 |
80 | )}
81 | {!viewData && (
82 |
{
85 | setPreviewSource("")
86 | setSelectedFile(null)
87 | setValue(name, null)
88 | }}
89 | className="mt-3 text-richblack-400 underline"
90 | >
91 | Cancel
92 |
93 | )}
94 |
95 | ) : (
96 |
100 |
101 |
102 |
103 |
104 |
105 | Drag and drop an {!video ? "image" : "video"}, or click to{" "}
106 | Browse a
107 | file
108 |
109 |
110 | Aspect ratio 16:9
111 | Recommended size 1024x576
112 |
113 |
114 | )}
115 |
116 | {errors[name] && (
117 |
118 | {label} is required
119 |
120 | )}
121 |
122 | )
123 | }
--------------------------------------------------------------------------------
/src/components/core/Course/CourseDetailsCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { json, useNavigate } from 'react-router-dom';
4 | import copy from 'copy-to-clipboard';
5 | import { toast } from 'react-hot-toast';
6 | import { ACCOUNT_TYPE } from '../../../utils/constants';
7 | import { addToCart } from '../../../slices/cartSlice';
8 | import { BiSolidRightArrow } from 'react-icons/bi';
9 | const CourseDetailsCard = ({course, setConfirmationModal, handleBuyCourse}) => {
10 | const { cart } = useSelector((state) => state.cart)
11 | const {user} = useSelector((state)=>state.profile);
12 | const {token} = useSelector((state)=>state.auth);
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | // console.log("Course Instruction type is", typeof(course?.instructions))
16 | const {
17 | thumbnail: ThumbnailImage,
18 | price: CurrentPrice,
19 |
20 | } = course;
21 |
22 | const handleAddToCart = () => {
23 | if (user && user?.accountType === ACCOUNT_TYPE.INSTRUCTOR) {
24 | toast.error("Instructor cannot buy the course")
25 | return
26 | }
27 | if (token) {
28 | // console.log("dispatching add to cart")
29 | dispatch(addToCart(course));
30 | // console.log("CART IN SLICE IS", cart)
31 | return;
32 | }
33 | setConfirmationModal({
34 | text1:"you are not logged in",
35 | text2:"Please login to add to cart",
36 | btn1text:"login",
37 | btn2Text:"cancel",
38 | btn1Handler:()=>navigate("/login"),
39 | btn2Handler: ()=> setConfirmationModal(null),
40 | })
41 | }
42 |
43 | const handleShare = () => {
44 | copy(window.location.href);
45 | toast.success("Link Copied to Clipboard")
46 | }
47 |
48 | return (
49 |
50 |
55 |
56 |
57 | Rs. {CurrentPrice}
58 |
59 |
60 | navigate("/dashboard/enrolled-courses")
64 | : handleBuyCourse
65 | }
66 | >
67 | {
68 | user && course?.studentsEnrolled.includes(user?._id) ? "Go to Course ": "Buy Now"
69 | }
70 |
71 |
72 | {
73 | (!course?.studentsEnrolled.includes(user?._id)) && (
74 |
75 | Add to Cart
76 |
77 | )
78 | }
79 |
80 |
81 |
82 |
83 | 30-Day Money-Back Guarantee
84 |
85 |
86 |
87 |
88 | This Course Includes:
89 |
90 |
91 | {
92 | JSON.parse(course?.instructions).map((item, index)=> (
93 |
94 |
95 | {item}
96 |
97 | ))
98 | }
99 |
100 |
101 |
102 |
106 | Share
107 |
108 |
109 |
110 |
111 | )
112 | }
113 |
114 | export default CourseDetailsCard
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/UpdatePassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { useForm } from "react-hook-form"
3 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai"
4 | import { useSelector } from "react-redux"
5 | import { useNavigate } from "react-router-dom"
6 |
7 | import { changePassword } from "../../../../services/operations/SettingsAPI"
8 | import IconBtn from "../../../common/IconBtn"
9 |
10 | export default function UpdatePassword() {
11 | const { token } = useSelector((state) => state.auth)
12 | const navigate = useNavigate()
13 |
14 | const [showOldPassword, setShowOldPassword] = useState(false)
15 | const [showNewPassword, setShowNewPassword] = useState(false)
16 |
17 | const {
18 | register,
19 | handleSubmit,
20 | formState: { errors },
21 | } = useForm()
22 |
23 | const submitPasswordForm = async (data) => {
24 | // console.log("password Data - ", data)
25 | try {
26 | await changePassword(token, data)
27 | } catch (error) {
28 | console.log("ERROR MESSAGE - ", error.message)
29 | }
30 | }
31 |
32 | return (
33 | <>
34 |
35 |
36 |
Password
37 |
38 |
39 |
40 | Current Password
41 |
42 |
50 |
setShowOldPassword((prev) => !prev)}
52 | className="absolute right-3 top-[38px] z-[10] cursor-pointer"
53 | >
54 | {showOldPassword ? (
55 |
56 | ) : (
57 |
58 | )}
59 |
60 | {errors.oldPassword && (
61 |
62 | Please enter your Current Password.
63 |
64 | )}
65 |
66 |
67 |
68 | New Password
69 |
70 |
78 |
setShowNewPassword((prev) => !prev)}
80 | className="absolute right-3 top-[38px] z-[10] cursor-pointer"
81 | >
82 | {showNewPassword ? (
83 |
84 | ) : (
85 |
86 | )}
87 |
88 | {errors.newPassword && (
89 |
90 | Please enter your New Password.
91 |
92 | )}
93 |
94 |
95 |
96 |
97 | {
99 | navigate("/dashboard/my-profile")
100 | }}
101 | className="cursor-pointer rounded-md bg-richblack-700 py-2 px-5 font-semibold text-richblack-50"
102 | >
103 | Cancel
104 |
105 |
106 |
107 |
108 | >
109 | )
110 | }
--------------------------------------------------------------------------------
/Server/controllers/SubSection.js:
--------------------------------------------------------------------------------
1 | const SubSection = require('../models/SubSection');
2 | const Section = require("../models/Section");
3 | const { uploadImageToCloudinary } = require("../utils/imageUploader");
4 |
5 | exports.createSubSection = async (req,res) =>{
6 | try {
7 | const {sectionId,title, timeDuration, description } = req.body;
8 |
9 | const video = req.files.video;
10 |
11 | if(!sectionId || !title || !description || !video) {
12 | return res.status(400).json({
13 | success:false,
14 | message:'All fields are required',
15 | });
16 | }
17 |
18 | const uploadDetails = await uploadImageToCloudinary(video, process.env.FOLDER_NAME);
19 |
20 | const newSubSection = await SubSection.create({
21 | title,
22 | timeDuration: `${uploadDetails.duration}`,
23 | description,
24 | videoUrl:uploadDetails.secure_url
25 | })
26 |
27 | const updatedSection = await Section.findByIdAndUpdate(sectionId, { $push: {subSection: newSubSection._id}},{new:true}).populate("subSection");
28 |
29 | return res.status(200).json({
30 | success:true,
31 | message:'SubSection created successfully',
32 | updatedSection
33 | })
34 | } catch (error) {
35 | console.error(error);
36 | return res.status(500).json({
37 | success:false,
38 | message:'Failed to create SubSection',
39 | error: error.message,
40 | })
41 | }
42 | }
43 |
44 | exports.updateSubSection = async (req, res) => {
45 | try {
46 | const { sectionId,subSectionId, title, description } = req.body
47 | const subSection = await SubSection.findById(subSectionId)
48 |
49 | if (!subSection) {
50 | return res.status(404).json({
51 | success: false,
52 | message: "SubSection not found",
53 | })
54 | }
55 |
56 | if (title !== undefined) {
57 | subSection.title = title
58 | }
59 |
60 | if (description !== undefined) {
61 | subSection.description = description
62 | }
63 | if (req.files && req.files.video !== undefined) {
64 | const video = req.files.video
65 | const uploadDetails = await uploadImageToCloudinary(
66 | video,
67 | process.env.FOLDER_NAME
68 | )
69 | subSection.videoUrl = uploadDetails.secure_url
70 | subSection.timeDuration = `${uploadDetails.duration}`
71 | }
72 |
73 | await subSection.save()
74 |
75 | const updatedSection = await Section.findById(sectionId).populate("subSection")
76 |
77 |
78 | return res.json({
79 | success: true,
80 | data:updatedSection,
81 | message: "Section updated successfully",
82 | })
83 | } catch (error) {
84 | console.error(error)
85 | return res.status(500).json({
86 | success: false,
87 | message: "An error occurred while updating the section",
88 | })
89 | }
90 | }
91 |
92 | exports.deleteSubSection = async (req,res) =>{
93 | try {
94 |
95 | const {subSectionId,sectionId } = req.body;
96 | await Section.findByIdAndUpdate(
97 | { _id: sectionId },
98 | {
99 | $pull: {
100 | subSection: subSectionId,
101 | },
102 | }
103 | )
104 |
105 | if(!subSectionId) {
106 | return res.status(400).json({
107 | success:false,
108 | message:'SubSection Id to be deleted is required',
109 | });
110 | }
111 |
112 |
113 | const subSection = await SubSection.findByIdAndDelete({ _id: subSectionId })
114 |
115 | if (!subSection) {
116 | return res
117 | .status(404)
118 | .json({ success: false, message: "SubSection not found" })
119 | }
120 |
121 | const updatedSection = await Section.findById(sectionId).populate("subSection")
122 |
123 | return res.json({
124 | success: true,
125 | data:updatedSection,
126 | message: "SubSection deleted successfully",
127 | })
128 | } catch (error) {
129 | console.error(error);
130 | return res.status(500).json({
131 | success:false,
132 | message:'Failed to delete SubSection',
133 | error: error.message,
134 | })
135 | }
136 | }
--------------------------------------------------------------------------------