"
4 | labels: ["priority: high"]
5 | body:
6 | - type: textarea
7 | attributes:
8 | label: Description
9 | description: A brief description of the issue or bug you are facing, also include what you tried and what didn't work.
10 | validations:
11 | required: false
12 | - type: textarea
13 | attributes:
14 | label: Screenshots
15 | description: Please include screenshots if required
16 | validations:
17 | required: false
18 | - type: textarea
19 | attributes:
20 | label: Any additional Details?
21 | description: Please Provide Key Details, if required (Goals and objectives,Deliverables,Dependencies)
22 | validations:
23 | required: false
24 | - type: textarea
25 | attributes:
26 | label: Deadline
27 | description: Specify the task deadline
28 | validations:
29 | required: true
30 |
31 | - type: markdown
32 | attributes:
33 | value: "Stay you can start working on it by taking latest pull from main branch
34 |
35 | Please read the contributors guidelines Contributing Guidelines.
36 |
37 | Please follow the code of conduct Code Of Conduct."
38 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Related Issue
2 | [Cite any related issue(s) this pull request addresses. If none, simply state “None”]
3 |
4 | ## Description
5 | [Please include a brief description of the changes or features added]
6 |
7 | ## Type of PR
8 |
9 | - [ ] Bug fix
10 | - [ ] Feature enhancement
11 | - [ ] Documentation update
12 | - [ ] Other (specify): _______________
13 |
14 | ## Screenshots / videos (if applicable)
15 | [Attach any relevant screenshots or videos demonstrating the changes]
16 |
17 | ## Checklist:
18 | - [ ] I have performed a self-review of my code
19 | - [ ] I have read and followed the Contribution Guidelines.
20 | - [ ] I have tested the changes thoroughly before submitting this pull request.
21 | - [ ] I have provided relevant issue numbers, screenshots, and videos after making the changes.
22 | - [ ] I have commented my code, particularly in hard-to-understand areas.
23 |
24 |
25 | ## Additional context:
26 | [Include any additional information or context that might be helpful for reviewers.]
27 |
--------------------------------------------------------------------------------
/.github/workflows/autocomment-iss-close.yml:
--------------------------------------------------------------------------------
1 | name: Comment on Issue Close
2 |
3 | on:
4 | issues:
5 | types: [closed]
6 |
7 | jobs:
8 | greet-on-close:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | issues: write
12 | steps:
13 | - name: Greet User
14 | uses: actions/github-script@v5
15 | with:
16 | github-token: ${{ secrets.GITHUB_TOKEN }}
17 | script: |
18 | const issue = context.payload.issue;
19 | const issueCreator = issue.user.login;
20 | const issueNumber = issue.number;
21 |
22 | const greetingMessage = `Hello @${issueCreator}! Your issue #${issueNumber} has been closed. Thank you for your contribution!`;
23 |
24 | github.rest.issues.createComment({
25 | owner: context.repo.owner,
26 | repo: context.repo.repo,
27 | issue_number: issueNumber,
28 | body: greetingMessage
29 | });
30 |
--------------------------------------------------------------------------------
/.github/workflows/close-old-issue.yml:
--------------------------------------------------------------------------------
1 | name: Close Old Issues
2 | on:
3 | schedule:
4 | - cron: "0 0 * * *"
5 |
6 | jobs:
7 | close-issues:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - name: Checkout Repository
12 | uses: actions/checkout@v4
13 |
14 | - name: Close Old Issues
15 | run: |
16 | open_issues=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
17 | "https://api.github.com/repos/${{ github.repository }}/issues?state=open" \
18 | | jq -r '.[] | .number')
19 | for issue in $open_issues; do
20 | # Get the last updated timestamp of the issue
21 | last_updated=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
22 | "https://api.github.com/repos/${{ github.repository }}/issues/$issue" \
23 | | jq -r '.updated_at')
24 | days_since_update=$(( ( $(date +%s) - $(date -d "$last_updated" +%s) ) / 86400 ))
25 | if [ $days_since_update -gt 30 ]; then
26 | curl -s -X PATCH -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
27 | -H "Accept: application/vnd.github.v3+json" \
28 | -d '{"state":"closed"}' \
29 | "https://api.github.com/repos/${{ github.repository }}/issues/$issue"
30 |
31 | # Add a comment explaining when the issue will be closed
32 | curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
33 | -H "Accept: application/vnd.github.v3+json" \
34 | -d '{"body":"This issue has been automatically closed because it has been inactive for more than 30 days. If you believe this is still relevant, feel free to reopen it or create a new one. Thank you!"}' \
35 | "https://api.github.com/repos/${{ github.repository }}/issues/$issue/comments"
36 | fi
37 | done
38 |
--------------------------------------------------------------------------------
/.github/workflows/close-old-pr.yml:
--------------------------------------------------------------------------------
1 | name: Close Stale PRs
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *' # Runs daily at midnight
6 | pull_request:
7 | types:
8 | - opened
9 | - reopened
10 | - synchronize
11 |
12 | permissions:
13 | pull-requests: write
14 | issues: write
15 |
16 | jobs:
17 | close_stale_prs:
18 | runs-on: ubuntu-latest
19 | permissions:
20 | pull-requests: write
21 |
22 | steps:
23 | - uses: actions/stale@v7
24 | with:
25 | repo-token: ${{ secrets.GITHUB_TOKEN }}
26 | stale-pr-message: 'This PR has been automatically closed due to inactivity from the owner for 15 days.'
27 | days-before-pr-stale: 15
28 | days-before-pr-close: 0
29 | exempt-pr-author: false
30 | exempt-pr-labels: ''
31 | only-labels: ''
32 | operations-per-run: 30
33 | remove-stale-when-updated: true
34 | debug-only: false
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /server/node_modules
3 |
4 | .env
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.analysis.extraPaths": [
3 | "./src/pages/import "
4 | ]
5 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Muskan Sahu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/images/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/images/architecture.png
--------------------------------------------------------------------------------
/images/mainpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/images/mainpage.png
--------------------------------------------------------------------------------
/images/schema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/images/schema.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tailwind-css-starter-pack",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.13.0",
7 | "@emotion/styled": "^11.13.0",
8 | "@mui/icons-material": "^5.16.4",
9 | "@mui/material": "^5.16.4",
10 | "@ramonak/react-progress-bar": "^5.0.3",
11 | "@reduxjs/toolkit": "^1.9.5",
12 | "axios": "^1.4.0",
13 | "bcrypt": "^5.1.1",
14 | "chart.js": "^4.3.0",
15 | "concurrently": "^8.0.1",
16 | "copy-to-clipboard": "^3.3.3",
17 | "dotenv": "^16.4.5",
18 | "framer-motion": "^11.3.19",
19 | "json-server": "^1.0.0-beta.0",
20 | "react": "^18.2.0",
21 | "react-chartjs-2": "^5.2.0",
22 | "react-dom": "^18.2.0",
23 | "react-dropzone": "^14.2.3",
24 | "react-hook-form": "^7.44.2",
25 | "react-hot-toast": "^2.4.1",
26 | "react-icons": "^4.12.0",
27 | "react-markdown": "^8.0.7",
28 | "react-otp-input": "^3.0.2",
29 | "react-rating-stars-component": "^2.2.0",
30 | "react-redux": "^8.0.5",
31 | "react-router-dom": "^6.11.2",
32 | "react-scripts": "^5.0.1",
33 | "react-spinners": "^0.13.8",
34 | "react-super-responsive-table": "^5.2.1",
35 | "react-type-animation": "^3.0.1",
36 | "redux-toolkit": "^1.1.2",
37 | "styled-components": "^6.1.11",
38 | "swiper": "^11.1.4",
39 | "video-react": "^0.16.0",
40 | "web-vitals": "^2.1.4"
41 | },
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/thepranaygupta/react-tailwind-css-starter-pack.git"
45 | },
46 | "author": "Pranay Gupta",
47 | "bugs": {
48 | "url": "https://github.com/thepranaygupta/react-tailwind-css-starter-pack/issues"
49 | },
50 | "scripts": {
51 | "start": "react-scripts start",
52 | "build": "react-scripts build",
53 | "eject": "react-scripts eject",
54 | "server": "cd server && npm run dev",
55 | "dev": "concurrently -n \"client,server\" -c \"bgBlue,bgYellow\" \"npm start\" \"npm run server\""
56 | },
57 | "eslintConfig": {
58 | "extends": [
59 | "react-app",
60 | "react-app/jest"
61 | ]
62 | },
63 | "browserslist": {
64 | "production": [
65 | ">0.2%",
66 | "not dead",
67 | "not op_mini all"
68 | ],
69 | "development": [
70 | "last 1 chrome version",
71 | "last 1 firefox version",
72 | "last 1 safari version"
73 | ]
74 | },
75 | "devDependencies": {
76 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
77 | "autoprefixer": "^10.4.19",
78 | "postcss": "^8.4.38",
79 | "tailwindcss": "^3.4.4"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | StudyNotion
9 |
18 |
22 |
23 |
24 |
25 | You need to enable JavaScript to run this app.
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/server/config/cloudinary.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require("cloudinary").v2; //! Cloudinary is being required
2 |
3 | exports.cloudinaryConnect = () => {
4 | try {
5 | cloudinary.config({
6 | //! ######## Configuring the Cloudinary to Upload MEDIA ########
7 | cloud_name: process.env.CLOUD_NAME,
8 | api_key: process.env.API_KEY,
9 | api_secret: process.env.API_SECRET,
10 | });
11 | } catch (error) {
12 | console.log(error);
13 | }
14 | };
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | require("dotenv").config();
3 |
4 | exports.connect = () => {
5 | mongoose.connect(process.env.MONGODB_URL, {
6 | useNewUrlParser: true,
7 | useUnifiedTopology:true,
8 | })
9 | .then(() => console.log("DB Connected Successfully"))
10 | .catch( (error) => {
11 | console.log("DB Connection Failed");
12 | console.error(error);
13 | process.exit(1);
14 | } )
15 | };
--------------------------------------------------------------------------------
/server/config/razorpay.js:
--------------------------------------------------------------------------------
1 | const Razorpay = require("razorpay");
2 |
3 |
4 | exports.instance = new Razorpay({
5 | key_id: process.env.RAZORPAY_KEY,
6 | key_secret: process.env.RAZORPAY_SECRET,
7 | });
--------------------------------------------------------------------------------
/server/controllers/ContactUs.js:
--------------------------------------------------------------------------------
1 | const { contactUsEmail } = require("../mail/templates/contactFormRes")
2 | const mailSender = require("../utils/mailSender")
3 |
4 | exports.contactUsController = async (req, res) => {
5 | const { email, firstname, lastname, message, phoneNo, countrycode } = req.body
6 | console.log(req.body)
7 | try {
8 | const emailRes = await mailSender(
9 | email,
10 | "Your Data send successfully",
11 | contactUsEmail(email, firstname, lastname, message, phoneNo, countrycode)
12 | )
13 | console.log("Email Res ", emailRes)
14 | return res.json({
15 | success: true,
16 | message: "Email send successfully",
17 | })
18 | } catch (error) {
19 | console.log("Error", error)
20 | console.log("Error message :", error.message)
21 | return res.json({
22 | success: false,
23 | message: "Something went wrong...",
24 | })
25 | }
26 | }
--------------------------------------------------------------------------------
/server/controllers/ResetPassword.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/User");
2 | const mailSender = require("../utils/mailSender");
3 | const bcrypt = require("bcryptjs");
4 | const crypto = require("crypto");
5 |
6 | exports.resetPasswordToken = async (req, res) => {
7 | try {
8 | const email = req.body.email;
9 | const user = await User.findOne({ email: email });
10 | if (!user) {
11 | return res.json({
12 | success: false,
13 | message: `This Email: ${email} is not Registered With Us Enter a Valid Email `,
14 | });
15 | }
16 | const token = crypto.randomBytes(20).toString("hex");
17 |
18 | const updatedDetails = await User.findOneAndUpdate(
19 | { email: email },
20 | {
21 | token: token,
22 | resetPasswordExpires: Date.now() + 3600000,
23 | },
24 | { new: true }
25 | );
26 | console.log("DETAILS", updatedDetails);
27 |
28 | const url = `http://localhost:3000/update-password/${token}`;
29 |
30 | await mailSender(
31 | email,
32 | "Password Reset",
33 | `Your Link for email verification is ${url}. Please click this url to reset your password.`
34 | );
35 |
36 | res.json({
37 | success: true,
38 | message:
39 | "Email Sent Successfully, Please Check Your Email to Continue Further",
40 | });
41 | } catch (error) {
42 | return res.json({
43 | error: error.message,
44 | success: false,
45 | message: `Some Error in Sending the Reset Message`,
46 | });
47 | }
48 | };
49 |
50 | exports.resetPassword = async (req, res) => {
51 | try {
52 | const { password, confirmPassword, token } = req.body;
53 |
54 | if (confirmPassword !== password) {
55 | return res.json({
56 | success: false,
57 | message: "Password and Confirm Password Does not Match",
58 | });
59 | }
60 | const userDetails = await User.findOne({ token: token });
61 | if (!userDetails) {
62 | return res.json({
63 | success: false,
64 | message: "Token is Invalid",
65 | });
66 | }
67 | if (!(userDetails.resetPasswordExpires > Date.now())) {
68 | return res.status(403).json({
69 | success: false,
70 | message: `Token is Expired, Please Regenerate Your Token`,
71 | });
72 | }
73 | const encryptedPassword = await bcrypt.hash(password, 10);
74 | await User.findOneAndUpdate(
75 | { token: token },
76 | { password: encryptedPassword },
77 | { new: true }
78 | );
79 | res.json({
80 | success: true,
81 | message: `Password Reset Successful`,
82 | });
83 | } catch (error) {
84 | return res.json({
85 | error: error.message,
86 | success: false,
87 | message: `Some Error in Updating the Password`,
88 | });
89 | }
90 | };
--------------------------------------------------------------------------------
/server/controllers/Section.js:
--------------------------------------------------------------------------------
1 | const Section = require("../models/Section");
2 | const Course = require("../models/Course");
3 | const SubSection = require("../models/SubSection");
4 | // CREATE a new section
5 | exports.createSection = async (req, res) => {
6 | try {
7 | // Extract the required properties from the request body
8 | const { sectionName, courseId } = req.body;
9 |
10 | // Validate the input
11 | if (!sectionName || !courseId) {
12 | return res.status(400).json({
13 | success: false,
14 | message: "Missing required properties",
15 | });
16 | }
17 |
18 | // Create a new section with the given name
19 | const newSection = await Section.create({ sectionName });
20 |
21 | // Add the new section to the course's content array
22 | const updatedCourse = await Course.findByIdAndUpdate(
23 | courseId,
24 | {
25 | $push: {
26 | courseContent: newSection._id,
27 | },
28 | },
29 | { new: true }
30 | )
31 | .populate({
32 | path: "courseContent",
33 | populate: {
34 | path: "subSection",
35 | },
36 | })
37 | .exec();
38 |
39 | // Return the updated course object in the response
40 | res.status(200).json({
41 | success: true,
42 | message: "Section created successfully",
43 | updatedCourse,
44 | });
45 | } catch (error) {
46 | // Handle errors
47 | res.status(500).json({
48 | success: false,
49 | message: "Internal server error",
50 | error: error.message,
51 | });
52 | }
53 | };
54 |
55 | // UPDATE a section
56 | exports.updateSection = async (req, res) => {
57 | try {
58 | const { sectionName, sectionId,courseId } = req.body;
59 | const section = await Section.findByIdAndUpdate(
60 | sectionId,
61 | { sectionName },
62 | { new: true }
63 | );
64 |
65 | const course = await Course.findById(courseId)
66 | .populate({
67 | path:"courseContent",
68 | populate:{
69 | path:"subSection",
70 | },
71 | })
72 | .exec();
73 |
74 | res.status(200).json({
75 | success: true,
76 | message: section,
77 | data:course,
78 | });
79 | } catch (error) {
80 | console.error("Error updating section:", error);
81 | res.status(500).json({
82 | success: false,
83 | message: "Internal server error",
84 | });
85 | }
86 | };
87 |
88 | // DELETE a section
89 | exports.deleteSection = async (req, res) => {
90 | try {
91 |
92 | const { sectionId, courseId } = req.body;
93 | await Course.findByIdAndUpdate(courseId, {
94 | $pull: {
95 | courseContent: sectionId,
96 | }
97 | })
98 | const section = await Section.findById(sectionId);
99 | console.log(sectionId, courseId);
100 | if(!section) {
101 | return res.status(404).json({
102 | success:false,
103 | message:"Section not Found",
104 | })
105 | }
106 |
107 | //delete sub section
108 | await SubSection.deleteMany({_id: {$in: section.subSection}});
109 |
110 | await Section.findByIdAndDelete(sectionId);
111 |
112 | //find the updated course and return
113 | const course = await Course.findById(courseId).populate({
114 | path:"courseContent",
115 | populate: {
116 | path: "subSection"
117 | }
118 | })
119 | .exec();
120 |
121 | res.status(200).json({
122 | success:true,
123 | message:"Section deleted",
124 | data:course
125 | });
126 | } catch (error) {
127 | console.error("Error deleting section:", error);
128 | res.status(500).json({
129 | success: false,
130 | message: "Internal server error",
131 | });
132 | }
133 | };
--------------------------------------------------------------------------------
/server/controllers/courseProgress.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 | const Section = require("../models/Section")
3 | const SubSection = require("../models/SubSection")
4 | const CourseProgress = require("../models/CourseProgress")
5 | const Course = require("../models/Course")
6 |
7 | exports.updateCourseProgress = async (req, res) => {
8 | const { courseId, subsectionId } = req.body
9 | const userId = req.user.id
10 |
11 | try {
12 | // Check if the subsection is valid
13 | const subsection = await SubSection.findById(subsectionId)
14 | if (!subsection) {
15 | return res.status(404).json({ error: "Invalid subsection" })
16 | }
17 |
18 | // Find the course progress document for the user and course
19 | let courseProgress = await CourseProgress.findOne({
20 | courseID: courseId,
21 | userId: userId,
22 | })
23 |
24 | if (!courseProgress) {
25 | // If course progress doesn't exist, create a new one
26 | return res.status(404).json({
27 | success: false,
28 | message: "Course progress Does Not Exist",
29 | })
30 | } else {
31 | // If course progress exists, check if the subsection is already completed
32 | if (courseProgress.completedVideos.includes(subsectionId)) {
33 | return res.status(400).json({ error: "Subsection already completed" })
34 | }
35 |
36 | // Push the subsection into the completedVideos array
37 | courseProgress.completedVideos.push(subsectionId)
38 | }
39 |
40 | // Save the updated course progress
41 | await courseProgress.save()
42 |
43 | return res.status(200).json({ message: "Course progress updated" })
44 | } catch (error) {
45 | console.error(error)
46 | return res.status(500).json({ error: "Internal server error" })
47 | }
48 | }
49 |
50 | // exports.getProgressPercentage = async (req, res) => {
51 | // const { courseId } = req.body
52 | // const userId = req.user.id
53 |
54 | // if (!courseId) {
55 | // return res.status(400).json({ error: "Course ID not provided." })
56 | // }
57 |
58 | // try {
59 | // // Find the course progress document for the user and course
60 | // let courseProgress = await CourseProgress.findOne({
61 | // courseID: courseId,
62 | // userId: userId,
63 | // })
64 | // .populate({
65 | // path: "courseID",
66 | // populate: {
67 | // path: "courseContent",
68 | // },
69 | // })
70 | // .exec()
71 |
72 | // if (!courseProgress) {
73 | // return res
74 | // .status(400)
75 | // .json({ error: "Can not find Course Progress with these IDs." })
76 | // }
77 | // console.log(courseProgress, userId)
78 | // let lectures = 0
79 | // courseProgress.courseID.courseContent?.forEach((sec) => {
80 | // lectures += sec.subSection.length || 0
81 | // })
82 |
83 | // let progressPercentage =
84 | // (courseProgress.completedVideos.length / lectures) * 100
85 |
86 | // // To make it up to 2 decimal point
87 | // const multiplier = Math.pow(10, 2)
88 | // progressPercentage =
89 | // Math.round(progressPercentage * multiplier) / multiplier
90 |
91 | // return res.status(200).json({
92 | // data: progressPercentage,
93 | // message: "Succesfully fetched Course progress",
94 | // })
95 | // } catch (error) {
96 | // console.error(error)
97 | // return res.status(500).json({ error: "Internal server error" })
98 | // }
99 | // }
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 |
4 | const userRoutes = require("./routes/User");
5 | const profileRoutes = require("./routes/Profile");
6 | const paymentRoutes = require("./routes/Payments");
7 | const courseRoutes = require("./routes/Course");
8 | const contactUsRoute = require("./routes/Contact");
9 | const database = require("./config/database");
10 | const cookieParser = require("cookie-parser");
11 | const cors = require("cors");
12 | const {cloudinaryConnect } = require("./config/cloudinary");
13 | const fileUpload = require("express-fileupload");
14 | const dotenv = require("dotenv");
15 |
16 | dotenv.config();
17 | const PORT = process.env.PORT || 4000;
18 |
19 | //database connect
20 | database.connect();
21 | //middlewares
22 | app.use(express.json());
23 | app.use(express.urlencoded({extended: true}))//for using postman
24 | app.use(cookieParser());
25 | app.use(
26 | cors({
27 | origin:"http://localhost:3000",
28 | credentials:true,
29 | })
30 | )
31 |
32 | app.use(
33 | fileUpload({
34 | useTempFiles:true,
35 | tempFileDir:"/tmp",
36 | })
37 | )
38 | //cloudinary connection
39 | cloudinaryConnect();
40 |
41 | //routes
42 | app.use("/api/v1/auth", userRoutes);
43 | app.use("/api/v1/profile", profileRoutes);
44 | app.use("/api/v1/course", courseRoutes);
45 | app.use("/api/v1/payment", paymentRoutes);
46 | app.use("/api/v1/reach", contactUsRoute);
47 |
48 | //def route
49 |
50 | app.get("/", (req, res) => {
51 | return res.json({
52 | success:true,
53 | message:'Your server is up and running....'
54 | });
55 | });
56 |
57 | app.listen(PORT, () => {
58 | console.log(`App is running at ${PORT}`)
59 | })
60 |
61 |
--------------------------------------------------------------------------------
/server/mail/templates/contactFormRes.js:
--------------------------------------------------------------------------------
1 | exports.contactUsEmail = (
2 | email,
3 | firstname,
4 | lastname,
5 | message,
6 | phoneNo,
7 | countrycode
8 | ) => {
9 | return `
10 |
11 |
12 |
13 |
14 | Contact Form Confirmation
15 |
72 |
73 |
74 |
75 |
76 |
77 |
79 |
Contact Form Confirmation
80 |
81 |
Dear ${firstname} ${lastname},
82 |
Thank you for contacting us. We have received your message and will respond to you as soon as possible.
83 |
84 |
Here are the details you provided:
85 |
Name: ${firstname} ${lastname}
86 |
Email: ${email}
87 |
Phone Number: ${phoneNo}
88 |
Message: ${message}
89 |
We appreciate your interest and will get back to you shortly.
90 |
91 |
If you have any further questions or need immediate assistance, please feel free to reach
92 | out to us at
info@studynotion.com . We are here to help!
93 |
94 |
95 |
96 | `
97 | }
--------------------------------------------------------------------------------
/server/mail/templates/courseEnrollmentEmail.js:
--------------------------------------------------------------------------------
1 | exports.courseEnrollmentEmail = (courseName, name) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Course Registration Confirmation
8 |
65 |
66 |
67 |
68 |
69 |
70 |
72 |
Course Registration Confirmation
73 |
74 |
Dear ${name},
75 |
You have successfully registered for the course "${courseName}" . We
76 | are excited to have you as a participant!
77 |
Please log in to your learning dashboard to access the course materials and start your learning journey.
78 |
79 |
Go to Dashboard
80 |
81 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
83 |
84 |
85 |
86 | `;
87 | };
--------------------------------------------------------------------------------
/server/mail/templates/emailVerificationTemplate.js:
--------------------------------------------------------------------------------
1 | const otpTemplate = (otp) => {
2 | return `
3 |
4 |
5 |
6 |
7 | OTP Verification Email
8 |
64 |
65 |
66 |
67 |
68 |
69 |
71 |
OTP Verification Email
72 |
73 |
Dear User,
74 |
Thank you for registering with StudyNotion. To complete your registration, please use the following OTP
75 | (One-Time Password) to verify your account:
76 |
${otp}
77 |
This OTP is valid for 5 minutes. If you did not request this verification, please disregard this email.
78 | Once your account is verified, you will have access to our platform and its features.
79 |
80 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
82 |
83 |
84 |
85 | `;
86 | };
87 | module.exports = otpTemplate;
--------------------------------------------------------------------------------
/server/mail/templates/passwordUpdate.js:
--------------------------------------------------------------------------------
1 | exports.passwordUpdated = (email, name) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Password Update Confirmation
8 |
53 |
54 |
55 |
56 |
57 |
58 |
60 |
Password Update Confirmation
61 |
62 |
Hey ${name},
63 |
Your password has been successfully updated for the email ${email} .
64 |
65 |
If you did not request this password change, please contact us immediately to secure your account.
66 |
67 |
If you have any questions or need further assistance, please feel free to reach out to us
68 | at
69 |
info@studynotion.com . We are here to help!
70 |
71 |
72 |
73 |
74 | `;
75 | };
--------------------------------------------------------------------------------
/server/mail/templates/paymentSuccessEmail.js:
--------------------------------------------------------------------------------
1 | exports.paymentSuccessEmail = (name, amount, orderId, paymentId) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Payment Confirmation
8 |
65 |
66 |
67 |
68 |
69 |
70 |
72 |
Course Payment Confirmation
73 |
74 |
Dear ${name},
75 |
We have received a payment of ₹${amount}
.
76 |
Your Payment ID is ${paymentId}
77 |
Your Order ID is ${orderId}
78 |
79 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
81 |
82 |
83 |
84 | `
85 | }
--------------------------------------------------------------------------------
/server/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | // Importing required modules
2 | const jwt = require("jsonwebtoken");
3 | const dotenv = require("dotenv");
4 | const User = require("../models/User");
5 | // Configuring dotenv to load environment variables from .env file
6 | dotenv.config();
7 |
8 | // This function is used as middleware to authenticate user requests
9 | exports.auth = async (req, res, next) => {
10 | try {
11 | // Extracting JWT from request cookies, body or header
12 | const token =
13 | req.cookies.token ||
14 | req.body.token ||
15 | req.header("Authorization").replace("Bearer ", "");
16 |
17 | // If JWT is missing, return 401 Unauthorized response
18 | if (!token) {
19 | return res.status(401).json({ success: false, message: `Token Missing` });
20 | }
21 |
22 | try {
23 | // Verifying the JWT using the secret key stored in environment variables
24 | const decode = await jwt.verify(token, process.env.JWT_SECRET);
25 | console.log(decode);
26 | // Storing the decoded JWT payload in the request object for further use
27 | req.user = decode;
28 | } catch (error) {
29 | // If JWT verification fails, return 401 Unauthorized response
30 | return res
31 | .status(401)
32 | .json({ success: false, message: "token is invalid" });
33 | }
34 |
35 | // If JWT is valid, move on to the next middleware or request handler
36 | next();
37 | } catch (error) {
38 | // If there is an error during the authentication process, return 401 Unauthorized response
39 | return res.status(401).json({
40 | success: false,
41 | message: `Something Went Wrong While Validating the Token`,
42 | });
43 | }
44 | };
45 | exports.isStudent = async (req, res, next) => {
46 | try {
47 | const userDetails = await User.findOne({ email: req.user.email });
48 |
49 | if (userDetails.accountType !== "Student") {
50 | return res.status(401).json({
51 | success: false,
52 | message: "This is a Protected Route for Students",
53 | });
54 | }
55 | next();
56 | } catch (error) {
57 | return res
58 | .status(500)
59 | .json({ success: false, message: `User Role Can't be Verified` });
60 | }
61 | };
62 | exports.isAdmin = async (req, res, next) => {
63 | try {
64 | const userDetails = await User.findOne({ email: req.user.email });
65 |
66 | if (userDetails.accountType !== "Admin") {
67 | return res.status(401).json({
68 | success: false,
69 | message: "This is a Protected Route for Admin",
70 | });
71 | }
72 | next();
73 | } catch (error) {
74 | return res
75 | .status(500)
76 | .json({ success: false, message: `User Role Can't be Verified` });
77 | }
78 | };
79 | exports.isInstructor = async (req, res, next) => {
80 | try {
81 | const userDetails = await User.findOne({ email: req.user.email });
82 | console.log(userDetails);
83 |
84 | console.log(userDetails.accountType);
85 |
86 | if (userDetails.accountType !== "Instructor") {
87 | return res.status(401).json({
88 | success: false,
89 | message: "This is a Protected Route for Instructor",
90 | });
91 | }
92 | next();
93 | } catch (error) {
94 | return res
95 | .status(500)
96 | .json({ success: false, message: `User Role Can't be Verified` });
97 | }
98 | };
--------------------------------------------------------------------------------
/server/models/Category.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Tags schema
4 | const categorySchema = new mongoose.Schema({
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | description: { type: String },
10 | courses: [
11 | {
12 | type: mongoose.Schema.Types.ObjectId,
13 | ref: "Course",
14 | },
15 | ],
16 | });
17 |
18 | // Export the Tags model
19 | module.exports = mongoose.model("Category", categorySchema);
--------------------------------------------------------------------------------
/server/models/Course.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Courses schema
4 | const coursesSchema = new mongoose.Schema({
5 | courseName: { type: String },
6 | courseDescription: { type: String },
7 | instructor: {
8 | type: mongoose.Schema.Types.ObjectId,
9 | required: true,
10 | ref: "user",
11 | },
12 | whatYouWillLearn: {
13 | type: String,
14 | },
15 | courseContent: [
16 | {
17 | type: mongoose.Schema.Types.ObjectId,
18 | ref: "Section",
19 | },
20 | ],
21 | ratingAndReviews: [
22 | {
23 | type: mongoose.Schema.Types.ObjectId,
24 | ref: "RatingAndReview",
25 | },
26 | ],
27 | price: {
28 | type: Number,
29 | },
30 | thumbnail: {
31 | type: String,
32 | },
33 | tag: {
34 | type: [String],
35 | required: true,
36 | },
37 | category: {
38 | type: mongoose.Schema.Types.ObjectId,
39 | // required: true,
40 | ref: "Category",
41 | },
42 | studentsEnrolled: [
43 | {
44 | type: mongoose.Schema.Types.ObjectId,
45 | required: true,
46 | ref: "user",
47 | },
48 | ],
49 | instructions: {
50 | type: [String],
51 | },
52 | status: {
53 | type: String,
54 | enum: ["Draft", "Published"],
55 | },
56 | createdAt: {
57 | type:Date,
58 | default:Date.now
59 | },
60 | });
61 |
62 | // Export the Courses model
63 | module.exports = mongoose.model("Course", coursesSchema);
--------------------------------------------------------------------------------
/server/models/CourseProgress.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 |
3 | const courseProgress = new mongoose.Schema({
4 | courseID: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "Course",
7 | },
8 | userId: {
9 | type: mongoose.Schema.Types.ObjectId,
10 | ref: "user",
11 | },
12 | completedVideos: [
13 | {
14 | type: mongoose.Schema.Types.ObjectId,
15 | ref: "SubSection",
16 | },
17 | ],
18 | })
19 |
20 | module.exports = mongoose.model("courseProgress", courseProgress)
--------------------------------------------------------------------------------
/server/models/OTP.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const mailSender = require("../utils/mailSender");
3 | const emailTemplate = require("../mail/templates/emailVerificationTemplate");
4 | const OTPSchema = new mongoose.Schema({
5 | email: {
6 | type: String,
7 | required: true,
8 | },
9 | otp: {
10 | type: String,
11 | required: true,
12 | },
13 | createdAt: {
14 | type: Date,
15 | default: Date.now,
16 | expires: 60 * 5, // The document will be automatically deleted after 5 minutes of its creation time
17 | },
18 | });
19 |
20 | // Define a function to send emails
21 | async function sendVerificationEmail(email, otp) {
22 | // Create a transporter to send emails
23 |
24 | // Define the email options
25 |
26 | // Send the email
27 | try {
28 | const mailResponse = await mailSender(
29 | email,
30 | "Verification Email",
31 | emailTemplate(otp)
32 | );
33 | console.log("Email sent successfully: ", mailResponse.response);
34 | } catch (error) {
35 | console.log("Error occurred while sending email: ", error);
36 | throw error;
37 | }
38 | }
39 |
40 | // Define a post-save hook to send email after the document has been saved
41 | OTPSchema.pre("save", async function (next) {
42 | console.log("New document saved to database");
43 |
44 | // Only send an email when a new document is created
45 | if (this.isNew) {
46 | await sendVerificationEmail(this.email, this.otp);
47 | }
48 | next();
49 | });
50 |
51 | const OTP = mongoose.model("OTP", OTPSchema);
52 |
53 | module.exports = OTP;
--------------------------------------------------------------------------------
/server/models/Profile.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Profile schema
4 | const profileSchema = new mongoose.Schema({
5 | gender: {
6 | type: String,
7 | },
8 | dateOfBirth: {
9 | type: String,
10 | },
11 | about: {
12 | type: String,
13 | trim: true,
14 | },
15 | contactNumber: {
16 | type: Number,
17 | trim: true,
18 | },
19 | });
20 |
21 | // Export the Profile model
22 | module.exports = mongoose.model("Profile", profileSchema);
23 |
--------------------------------------------------------------------------------
/server/models/RatingAndRaview.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the RatingAndReview schema
4 | const ratingAndReviewSchema = new mongoose.Schema({
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: "user",
9 | },
10 | rating: {
11 | type: Number,
12 | required: true,
13 | },
14 | review: {
15 | type: String,
16 | required: true,
17 | },
18 | course: {
19 | type: mongoose.Schema.Types.ObjectId,
20 | required: true,
21 | ref: "Course",
22 | index: true,
23 | },
24 | });
25 |
26 | // Export the RatingAndReview model
27 | module.exports = mongoose.model("RatingAndReview", ratingAndReviewSchema);
--------------------------------------------------------------------------------
/server/models/Section.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Section schema
4 | const sectionSchema = new mongoose.Schema({
5 | sectionName: {
6 | type: String,
7 | },
8 | subSection: [
9 | {
10 | type: mongoose.Schema.Types.ObjectId,
11 | required: true,
12 | ref: "SubSection",
13 | },
14 | ],
15 | });
16 |
17 | // Export the Section model
18 | module.exports = mongoose.model("Section", sectionSchema);
--------------------------------------------------------------------------------
/server/models/SubSection.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const SubSectionSchema = new mongoose.Schema({
4 | title: { type: String },
5 | timeDuration: { type: String },
6 | description: { type: String },
7 | videoUrl: { type: String },
8 | });
9 |
10 | module.exports = mongoose.model("SubSection", SubSectionSchema);
--------------------------------------------------------------------------------
/server/models/User.js:
--------------------------------------------------------------------------------
1 | // Import the Mongoose library
2 | const mongoose = require("mongoose");
3 |
4 | // Define the user schema using the Mongoose Schema constructor
5 | const userSchema = new mongoose.Schema(
6 | {
7 | // Define the name field with type String, required, and trimmed
8 | firstName: {
9 | type: String,
10 | required: true,
11 | trim: true,
12 | },
13 | lastName: {
14 | type: String,
15 | required: true,
16 | trim: true,
17 | },
18 | // Define the email field with type String, required, and trimmed
19 | email: {
20 | type: String,
21 | required: true,
22 | trim: true,
23 | },
24 |
25 | // Define the password field with type String and required
26 | password: {
27 | type: String,
28 | required: true,
29 | },
30 | // Define the role field with type String and enum values of "Admin", "Student", or "Visitor"
31 | accountType: {
32 | type: String,
33 | enum: ["Admin", "Student", "Instructor"],
34 | required: true,
35 | },
36 | active: {
37 | type: Boolean,
38 | default: true,
39 | },
40 | approved: {
41 | type: Boolean,
42 | default: true,
43 | },
44 | additionalDetails: {
45 | type: mongoose.Schema.Types.ObjectId,
46 | required: true,
47 | ref: "Profile",
48 | },
49 | courses: [
50 | {
51 | type: mongoose.Schema.Types.ObjectId,
52 | ref: "Course",
53 | },
54 | ],
55 | token: {
56 | type: String,
57 | },
58 | resetPasswordExpires: {
59 | type: Date,
60 | },
61 | image: {
62 | type: String,
63 | //at registration image cannot be uploaded
64 | // required: true,
65 | },
66 | courseProgress: [
67 | {
68 | type: mongoose.Schema.Types.ObjectId,
69 | ref: "courseProgress",
70 | },
71 | ],
72 |
73 | // Add timestamps for when the document is created and last modified
74 | },
75 | { timestamps: true }
76 | );
77 |
78 | // Export the Mongoose model for the user schema, using the name "user"
79 | module.exports = mongoose.model("user", userSchema);
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "dev": "nodemon index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcrypt": "^5.1.0",
15 | "bcryptjs": "^2.4.3",
16 | "cloudinary": "^1.36.4",
17 | "cookie-parser": "^1.4.6",
18 | "cors": "^2.8.5",
19 | "crypto-random-string": "^5.0.0",
20 | "dotenv": "^16.0.3",
21 | "express": "^4.18.2",
22 | "express-fileupload": "^1.4.0",
23 | "jsonwebtoken": "^9.0.0",
24 | "mongoose": "^7.0.3",
25 | "node-schedule": "^2.1.1",
26 | "nodemailer": "^6.9.1",
27 | "nodemon": "^3.1.3",
28 | "otp-generator": "^4.0.1",
29 | "razorpay": "^2.8.6",
30 | "@babel/plugin-proposal-private-property-in-object": "7.21.11"
31 | }
32 | }
33 |
34 |
35 |
--------------------------------------------------------------------------------
/server/routes/Contact.js:
--------------------------------------------------------------------------------
1 | const express = require("express")
2 | const router = express.Router()
3 | const { contactUsController } = require("../controllers/ContactUs")
4 |
5 | router.post("/contact", contactUsController)
6 |
7 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/Payments.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | const { capturePayment, verifyPayment, sendPaymentSuccessEmail } = require("../controllers/Payments")
6 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth")
7 | router.post("/capturePayment", auth, isStudent, capturePayment)
8 | router.post("/verifyPayment",auth, isStudent, verifyPayment)
9 | router.post("/sendPaymentSuccessEmail", auth, isStudent, sendPaymentSuccessEmail);
10 |
11 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/Profile.js:
--------------------------------------------------------------------------------
1 | const express = require("express")
2 | const router = express.Router()
3 | const { auth, isInstructor } = require("../middlewares/auth")
4 | const {
5 | deleteAccount,
6 | updateProfile,
7 | getAllUserDetails,
8 | updateDisplayPicture,
9 | getEnrolledCourses,
10 | instructorDashboard,
11 | } = require("../controllers/Profile")
12 |
13 | // ********************************************************************************************************
14 | // Profile routes
15 | // ********************************************************************************************************
16 | // Delet User Account
17 | router.delete("/deleteProfile", auth, deleteAccount)
18 | router.put("/updateProfile", auth, updateProfile)
19 | router.get("/getUserDetails", auth, getAllUserDetails)
20 | // Get Enrolled Courses
21 | router.get("/getEnrolledCourses", auth, getEnrolledCourses)
22 | router.put("/updateDisplayPicture", auth, updateDisplayPicture)
23 | router.get("/instructorDashboard", auth, isInstructor, instructorDashboard)
24 |
25 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/User.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | // Import the required controllers and middleware functions
6 | const {
7 | login,
8 | signup,
9 | sendotp,
10 | changePassword,
11 | } = require("../controllers/Auth")
12 | const {
13 | resetPasswordToken,
14 | resetPassword,
15 | } = require("../controllers/ResetPassword")
16 |
17 | const { auth } = require("../middlewares/auth")
18 |
19 | // Routes for Login, Signup, and Authentication
20 |
21 | // ********************************************************************************************************
22 | // Authentication routes
23 | // ********************************************************************************************************
24 |
25 | // Route for user login
26 | router.post("/login", login)
27 |
28 | // Route for user signup
29 | router.post("/signup", signup)
30 |
31 | // Route for sending OTP to the user's email
32 | router.post("/sendotp", sendotp)
33 |
34 | // Route for Changing the password
35 | router.post("/changepassword", auth, changePassword)
36 |
37 | // ********************************************************************************************************
38 | // Reset Password
39 | // ********************************************************************************************************
40 |
41 | // Route for generating a reset password token
42 | router.post("/reset-password-token", resetPasswordToken)
43 |
44 | // Route for resetting user's password after verification
45 | router.post("/reset-password", resetPassword)
46 |
47 | // Export the router for use in the main application
48 | module.exports = router
--------------------------------------------------------------------------------
/server/utils/imageUploader.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require('cloudinary').v2
2 |
3 |
4 | exports.uploadImageToCloudinary = async (file, folder, height, quality) => {
5 | const options = {folder};
6 | if(height) {
7 | options.height = height;
8 | }
9 | if(quality) {
10 | options.quality = quality;
11 | }
12 | options.resource_type = "auto";
13 |
14 | return await cloudinary.uploader.upload(file.tempFilePath, options);
15 | }
--------------------------------------------------------------------------------
/server/utils/mailSender.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 |
3 | const mailSender = async (email, title, body) => {
4 | try{
5 | let transporter = nodemailer.createTransport({
6 | host:process.env.MAIL_HOST,
7 | //add certification
8 | port: 465,
9 | secure: true,
10 | auth:{
11 | user: process.env.MAIL_USER,
12 | pass: process.env.MAIL_PASS,
13 | }
14 | })
15 |
16 |
17 | let info = await transporter.sendMail({
18 | from: 'StudyNotion || CodeHelp - by Babbar',
19 | to:`${email}`,
20 | subject: `${title}`,
21 | html: `${body}`,
22 | })
23 | console.log(info);
24 | return info;
25 | }
26 | catch(error) {
27 | console.log(error);
28 | //throw right error
29 | throw error;
30 | }
31 | }
32 |
33 |
34 | module.exports = mailSender;
--------------------------------------------------------------------------------
/server/utils/secToDuration.js:
--------------------------------------------------------------------------------
1 | // Helper function to convert total seconds to the duration format
2 | function convertSecondsToDuration(totalSeconds) {
3 | const hours = Math.floor(totalSeconds / 3600)
4 | const minutes = Math.floor((totalSeconds % 3600) / 60)
5 | const seconds = Math.floor((totalSeconds % 3600) % 60)
6 |
7 | if (hours > 0) {
8 | return `${hours}h ${minutes}m`
9 | } else if (minutes > 0) {
10 | return `${minutes}m ${seconds}s`
11 | } else {
12 | return `${seconds}s`
13 | }
14 | }
15 |
16 | module.exports = {
17 | convertSecondsToDuration,
18 | }
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/.DS_Store
--------------------------------------------------------------------------------
/src/assets/Images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/404.png
--------------------------------------------------------------------------------
/src/assets/Images/Compare_with_others.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/Compare_with_others.png
--------------------------------------------------------------------------------
/src/assets/Images/FoundingStory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/FoundingStory.png
--------------------------------------------------------------------------------
/src/assets/Images/Instructor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/Instructor.png
--------------------------------------------------------------------------------
/src/assets/Images/Know_your_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/Know_your_progress.png
--------------------------------------------------------------------------------
/src/assets/Images/Plan_your_lessons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/Plan_your_lessons.png
--------------------------------------------------------------------------------
/src/assets/Images/TimelineImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/TimelineImage.png
--------------------------------------------------------------------------------
/src/assets/Images/aboutus1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/aboutus1.webp
--------------------------------------------------------------------------------
/src/assets/Images/aboutus2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/aboutus2.webp
--------------------------------------------------------------------------------
/src/assets/Images/aboutus3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/aboutus3.webp
--------------------------------------------------------------------------------
/src/assets/Images/ads_click_24dp_FFFFFF_FILL0_wght400_GRAD0_opsz24 (2).svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/Images/banner.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/banner.mp4
--------------------------------------------------------------------------------
/src/assets/Images/boxoffice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/boxoffice.png
--------------------------------------------------------------------------------
/src/assets/Images/frame.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/frame.png
--------------------------------------------------------------------------------
/src/assets/Images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image1.png
--------------------------------------------------------------------------------
/src/assets/Images/image2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image2.jpg
--------------------------------------------------------------------------------
/src/assets/Images/image3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image3.webp
--------------------------------------------------------------------------------
/src/assets/Images/image4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image4.jpg
--------------------------------------------------------------------------------
/src/assets/Images/image5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image5.jpg
--------------------------------------------------------------------------------
/src/assets/Images/image6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image6.jpg
--------------------------------------------------------------------------------
/src/assets/Images/image7.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image7.jpeg
--------------------------------------------------------------------------------
/src/assets/Images/image8.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image8.webp
--------------------------------------------------------------------------------
/src/assets/Images/image9.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/image9.webp
--------------------------------------------------------------------------------
/src/assets/Images/loaderbg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/loaderbg.jpg
--------------------------------------------------------------------------------
/src/assets/Images/login.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/login.webp
--------------------------------------------------------------------------------
/src/assets/Images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/logo.png
--------------------------------------------------------------------------------
/src/assets/Images/preloader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/preloader.gif
--------------------------------------------------------------------------------
/src/assets/Images/signup.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Images/signup.webp
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Full-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Logo/Logo-Full-Dark.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Full-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Logo/Logo-Full-Light.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Small-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Logo/Logo-Small-Dark.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Small-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Logo/Logo-Small-Light.png
--------------------------------------------------------------------------------
/src/assets/Logo/rzp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Muskansahuincredible/StudyNotion-An-Online-Education-Platform/510af9c37a48b8eec913c5f6e98018665903ac3c/src/assets/Logo/rzp_logo.png
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/ContactPage/ContactDetails.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import * as Icon1 from "react-icons/bi"
3 | import * as Icon3 from "react-icons/hi2"
4 | import * as Icon2 from "react-icons/io5"
5 |
6 | const contactDetails = [
7 | {
8 | icon: "HiChatBubbleLeftRight",
9 | heading: "Chat with us",
10 | description: "Our friendly team is here to help.",
11 | details: "info@studynotion.com",
12 | linkType: "email"
13 | },
14 | {
15 | icon: "BiWorld",
16 | heading: "Visit us",
17 | description: "Come and say hello at our office HQ.",
18 | details:
19 | "Akshya Nagar 1st Block 1st Cross, Rammurthy nagar, Bangalore-560016",
20 | linkType: null
21 | },
22 | {
23 | icon: "IoCall",
24 | heading: "Call us",
25 | description: "Mon - Fri From 8am to 5pm",
26 | details: "+123 456 7869",
27 | linkType: "phone"
28 | },
29 | ]
30 |
31 | const ContactDetails = () => {
32 | return (
33 |
34 | {contactDetails.map((ele, i) => {
35 | let Icon = Icon1[ele.icon] || Icon2[ele.icon] || Icon3[ele.icon];
36 | let detailElement;
37 |
38 | if (ele.linkType === "email") {
39 | detailElement = (
40 |
41 | {ele.details}
42 |
43 | );
44 | } else if (ele.linkType === "phone") {
45 | detailElement = (
46 |
47 | {ele.details}
48 |
49 | );
50 | } else {
51 | detailElement =
{ele.details}
;
52 | }
53 |
54 | return (
55 |
59 |
60 |
61 |
62 | {ele?.heading}
63 |
64 |
65 |
{ele?.description}
66 | {detailElement}
67 |
68 | )
69 | })}
70 |
71 | )
72 | }
73 |
74 | export default ContactDetails
--------------------------------------------------------------------------------
/src/components/ContactPage/ContactForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ContactUsForm from "./ContactUsForm";
3 |
4 | const ContactForm = () => {
5 | return (
6 |
7 |
11 | Got an Idea? We've got the skills. Let's team up !!
12 |
13 |
14 | Tell us more about yourself and what you're got in mind.
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default ContactForm;
25 |
--------------------------------------------------------------------------------
/src/components/common/BackToTop.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
3 |
4 | const BackToTop = () => {
5 | const [isVisible, setIsVisible] = useState(false);
6 |
7 | useEffect(() => {
8 | const handleScroll = () => {
9 | if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
10 | setIsVisible(true);
11 | } else {
12 | setIsVisible(false);
13 | }
14 | };
15 |
16 | window.addEventListener("scroll", handleScroll);
17 | return () => window.removeEventListener("scroll", handleScroll);
18 | }, []);
19 |
20 | const handleClick = () => {
21 | document.body.scrollTop = 0;
22 | document.documentElement.scrollTop = 0;
23 | };
24 |
25 | return (
26 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default BackToTop;
38 |
--------------------------------------------------------------------------------
/src/components/common/ConfirmationModal.jsx:
--------------------------------------------------------------------------------
1 | import IconBtn from "./IconBtn"
2 |
3 | export default function ConfirmationModal({ modalData }) {
4 | return (
5 |
6 |
7 |
8 | {modalData?.text1}
9 |
10 |
11 | {modalData?.text2}
12 |
13 |
14 |
18 |
22 | {modalData?.btn2Text}
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/common/IconBtn.jsx:
--------------------------------------------------------------------------------
1 | export default function IconBtn({
2 | text,
3 | onclick,
4 | children,
5 | disabled,
6 | outline = false,
7 | customClasses,
8 | type,
9 | }) {
10 | return (
11 |
19 | {children ? (
20 | <>
21 | {text}
22 | {children}
23 | >
24 | ) : (
25 | text
26 | )}
27 |
28 | )
29 | }
--------------------------------------------------------------------------------
/src/components/common/Loading.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PropagateLoader } from 'react-spinners';
3 | import loadingGif from '../../assets/Images/preloader.gif';
4 | import bg from "../../assets/Images/loaderbg.jpg"
5 |
6 | const Loading = () => {
7 | return (
8 | <>
9 |
10 |
15 |
16 |
17 | >
18 | );
19 | };
20 |
21 | const styles = {
22 | loaderContainer: {
23 | display: 'flex',
24 | flexDirection: 'column',
25 | justifyContent: 'center',
26 | alignItems: 'center',
27 | width: '100vw',
28 | height: '100vh',
29 | backgroundColor: 'white',
30 | backgroundSize: 'cover',
31 | backgroundPosition: 'center',
32 | },
33 | gif: {
34 | marginBottom: '20px',
35 | height: '250px',
36 | width: 'auto',
37 | backgroundColor: 'none'
38 | },
39 | };
40 |
41 | export default Loading;
42 |
--------------------------------------------------------------------------------
/src/components/common/RatingStars.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import {
3 | TiStarFullOutline,
4 | TiStarHalfOutline,
5 | TiStarOutline,
6 | } from "react-icons/ti"
7 |
8 | function RatingStars({ Review_Count, Star_Size }) {
9 | const [starCount, SetStarCount] = useState({
10 | full: 0,
11 | half: 0,
12 | empty: 0,
13 | })
14 |
15 | useEffect(() => {
16 | const wholeStars = Math.floor(Review_Count) || 0
17 | SetStarCount({
18 | full: wholeStars,
19 | half: Number.isInteger(Review_Count) ? 0 : 1,
20 | empty: Number.isInteger(Review_Count) ? 5 - wholeStars : 4 - wholeStars,
21 | })
22 | }, [Review_Count])
23 | return (
24 |
25 | {[...new Array(starCount.full)].map((_, i) => {
26 | return
27 | })}
28 | {[...new Array(starCount.half)].map((_, i) => {
29 | return
30 | })}
31 | {[...new Array(starCount.empty)].map((_, i) => {
32 | return
33 | })}
34 |
35 | )
36 | }
37 |
38 | export default RatingStars
--------------------------------------------------------------------------------
/src/components/common/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 | }
--------------------------------------------------------------------------------
/src/components/common/progess.css:
--------------------------------------------------------------------------------
1 | .progress-container {
2 | width: 100%;
3 | height: 3px;
4 | background-color:yellow;
5 | position: fixed; /* Ensure the bar stays fixed at the top */
6 |
7 | z-index: 999;
8 | /* Smooth transition */
9 | }
10 |
11 | .progress-bar {
12 | height: 100%;
13 | background-color: cyan;
14 | width: 60%;
15 | transition: width 0.2s ease-in-out;
16 | }
--------------------------------------------------------------------------------
/src/components/common/progressbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import './progess.css';
3 |
4 | const ProgressBar = () => {
5 | const [scrollProgress, setScrollProgress] = useState(0);
6 |
7 | const handleScroll = () => {
8 | const totalScroll = document.documentElement.scrollTop;
9 | const windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
10 | const scroll = `${totalScroll / windowHeight * 100}`;
11 | setScrollProgress(scroll);
12 | };
13 |
14 | useEffect(() => {
15 | window.addEventListener('scroll', handleScroll);
16 | return () => window.removeEventListener('scroll', handleScroll);
17 | }, []);
18 |
19 | return (
20 |
28 | );
29 | };
30 |
31 | export default ProgressBar;
32 |
--------------------------------------------------------------------------------
/src/components/core/AboutPage/ContactFormSection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ContactUsForm from "../../ContactPage/ContactUsForm";
3 |
4 | const ContactFormSection = () => {
5 | return (
6 |
7 |
Get in Touch
8 |
9 | We'd love to hear form you. Please fill out this form.
10 |
11 | {/*
*/}
12 |
13 | {/*
*/}
14 |
15 | );
16 | };
17 |
18 | export default ContactFormSection;
--------------------------------------------------------------------------------
/src/components/core/AboutPage/LearningGrid.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import HighlightText from "../../../components/core/HomePage/HighlightText";
3 | import CTAButton from "../../../components/core/HomePage/Button";
4 |
5 | const LearningGridArray = [
6 | {
7 | order: -1,
8 | heading: "World-Class Learning for",
9 | highlightText: "Anyone, Anywhere",
10 | description:
11 | "Studynotion partners with more than 275+ leading universities and companies to bring flexible, affordable, job-relevant online learning to individuals and organizations worldwide.",
12 | BtnText: "Learn More",
13 | BtnLink: "/",
14 | },
15 | {
16 | order: 1,
17 | heading: "Curriculum Based on Industry Needs",
18 | description:
19 | "Save time and money! The Belajar curriculum is made to be easier to understand and in line with industry needs.",
20 | },
21 | {
22 | order: 2,
23 | heading: "Our Learning Methods",
24 | description:
25 | "Our learning method combines flexible and practical approaches to ensure a comprehensive and engaging educational experience.",
26 | },
27 | {
28 | order: 3,
29 | heading: "Certification",
30 | description:
31 | "Studynotion provides industry-recognized certification to validate your new skills and enhance your career prospects.",
32 | },
33 | {
34 | order: 4,
35 | heading: `Rating "Auto-grading"`,
36 | description:
37 | "Studynotion’s auto-grading feature provides instant, objective feedback to help learners assess their understanding and progress efficiently.",
38 | },
39 | {
40 | order: 5,
41 | heading: "Ready to Work",
42 | description:
43 | "Studynotion equips learners with job-ready skills, preparing them to excel in the workforce.",
44 | },
45 | ];
46 |
47 | const LearningGrid = () => {
48 | return (
49 |
50 | {LearningGridArray.map((card, i) => {
51 | const isHighlightCard = card.order < 0;
52 | const cardBgClass =
53 | card.order % 2 === 1
54 | ? "bg-richblack-600"
55 | : card.order % 2 === 0
56 | ? "bg-richblack-800"
57 | : "bg-transparent";
58 | const cardHeightClass = "h-[320px]";
59 | const colSpanClass = i === 0 ? "xl:col-span-2" : "";
60 | const colStartClass = card.order === 3 ? "xl:col-start-2" : "";
61 |
62 | return (
63 |
67 | {isHighlightCard ? (
68 |
69 |
70 | {card.heading}
71 |
72 |
73 |
74 | {card.description}
75 |
76 |
77 |
78 | {card.BtnText}
79 |
80 |
81 |
82 | ) : (
83 |
84 |
85 | {card.heading}
86 |
87 |
88 | {card.description}
89 |
90 |
91 | )}
92 |
93 | );
94 | })}
95 |
96 | );
97 | };
98 |
99 | export default LearningGrid;
100 |
--------------------------------------------------------------------------------
/src/components/core/AboutPage/Quote.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from '../HomePage/HighlightText'
3 |
4 | const Quote = () => {
5 | return (
6 |
7 | We are passionate about revolutionizing the way we learn. Our
8 | innovative platform ,{" "}
9 |
10 | {" "}
11 | expertise
12 |
13 | , and community to create an
14 |
15 | {" "}
16 | unparalleled educational
17 | experience.
18 |
19 |
20 | )
21 | }
22 |
23 | export default Quote
--------------------------------------------------------------------------------
/src/components/core/AboutPage/Stats.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import useIntersectionObserver from './useIntersectionObserver'; // Adjust the path as needed
3 |
4 | const countUp = (element, start, end, duration, finalDisplay) => {
5 | let startTime = null;
6 |
7 | const animation = (currentTime) => {
8 | if (startTime === null) startTime = currentTime;
9 | const elapsedTime = currentTime - startTime;
10 | const progress = Math.min(elapsedTime / duration, 1);
11 | const currentNumber = Math.floor(progress * (end - start) + start);
12 |
13 | element.textContent = currentNumber.toLocaleString(); // Format number with commas
14 |
15 | if (progress < 1) {
16 | requestAnimationFrame(animation);
17 | } else {
18 | element.textContent = finalDisplay; // Set final display value
19 | }
20 | };
21 |
22 | requestAnimationFrame(animation);
23 | };
24 |
25 | const parseCountValue = (count) => {
26 | if (count.endsWith('K')) {
27 | return parseFloat(count) * 1000;
28 | } else {
29 | return parseFloat(count);
30 | }
31 | };
32 |
33 | const getFinalDisplay = (count) => {
34 | if (count.endsWith('K')) {
35 | return count;
36 | } else {
37 | return parseFloat(count).toLocaleString();
38 | }
39 | };
40 |
41 | const StatsComponent = () => {
42 | const stats = [
43 | { count: '5K', label: 'Active Students' },
44 | { count: '10', label: 'Mentors' },
45 | { count: '200', label: 'Courses' },
46 | { count: '50', label: 'Awards' },
47 | ];
48 |
49 | const elementsRef = useRef([]);
50 | const [isIntersecting, setElement] = useIntersectionObserver({
51 | threshold: 0.5, // Adjust as needed
52 | });
53 |
54 | useEffect(() => {
55 | if (isIntersecting) {
56 | elementsRef.current.forEach((element, index) => {
57 | const countValue = stats[index].count;
58 | const endValue = parseCountValue(countValue);
59 | const finalDisplay = getFinalDisplay(countValue);
60 | countUp(element, 0, endValue, 2000, finalDisplay);
61 | });
62 | }
63 | }, [isIntersecting]);
64 |
65 | return (
66 |
67 | {/* Stats */}
68 |
69 |
70 | {stats.map((data, index) => (
71 |
72 |
73 |
(elementsRef.current[index] = el)}
76 | >
77 | {parseCountValue(data.count).toLocaleString()}
78 |
79 | +
80 |
81 |
82 |
83 | {data.label}
84 |
85 |
86 |
87 | ))}
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default StatsComponent;
95 |
--------------------------------------------------------------------------------
/src/components/core/AboutPage/useIntersectionObserver.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 |
3 | const useIntersectionObserver = (options) => {
4 | const [isIntersecting, setIsIntersecting] = useState(false);
5 | const observerRef = useRef(null);
6 |
7 | const setElement = (element) => {
8 | if (observerRef.current) {
9 | observerRef.current.disconnect();
10 | }
11 |
12 | if (element) {
13 | observerRef.current = new IntersectionObserver(
14 | ([entry]) => {
15 | setIsIntersecting(entry.isIntersecting);
16 | },
17 | { ...options }
18 | );
19 |
20 | observerRef.current.observe(element);
21 | }
22 | };
23 |
24 | return [isIntersecting, setElement];
25 | };
26 |
27 | export default useIntersectionObserver;
28 |
--------------------------------------------------------------------------------
/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 | import { SiGmail } from "react-icons/si";
6 | import { FaLock } from "react-icons/fa";
7 | import { login } from "../../../services/operations/authAPI"
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 |
98 | )
99 | }
100 |
101 | export default LoginForm
--------------------------------------------------------------------------------
/src/components/core/Auth/OpenRoute.jsx:
--------------------------------------------------------------------------------
1 | // This will prevent authenticated users from accessing this route
2 | import { useSelector } from "react-redux"
3 | import { Navigate } from "react-router-dom"
4 |
5 | function OpenRoute({ children }) {
6 | const { token } = useSelector((state) => state.auth)
7 |
8 | if (token === null) {
9 | return children
10 | } else {
11 | return
12 | }
13 | }
14 |
15 | export default OpenRoute
--------------------------------------------------------------------------------
/src/components/core/Auth/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { Navigate } from 'react-router-dom';
4 |
5 | const PrivateRoute = ({children}) => {
6 |
7 | const {token} = useSelector((state) => state.auth);
8 |
9 | if(token !== null)
10 | return children
11 | else
12 | return
13 |
14 | }
15 |
16 | export default PrivateRoute
17 |
--------------------------------------------------------------------------------
/src/components/core/Auth/ProfileDropDown.jsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react"
2 | import { AiOutlineCaretDown } from "react-icons/ai"
3 | import { VscDashboard, VscSignOut } from "react-icons/vsc"
4 | import { useDispatch, useSelector } from "react-redux"
5 | import { Link, useNavigate } from "react-router-dom"
6 |
7 | import useOnClickOutside from "../../../hooks/useOnClickOutside"
8 | import { logout } from "../../../services/operations/authAPI"
9 |
10 | export default function ProfileDropdown() {
11 | const { user } = useSelector((state) => state.profile)
12 | const dispatch = useDispatch()
13 | const navigate = useNavigate()
14 | const [open, setOpen] = useState(false)
15 | const ref = useRef(null)
16 |
17 | useOnClickOutside(ref, () => setOpen(false))
18 |
19 | if (!user) return null
20 |
21 | return (
22 | 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 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/src/components/core/Catalog/CourseSlider.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Swiper, SwiperSlide } from 'swiper/react';
3 | import 'swiper/css';
4 | import 'swiper/css/free-mode';
5 | import 'swiper/css/pagination';
6 | import 'swiper/css/navigation';
7 | import 'swiper/css/autoplay';
8 | import { FreeMode, Pagination, Autoplay, Navigation } from 'swiper/modules';
9 |
10 | import CourseCard from './Course_Card';
11 |
12 | const CourseSlider = ({ Courses = [] }) => {
13 | return (
14 | <>
15 | {Courses.length > 0 ? (
16 |
32 | {Courses.map((course, i) => (
33 |
34 |
35 |
36 | ))}
37 |
38 | ) : (
39 | No Course Found
40 | )}
41 | >
42 | );
43 | };
44 |
45 | export default CourseSlider;
46 |
--------------------------------------------------------------------------------
/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 |
32 | {course?.instructor?.firstName} {course?.instructor?.lastName}
33 |
34 |
35 | {avgReviewCount || 0}
36 |
37 |
38 | {course?.ratingAndReviews?.length} Ratings
39 |
40 |
41 |
Rs. {course?.price}
42 |
43 |
44 |
45 | >
46 | )
47 | }
48 |
49 | export default Course_Card
50 |
--------------------------------------------------------------------------------
/src/components/core/Course/CourseAccordionBar.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react"
2 | import { AiOutlineDown } from "react-icons/ai"
3 |
4 | import CourseSubSectionAccordion from "./CourseSubSectionAccordion"
5 |
6 | export default function CourseAccordionBar({ course, isActive, handleActive }) {
7 | const contentEl = useRef(null)
8 |
9 | // Accordian state
10 | const [active, setActive] = useState(false)
11 | useEffect(() => {
12 | setActive(isActive?.includes(course._id))
13 | }, [isActive])
14 | const [sectionHeight, setSectionHeight] = useState(0)
15 | useEffect(() => {
16 | setSectionHeight(active ? contentEl.current.scrollHeight : 0)
17 | }, [active])
18 |
19 | return (
20 |
21 |
22 |
{
25 | handleActive(course._id)
26 | }}
27 | >
28 |
29 |
34 |
35 |
36 |
{course?.sectionName}
37 |
38 |
39 |
40 | {`${course.subSection.length || 0} lecture(s)`}
41 |
42 |
43 |
44 |
45 |
52 |
53 | {course?.subSection?.map((subSec, i) => {
54 | return
55 | })}
56 |
57 |
58 |
59 | )
60 | }
--------------------------------------------------------------------------------
/src/components/core/Course/CourseSubSectionAccordion.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react"
2 | import { AiOutlineDown } from "react-icons/ai"
3 | import { HiOutlineVideoCamera } from "react-icons/hi"
4 |
5 | function CourseSubSectionAccordion({ subSec }) {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
{subSec?.title}
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default CourseSubSectionAccordion
--------------------------------------------------------------------------------
/src/components/core/Dashboard/AddCourse/CourseInformation/ChipInput.jsx:
--------------------------------------------------------------------------------
1 | // Importing React hook for managing component state
2 | import { useEffect, useState } from "react"
3 | // Importing React icon component
4 | import { MdClose } from "react-icons/md"
5 | import { useSelector } from "react-redux"
6 |
7 | // Defining a functional component ChipInput
8 | export default function ChipInput({
9 | // Props to be passed to the component
10 | label,
11 | name,
12 | placeholder,
13 | register,
14 | errors,
15 | setValue,
16 | getValues,
17 | }) {
18 | const { editCourse, course } = useSelector((state) => state.course)
19 |
20 | // Setting up state for managing chips array
21 | const [chips, setChips] = useState([])
22 |
23 | useEffect(() => {
24 | if (editCourse) {
25 | // console.log(course)
26 | setChips(course?.tag)
27 | }
28 | register(name, { required: true, validate: (value) => value.length > 0 })
29 | // eslint-disable-next-line react-hooks/exhaustive-deps
30 | }, [])
31 |
32 | useEffect(() => {
33 | setValue(name, chips)
34 | // eslint-disable-next-line react-hooks/exhaustive-deps
35 | }, [chips])
36 |
37 | // Function to handle user input when chips are added
38 | const handleKeyDown = (event) => {
39 | // Check if user presses "Enter" or ","
40 | if (event.key === "Enter" || event.key === ",") {
41 | // Prevent the default behavior of the event
42 | event.preventDefault()
43 | // Get the input value and remove any leading/trailing spaces
44 | const chipValue = event.target.value.trim()
45 | // Check if the input value exists and is not already in the chips array
46 | if (chipValue && !chips.includes(chipValue)) {
47 | // Add the chip to the array and clear the input
48 | const newChips = [...chips, chipValue]
49 | setChips(newChips)
50 | event.target.value = ""
51 | }
52 | }
53 | }
54 |
55 | // Function to handle deletion of a chip
56 | const handleDeleteChip = (chipIndex) => {
57 | // Filter the chips array to remove the chip with the given index
58 | const newChips = chips.filter((_, index) => index !== chipIndex)
59 | setChips(newChips)
60 | }
61 |
62 | // Render the component
63 | return (
64 |
65 | {/* Render the label for the input */}
66 |
67 | {label} *
68 |
69 | {/* Render the chips and input */}
70 |
71 | {/* Map over the chips array and render each chip */}
72 | {chips.map((chip, index) => (
73 |
77 | {/* Render the chip value */}
78 | {chip}
79 | {/* Render the button to delete the chip */}
80 | handleDeleteChip(index)}
84 | >
85 |
86 |
87 |
88 | ))}
89 | {/* Render the input for adding new chips */}
90 |
98 |
99 | {/* Render an error message if the input is required and not filled */}
100 | {errors[name] && (
101 |
102 | {label} is required
103 |
104 | )}
105 |
106 | )
107 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/AddCourse/CourseInformation/RequirementField.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { useSelector } from "react-redux"
3 |
4 | export default function RequirementsField({
5 | name,
6 | label,
7 | register,
8 | setValue,
9 | errors,
10 | getValues,
11 | }) {
12 | const { editCourse, course } = useSelector((state) => state.course)
13 | const [requirement, setRequirement] = useState("")
14 | const [requirementsList, setRequirementsList] = useState([])
15 |
16 | useEffect(() => {
17 | if (editCourse) {
18 | setRequirementsList(course?.instructions)
19 | }
20 | register(name, { required: true, validate: (value) => value.length > 0 })
21 | // eslint-disable-next-line react-hooks/exhaustive-deps
22 | }, [])
23 |
24 | useEffect(() => {
25 | setValue(name, requirementsList)
26 | // eslint-disable-next-line react-hooks/exhaustive-deps
27 | }, [requirementsList])
28 |
29 | const handleAddRequirement = () => {
30 | if (requirement) {
31 | setRequirementsList([...requirementsList, requirement])
32 | setRequirement("")
33 | }
34 | }
35 |
36 | const handleRemoveRequirement = (index) => {
37 | const updatedRequirements = [...requirementsList]
38 | updatedRequirements.splice(index, 1)
39 | setRequirementsList(updatedRequirements)
40 | }
41 |
42 | return (
43 |
44 |
45 | {label} *
46 |
47 |
48 | setRequirement(e.target.value)}
53 | className="form-style w-full"
54 | />
55 |
60 | Add
61 |
62 |
63 | {requirementsList.length > 0 && (
64 |
65 | {requirementsList.map((requirement, index) => (
66 |
67 | {requirement}
68 | handleRemoveRequirement(index)}
72 | >
73 | clear
74 |
75 |
76 | ))}
77 |
78 | )}
79 | {errors[name] && (
80 |
81 | {label} is required
82 |
83 | )}
84 |
85 | )
86 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/AddCourse/PublishCourse/index.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { useForm } from "react-hook-form"
3 | import { useDispatch, useSelector } from "react-redux"
4 | import { useNavigate } from "react-router-dom"
5 |
6 | import { editCourseDetails } from "../../../../../services/operations/courseDetailsAPI"
7 | import { resetCourseState, setStep } from "../../../../../slices/courseSlice"
8 | import { COURSE_STATUS } from "../../../../../utils/constants"
9 | import IconBtn from "../../../../common/IconBtn"
10 |
11 | export default function PublishCourse() {
12 | const { register, handleSubmit, setValue, getValues } = useForm()
13 |
14 | const dispatch = useDispatch()
15 | const navigate = useNavigate()
16 | const { token } = useSelector((state) => state.auth)
17 | const { course } = useSelector((state) => state.course)
18 | const [loading, setLoading] = useState(false)
19 |
20 | useEffect(() => {
21 | if (course?.status === COURSE_STATUS.PUBLISHED) {
22 | setValue("public", true)
23 | }
24 | }, [])
25 |
26 | const goBack = () => {
27 | dispatch(setStep(2))
28 | }
29 |
30 | const goToCourses = () => {
31 | dispatch(resetCourseState())
32 | navigate("/dashboard/my-courses")
33 | }
34 |
35 | const handleCoursePublish = async () => {
36 | // check if form has been updated or not
37 | if (
38 | (course?.status === COURSE_STATUS.PUBLISHED &&
39 | getValues("public") === true) ||
40 | (course?.status === COURSE_STATUS.DRAFT && getValues("public") === false)
41 | ) {
42 | // form has not been updated
43 | // no need to make api call
44 | goToCourses()
45 | return
46 | }
47 | const formData = new FormData()
48 | formData.append("courseId", course._id)
49 | const courseStatus = getValues("public")
50 | ? COURSE_STATUS.PUBLISHED
51 | : COURSE_STATUS.DRAFT
52 | formData.append("status", courseStatus)
53 | setLoading(true)
54 | const result = await editCourseDetails(formData, token)
55 | if (result) {
56 | goToCourses()
57 | }
58 | setLoading(false)
59 | }
60 |
61 | const onSubmit = (data) => {
62 | // console.log(data)
63 | handleCoursePublish()
64 | }
65 |
66 | return (
67 |
68 |
69 | Publish Settings
70 |
71 |
100 |
101 | )
102 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/AddCourse/RenderSteps.jsx:
--------------------------------------------------------------------------------
1 | import { FaCheck } from "react-icons/fa"
2 | import { useSelector } from "react-redux"
3 |
4 | import CourseBuilderForm from "./CourseBuilder/CourseBuilderForm"
5 | import CourseInformationForm from "./CourseInformation/CourseInformationForm"
6 | import PublishCourse from "./PublishCourse"
7 |
8 |
9 | export default function RenderSteps() {
10 | const { step } = useSelector((state) => state.course)
11 |
12 | const steps = [
13 | {
14 | id: 1,
15 | title: "Course Information",
16 | },
17 | {
18 | id: 2,
19 | title: "Course Builder",
20 | },
21 | {
22 | id: 3,
23 | title: "Publish",
24 | },
25 | ]
26 |
27 | return (
28 | <>
29 |
30 | {steps.map((item) => (
31 | <>
32 |
36 | item.id && "bg-yellow-50 text-yellow-50"}} `}
42 | >
43 | {step > item.id ? (
44 |
45 | ) : (
46 | item.id
47 | )}
48 |
49 |
50 |
51 | {item.id !== steps.length && (
52 | <>
53 |
item.id ? "border-yellow-50" : "border-richblack-500"
56 | } `}
57 | >
58 | >
59 | )}
60 | >
61 | ))}
62 |
63 |
64 |
65 | {steps.map((item) => (
66 | <>
67 |
71 |
72 |
= item.id ? "text-richblack-5" : "text-richblack-500"
75 | }`}
76 | >
77 | {item.title}
78 |
79 |
80 |
81 | >
82 | ))}
83 |
84 | {/* Render specific component based on current step */}
85 | {step === 1 && }
86 | {step === 2 && }
87 | {step === 3 && }
88 | >
89 | )
90 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/AddCourse/index.jsx:
--------------------------------------------------------------------------------
1 | import RenderSteps from "./RenderSteps"
2 |
3 | export default function AddCourse() {
4 | return (
5 | <>
6 |
7 |
8 |
9 | Add Course
10 |
11 |
12 |
13 |
14 |
15 | {/* Course Upload Tips */}
16 |
17 |
⚡ Course Upload Tips
18 |
19 | Set the Course Price option or make it free.
20 | Standard size for the course thumbnail is 1024x576.
21 | Video section controls the course overview video.
22 | Course Builder is where you create & organize a course.
23 |
24 | Add Topics in the Course Builder section to create lessons,
25 | quizzes, and assignments.
26 |
27 |
28 | Information from the Additional Data section shows up on the
29 | course single page.
30 |
31 | Make Announcements to notify any important
32 | Notes to all enrolled students at once.
33 |
34 |
35 |
36 | >
37 | )
38 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Cart/RenderCartCourses.jsx:
--------------------------------------------------------------------------------
1 | import { FaStar } from "react-icons/fa"
2 | import { RiDeleteBin6Line } from "react-icons/ri"
3 | import ReactStars from "react-rating-stars-component"
4 | import { useDispatch, useSelector } from "react-redux"
5 |
6 | import { removeFromCart } from "../../../../slices/cartSlice"
7 |
8 | export default function RenderCartCourses() {
9 | const { cart } = useSelector((state) => state.cart)
10 | const dispatch = useDispatch()
11 | return (
12 |
13 | {cart.map((course, indx) => (
14 |
20 |
21 |
26 |
27 |
28 | {course?.courseName}
29 |
30 |
31 | {course?.category?.name}
32 |
33 |
34 | 4.5
35 | }
42 | fullIcon={ }
43 | />
44 |
45 | {course?.ratingAndReviews?.length} Ratings
46 |
47 |
48 |
49 |
50 |
51 |
dispatch(removeFromCart(course._id))}
53 | className="flex items-center gap-x-1 rounded-md border border-richblack-600 bg-richblack-700 py-3 px-[12px] text-pink-200"
54 | >
55 |
56 | Remove
57 |
58 |
59 | ₹ {course?.price}
60 |
61 |
62 |
63 | ))}
64 |
65 | )
66 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Cart/RenderTotalAmount.jsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux"
2 | import { useNavigate } from "react-router-dom"
3 |
4 | import IconBtn from "../../../common/IconBtn"
5 | import { buyCourse } from "../../../../services/operations/studentFeaturesAPI"
6 |
7 | export default function RenderTotalAmount() {
8 | const { total, cart } = useSelector((state) => state.cart)
9 | const { token } = useSelector((state) => state.auth)
10 | const { user } = useSelector((state) => state.profile)
11 | const navigate = useNavigate()
12 | const dispatch = useDispatch()
13 |
14 | const handleBuyCourse = () => {
15 | const courses = cart.map((course) => course._id)
16 | buyCourse(token, courses, user, navigate, dispatch)
17 | }
18 |
19 | return (
20 |
21 |
Total:
22 |
₹ {total}
23 |
28 |
29 | )
30 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Cart/index.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux"
2 |
3 | import RenderCartCourses from "./RenderCartCourses"
4 | import RenderTotalAmount from "./RenderTotalAmount"
5 |
6 | export default function Cart() {
7 | const { total, totalItems } = useSelector((state) => state.cart)
8 |
9 | return (
10 | <>
11 | Cart
12 |
13 | {totalItems} Courses in Cart
14 |
15 | {total > 0 ? (
16 |
17 |
18 |
19 |
20 | ) : (
21 |
22 | Your cart is empty
23 |
24 | )}
25 | >
26 | )
27 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/EditCourse/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import { useParams } from "react-router-dom"
4 |
5 | import {
6 | fetchCourseDetails,
7 | getFullDetailsOfCourse,
8 | } from "../../../../services/operations/courseDetailsAPI"
9 | import { setCourse, setEditCourse } from "../../../../slices/courseSlice"
10 | import RenderSteps from "../AddCourse/RenderSteps"
11 |
12 | export default function EditCourse() {
13 | const dispatch = useDispatch()
14 | const { courseId } = useParams()
15 | const { course } = useSelector((state) => state.course)
16 | const [loading, setLoading] = useState(false)
17 | const { token } = useSelector((state) => state.auth)
18 |
19 | useEffect(() => {
20 | ;(async () => {
21 | setLoading(true)
22 | const result = await getFullDetailsOfCourse(courseId, token)
23 | if (result?.courseDetails) {
24 | dispatch(setEditCourse(true))
25 | dispatch(setCourse(result?.courseDetails))
26 | }
27 | setLoading(false)
28 | })()
29 | // eslint-disable-next-line react-hooks/exhaustive-deps
30 | }, [])
31 |
32 | if (loading) {
33 | return (
34 |
37 | )
38 | }
39 |
40 | return (
41 |
42 |
43 | Edit Course
44 |
45 |
46 | {course ? (
47 |
48 | ) : (
49 |
50 | Course not found
51 |
52 | )}
53 |
54 |
55 | )
56 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/EnrolledCourses.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import ProgressBar from "@ramonak/react-progress-bar"
3 | import { BiDotsVerticalRounded } from "react-icons/bi"
4 | import { useSelector } from "react-redux"
5 | import { useNavigate } from "react-router-dom"
6 |
7 | import { getUserEnrolledCourses } from "../../../services/operations/profileAPI"
8 |
9 | export default function EnrolledCourses() {
10 | const { token } = useSelector((state) => state.auth)
11 | const navigate = useNavigate()
12 |
13 | const [enrolledCourses, setEnrolledCourses] = useState(null)
14 | const getEnrolledCourses = async () => {
15 | try {
16 | const res = await getUserEnrolledCourses(token);
17 |
18 | setEnrolledCourses(res);
19 | } catch (error) {
20 | console.log("Could not fetch enrolled courses.")
21 | }
22 | };
23 | useEffect(() => {
24 | getEnrolledCourses();
25 | }, [])
26 |
27 | return (
28 | <>
29 | Enrolled Courses
30 | {!enrolledCourses ? (
31 |
34 | ) : !enrolledCourses.length ? (
35 |
36 | You have not enrolled in any course yet.
37 | {/* TODO: Modify this Empty State */}
38 |
39 | ) : (
40 |
41 | {/* Headings */}
42 |
43 |
Course Name
44 |
Duration
45 |
Progress
46 |
47 | {/* Course Names */}
48 | {enrolledCourses.map((course, i, arr) => (
49 |
55 |
{
58 | navigate(
59 | `/view-course/${course?._id}/section/${course.courseContent?.[0]?._id}/sub-section/${course.courseContent?.[0]?.subSection?.[0]?._id}`
60 | )
61 | }}
62 | >
63 |
68 |
69 |
{course.courseName}
70 |
71 | {course.courseDescription.length > 50
72 | ? `${course.courseDescription.slice(0, 50)}...`
73 | : course.courseDescription}
74 |
75 |
76 |
77 |
{course?.totalDuration}
78 |
79 |
Progress: {course.progressPercentage || 0}%
80 |
85 |
86 |
87 | ))}
88 |
89 | )}
90 | >
91 | )
92 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/InstructorDashboard/InstructorChart.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { Chart, registerables } from "chart.js"
3 | import { Pie } from "react-chartjs-2"
4 |
5 | Chart.register(...registerables)
6 |
7 | export default function InstructorChart({ courses }) {
8 | // State to keep track of the currently selected chart
9 | const [currChart, setCurrChart] = useState("students")
10 |
11 | // Function to generate random colors for the chart
12 | const generateRandomColors = (numColors) => {
13 | const colors = []
14 | for (let i = 0; i < numColors; i++) {
15 | const color = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
16 | Math.random() * 256
17 | )}, ${Math.floor(Math.random() * 256)})`
18 | colors.push(color)
19 | }
20 | return colors
21 | }
22 |
23 | // Data for the chart displaying student information
24 | const chartDataStudents = {
25 | labels: courses.map((course) => course.courseName),
26 | datasets: [
27 | {
28 | data: courses.map((course) => course.totalStudentsEnrolled),
29 | backgroundColor: generateRandomColors(courses.length),
30 | },
31 | ],
32 | }
33 |
34 | // Data for the chart displaying income information
35 | const chartIncomeData = {
36 | labels: courses.map((course) => course.courseName),
37 | datasets: [
38 | {
39 | data: courses.map((course) => course.totalAmountGenerated),
40 | backgroundColor: generateRandomColors(courses.length),
41 | },
42 | ],
43 | }
44 |
45 | // Options for the chart
46 | const options = {
47 | maintainAspectRatio: false,
48 | }
49 |
50 | return (
51 |
52 |
Visualize
53 |
54 | {/* Button to switch to the "students" chart */}
55 | setCurrChart("students")}
57 | className={`rounded-sm p-1 px-3 transition-all duration-200 ${
58 | currChart === "students"
59 | ? "bg-richblack-700 text-yellow-50"
60 | : "text-yellow-400"
61 | }`}
62 | >
63 | Students
64 |
65 | {/* Button to switch to the "income" chart */}
66 | setCurrChart("income")}
68 | className={`rounded-sm p-1 px-3 transition-all duration-200 ${
69 | currChart === "income"
70 | ? "bg-richblack-700 text-yellow-50"
71 | : "text-yellow-400"
72 | }`}
73 | >
74 | Income
75 |
76 |
77 |
78 | {/* Render the Pie chart based on the selected chart */}
79 |
83 |
84 |
85 | )
86 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/MyCourses.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { VscAdd } from "react-icons/vsc"
3 | import { useSelector } from "react-redux"
4 | import { useNavigate } from "react-router-dom"
5 |
6 | import { fetchInstructorCourses } from "../../../services/operations/courseDetailsAPI"
7 | import IconBtn from "../../common/IconBtn"
8 | import CoursesTable from "./InstructorCourses/CoursesTable"
9 |
10 | export default function MyCourses() {
11 | const { token } = useSelector((state) => state.auth)
12 | const navigate = useNavigate()
13 | const [courses, setCourses] = useState([])
14 |
15 | useEffect(() => {
16 | const fetchCourses = async () => {
17 | const result = await fetchInstructorCourses(token)
18 | if (result) {
19 | setCourses(result)
20 | }
21 | }
22 | fetchCourses()
23 | // eslint-disable-next-line react-hooks/exhaustive-deps
24 | }, [])
25 |
26 | return (
27 |
28 |
29 |
My Courses
30 | navigate("/dashboard/add-course")}
33 | >
34 |
35 |
36 |
37 | {courses &&
}
38 |
39 | )
40 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/ChangeProfilePicture.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react"
2 | import { FiUpload } from "react-icons/fi"
3 | import { useDispatch, useSelector } from "react-redux"
4 |
5 | import { updateDisplayPicture } from "../../../../services/operations/SettingsAPI"
6 | import IconBtn from "../../../common/IconBtn"
7 |
8 | export default function ChangeProfilePicture() {
9 | const { token } = useSelector((state) => state.auth)
10 | const { user } = useSelector((state) => state.profile)
11 | const dispatch = useDispatch()
12 |
13 | const [loading, setLoading] = useState(false)
14 | const [imageFile, setImageFile] = useState(null)
15 | const [previewSource, setPreviewSource] = useState(null)
16 |
17 | const fileInputRef = useRef(null)
18 |
19 | const handleClick = () => {
20 | fileInputRef.current.click()
21 | }
22 |
23 | const handleFileChange = (e) => {
24 | const file = e.target.files[0]
25 | // console.log(file)
26 | if (file) {
27 | setImageFile(file)
28 | previewFile(file)
29 | }
30 | }
31 |
32 | const previewFile = (file) => {
33 | const reader = new FileReader()
34 | reader.readAsDataURL(file)
35 | reader.onloadend = () => {
36 | setPreviewSource(reader.result)
37 | }
38 | }
39 |
40 | const handleFileUpload = () => {
41 | try {
42 | console.log("uploading...")
43 | setLoading(true)
44 | const formData = new FormData()
45 | formData.append("displayPicture", imageFile)
46 | // console.log("formdata", formData)
47 | dispatch(updateDisplayPicture(token, formData)).then(() => {
48 | setLoading(false)
49 | })
50 | } catch (error) {
51 | console.log("ERROR MESSAGE - ", error.message)
52 | }
53 | }
54 |
55 | useEffect(() => {
56 | if (imageFile) {
57 | previewFile(imageFile)
58 | }
59 | }, [imageFile])
60 | return (
61 | <>
62 |
63 |
64 |
69 |
70 |
Change Profile Picture
71 |
72 |
79 |
84 | Select
85 |
86 |
90 | {!loading && (
91 |
92 | )}
93 |
94 |
95 |
96 |
97 |
98 | >
99 | )
100 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/DeleteAccount.jsx:
--------------------------------------------------------------------------------
1 | import { FiTrash2 } from "react-icons/fi"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import { useNavigate } from "react-router-dom"
4 |
5 | import { deleteProfile } from "../../../../services/operations/SettingsAPI"
6 |
7 | export default function DeleteAccount() {
8 | const { token } = useSelector((state) => state.auth)
9 | const dispatch = useDispatch()
10 | const navigate = useNavigate()
11 |
12 | async function handleDeleteAccount() {
13 | try {
14 | dispatch(deleteProfile(token, navigate))
15 | } catch (error) {
16 | console.log("ERROR MESSAGE - ", error.message)
17 | }
18 | }
19 |
20 | return (
21 | <>
22 |
23 |
24 |
25 |
26 |
27 |
28 | Delete Account
29 |
30 |
31 |
Would you like to delete account?
32 |
33 | This account may contain Paid Courses. Deleting your account is
34 | permanent and will remove all the contain associated with it.
35 |
36 |
37 |
42 | I want to delete my account.
43 |
44 |
45 |
46 | >
47 | )
48 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/index.jsx:
--------------------------------------------------------------------------------
1 | import ChangeProfilePicture from "./ChangeProfilePicture"
2 | import DeleteAccount from "./DeleteAccount"
3 | import EditProfile from "./EditProfile"
4 | import UpdatePassword from "./UpdatePassword"
5 |
6 | export default function Settings() {
7 | return (
8 | <>
9 |
10 | Edit Profile
11 |
12 | {/* Change Profile Picture */}
13 |
14 | {/* Profile */}
15 |
16 | {/* Password */}
17 |
18 | {/* Delete Account */}
19 |
20 | >
21 | )
22 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { VscSignOut } from "react-icons/vsc"
3 | import { useDispatch, useSelector } from "react-redux"
4 | import { useNavigate } from "react-router-dom"
5 |
6 | import { sidebarLinks } from "../../../data/dashboard-links"
7 | import { logout } from "../../../services/operations/authAPI"
8 | import ConfirmationModal from "../../common/ConfirmationModal"
9 | import SidebarLink from "./SidebarLink"
10 |
11 | export default function Sidebar() {
12 | const { user, loading: profileLoading } = useSelector(
13 | (state) => state.profile
14 | )
15 | const { loading: authLoading } = useSelector((state) => state.auth)
16 | const dispatch = useDispatch()
17 | const navigate = useNavigate()
18 | const [confirmationModal, setConfirmationModal] = useState(null)
19 |
20 | if (profileLoading || authLoading) {
21 | return (
22 |
25 | )
26 | }
27 |
28 | return (
29 | <>
30 |
31 |
32 | {sidebarLinks.map((link) => {
33 | if (link.type && user?.accountType !== link.type) return null
34 | return (
35 |
36 | )
37 | })}
38 |
39 |
40 |
41 |
45 |
47 | setConfirmationModal({
48 | text1: "Are you sure?",
49 | text2: "You will be logged out of your account.",
50 | btn1Text: "Logout",
51 | btn2Text: "Cancel",
52 | btn1Handler: () => dispatch(logout(navigate)),
53 | btn2Handler: () => setConfirmationModal(null),
54 | })
55 | }
56 | className="mx-auto mt-6 px-4 py-2 text-sm font-medium text-richblack-300"
57 | >
58 |
59 |
60 | Logout
61 |
62 |
63 |
64 |
65 | {confirmationModal && }
66 | >
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/core/Dashboard/SidebarLink.jsx:
--------------------------------------------------------------------------------
1 | import * as Icons from "react-icons/vsc"
2 | import { useDispatch } from "react-redux"
3 | import { NavLink, matchPath, useLocation } from "react-router-dom"
4 |
5 | import { resetCourseState } from "../../../slices/courseSlice"
6 |
7 | export default function SidebarLink({ link, iconName }) {
8 | const Icon = Icons[iconName]
9 | const location = useLocation()
10 | const dispatch = useDispatch()
11 |
12 | const matchRoute = (route) => {
13 | return matchPath({ path: route }, location.pathname)
14 | }
15 |
16 | return (
17 | dispatch(resetCourseState())}
20 | className={`relative px-8 py-2 text-sm font-medium ${
21 | matchRoute(link.path)
22 | ? "bg-yellow-800 text-yellow-50"
23 | : "bg-opacity-0 text-richblack-300"
24 | } transition-all duration-200`}
25 | >
26 |
31 |
32 | {/* Icon Goes Here */}
33 |
34 | {link.name}
35 |
36 |
37 | )
38 | }
--------------------------------------------------------------------------------
/src/components/core/HomePage/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | const Button = ({ children, active, linkto }) => {
5 | return (
6 |
7 |
12 | {children}
13 |
14 |
15 | );
16 | };
17 |
18 | export default Button;
--------------------------------------------------------------------------------
/src/components/core/HomePage/CodeBlocks.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CTAButton from "./Button";
3 | import { TypeAnimation } from "react-type-animation";
4 | import { FaArrowRight } from "react-icons/fa";
5 |
6 | const CodeBlocks = ({
7 | position,
8 | heading,
9 | subheading,
10 | ctabtn1,
11 | ctabtn2,
12 | codeblock,
13 | backgroundGradient,
14 | codeColor,
15 | }) => {
16 | return (
17 |
18 |
19 |
20 | {/* Section 1 */}
21 |
22 | {heading}
23 |
24 | {/* Sub Heading */}
25 |
26 | {subheading}
27 |
28 |
29 | {/* Button Group */}
30 |
31 |
32 |
33 | {ctabtn1.btnText}
34 |
35 |
36 |
37 |
38 | {ctabtn2.btnText}
39 |
40 |
41 |
42 |
43 | {/* Section 2 */}
44 |
45 | {backgroundGradient}
46 | {/* Indexing */}
47 |
48 |
1
49 |
2
50 |
3
51 |
4
52 |
5
53 |
6
54 |
7
55 |
8
56 |
9
57 |
10
58 |
11
59 |
60 |
61 | {/* Codes */}
62 |
65 |
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export default CodeBlocks;
--------------------------------------------------------------------------------
/src/components/core/HomePage/CourseCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | // Importing React Icons
4 | import { HiUsers } from "react-icons/hi";
5 | import { ImTree } from "react-icons/im";
6 |
7 | const CourseCard = ({cardData, currentCard, setCurrentCard}) => {
8 | return (
9 | setCurrentCard(cardData?.heading)}
16 | >
17 |
18 |
23 | {cardData?.heading}
24 |
25 |
26 |
33 | {cardData?.description}
34 |
35 |
36 |
37 |
44 | {/* Level */}
45 |
46 |
47 |
{cardData?.level}
48 |
49 |
50 | {/* Flow Chart */}
51 |
52 |
53 |
{cardData?.lessonNumber}Lesson
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default CourseCard;
--------------------------------------------------------------------------------
/src/components/core/HomePage/ExploreMore.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { HomePageExplore } from "../../../data/homepage-explore";
3 | import CourseCard from "./CourseCard";
4 | import HighlightText from "./HighlightText";
5 |
6 | const tabsName = [
7 | "Free",
8 | "New to coding",
9 | "Most popular",
10 | "Skills paths",
11 | "Career paths",
12 | ];
13 |
14 | const ExploreMore = () => {
15 | const [currentTab, setCurrentTab] = useState(tabsName[0]);
16 | const [courses, setCourses] = useState(HomePageExplore[0].courses);
17 | const [currentCard, setCurrentCard] = useState(
18 | HomePageExplore[0].courses[0].heading
19 | );
20 |
21 | const setMyCards = (value) => {
22 | setCurrentTab(value);
23 | const result = HomePageExplore.filter((course) => course.tag === value);
24 | setCourses(result[0].courses);
25 | setCurrentCard(result[0].courses[0].heading);
26 | };
27 |
28 | return (
29 |
30 | {/* Explore more section */}
31 |
32 |
33 |
34 | Unlock the
35 |
36 |
37 |
38 | Learn to Build Anything You Can Imagine
39 |
40 |
41 |
42 |
43 | {/* Tabs Section */}
44 |
45 | {tabsName.map((ele, index) => {
46 | return (
47 |
setMyCards(ele)}
54 | >
55 | {ele}
56 |
57 | );
58 | })}
59 |
60 |
61 |
62 |
63 | {/* Cards Group */}
64 |
65 | {courses.map((ele, index) => {
66 | return (
67 |
73 | );
74 | })}
75 |
76 |
77 | );
78 | };
79 |
80 | export default ExploreMore;
--------------------------------------------------------------------------------
/src/components/core/HomePage/HighlightText.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const HighlightText = ({text}) => {
4 | return (
5 |
6 | {" "}
7 | {text}
8 |
9 | );
10 | };
11 |
12 | export default HighlightText;
--------------------------------------------------------------------------------
/src/components/core/HomePage/InstructorSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CTAButton from "../../../components/core/HomePage/Button";
3 | import { FaArrowRight } from "react-icons/fa";
4 | import Instructor from "../../../assets/Images/Instructor.png";
5 | import HighlightText from './HighlightText';
6 |
7 | const InstructorSection = () => {
8 | return (
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 | Become an
21 |
22 |
23 |
24 |
25 | Instructors from around the world teach millions of students on
26 | StudyNotion. We provide the tools and skills to teach what you
27 | love.
28 |
29 |
30 |
31 |
32 |
33 | Start Teaching Today
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default InstructorSection
--------------------------------------------------------------------------------
/src/components/core/HomePage/LearningLanguageSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from './HighlightText'
3 | import CTAButton from "../../../components/core/HomePage/Button";
4 | import Know_your_progress from "../../../assets/Images/Know_your_progress.png";
5 | import Compare_with_others from "../../../assets/Images/Compare_with_others.svg";
6 | import Plan_your_lessons from "../../../assets/Images/Plan_your_lessons.svg";
7 |
8 | const LearningLanguageSection = () => {
9 | return (
10 |
11 |
12 | Your swiss knife for
13 |
14 |
15 | Using spin making learning multiple languages easy. with 20+
16 | languages realistic voice-over, progress tracking, custom schedule
17 | and more.
18 |
19 |
36 |
37 |
38 |
39 |
40 | Learn More
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default LearningLanguageSection
--------------------------------------------------------------------------------
/src/components/core/HomePage/TimelineSection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TimeLineImage from "../../../assets/Images/TimelineImage.png";
3 | import Logo1 from "../../../assets/TimeLineLogo/Logo1.svg";
4 | import Logo2 from "../../../assets/TimeLineLogo/Logo2.svg";
5 | import Logo3 from "../../../assets/TimeLineLogo/Logo3.svg";
6 | import Logo4 from "../../../assets/TimeLineLogo/Logo4.svg";
7 |
8 | const TimeLine = [
9 | {
10 | Logo: Logo1,
11 | Heading: "Leadership",
12 | Description: "Fully committed to the success company",
13 | },
14 | {
15 | Logo: Logo2,
16 | Heading: "Responsibility",
17 | Description: "Students will always be our top priority",
18 | },
19 | {
20 | Logo: Logo3,
21 | Heading: "Flexibility",
22 | Description: "The ability to switch is an important skills",
23 | },
24 | {
25 | Logo: Logo4,
26 | Heading: "Solve the problem",
27 | Description: "Code your way to a solution",
28 | },
29 | ];
30 |
31 |
32 | const TimelineSection = () => {
33 | return (
34 |
35 |
36 |
37 | {TimeLine.map((ele, i) => {
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
{ele.Heading}
46 |
{ele.Description}
47 |
48 |
49 |
54 |
55 | );
56 | })}
57 |
58 |
59 |
60 | {/* Section 1 */}
61 |
62 |
10
63 |
64 | Years experiences
65 |
66 |
67 |
68 | {/* Section 2 */}
69 |
70 |
250
71 |
72 | types of courses
73 |
74 |
75 |
76 |
77 |
82 |
83 |
84 |
85 | );
86 | };
87 |
88 | export default TimelineSection;
--------------------------------------------------------------------------------
/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: "Your Cart",
40 | path: "/dashboard/cart",
41 | type: ACCOUNT_TYPE.STUDENT,
42 | icon: "VscHistory",
43 | },
44 | ];
45 |
--------------------------------------------------------------------------------
/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: "Full Catalog", link: "/full-catalog"},
51 | {title: "Beta Content", link: "/beta-content"}
52 | ]
53 | }
54 | ];
55 |
--------------------------------------------------------------------------------
/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: "Project",
12 | path: "/project",
13 | },
14 | {
15 | title: "About Us",
16 | path: "/about",
17 | },
18 | {
19 | title: "Contact Us",
20 | path: "/contact",
21 | },
22 | {
23 | title:"Rate Us",
24 | path:"/rateus",
25 | }
26 | ];
27 |
--------------------------------------------------------------------------------
/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/hooks/useRouteMatch.js:
--------------------------------------------------------------------------------
1 | import { useLocation, matchPath } from "react-router-dom";
2 |
3 | export default function useRouteMatch(path) {
4 | const location = useLocation();
5 | return matchPath(location.pathname, { path });
6 | }
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 |
--------------------------------------------------------------------------------
/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 { Provider } from "react-redux";
7 | import rootReducer from "./reducer";
8 | import {configureStore} from "@reduxjs/toolkit"
9 | import { Toaster } from "react-hot-toast";
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 |
28 | );
29 |
--------------------------------------------------------------------------------
/src/pages/Chatbot.jsx:
--------------------------------------------------------------------------------
1 | // src/components/Chatbot.js
2 | import React, { useState, useEffect } from 'react';
3 | import './Rating.css';
4 |
5 | const Chatbot = () => {
6 | const [messages, setMessages] = useState([]);
7 | const [input, setInput] = useState('');
8 | const [visible, setVisible] = useState(false);
9 |
10 | const faqData = {
11 | "What is your return policy?": "Our return policy is 30 days with a receipt.",
12 | "What are your hours of operation?": "We are open from 9am to 9pm, Monday to Saturday.",
13 | "Where can I find the courses?": "You can find them all on the landing page of the website.",
14 | "Where can I find projects to learn?": "You can find them all at the Projects section collectively.",
15 | "Will this be really beneficial for me?": "Yes, of course, if you are a true learner."
16 | // Add more FAQs here
17 |
18 | };
19 |
20 | useEffect(() => {
21 | // Add initial message on component mount
22 | setMessages([{ text: "Hello, How can I help you?", sender: 'bot' }]);
23 | }, []);
24 |
25 | const handleSend = () => {
26 | if (input.trim()) {
27 | const userMessage = { text: input, sender: 'user' };
28 | const botResponse = faqData[input.trim()] || "Sorry, I can able to answer for courses, projects & return policy.";
29 |
30 | setMessages([...messages, userMessage, { text: botResponse, sender: 'bot' }]);
31 |
32 | setInput('');
33 | }
34 | };
35 |
36 | const handleKeyPress = (e) => {
37 | if (e.key === 'Enter') {
38 |
39 | handleSend();
40 | }
41 | };
42 |
43 | return (
44 |
45 |
setVisible(!visible)}>
46 | 💬
47 |
48 | {visible && (
49 |
50 |
51 |
FAQ Chatbot
52 | setVisible(false)}>X
53 |
54 |
55 | {messages.map((msg, index) => (
56 |
57 | {msg.text}
58 |
59 | ))}
60 |
61 |
62 | setInput(e.target.value)}
66 | onKeyPress={handleKeyPress}
67 | placeholder="Ask a question..."
68 | />
69 | Send
70 |
71 |
72 | )}
73 |
74 | );
75 | };
76 |
77 | export default Chatbot;
78 |
--------------------------------------------------------------------------------
/src/pages/Contact.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Footer from "../components/common/Footer"
3 | import ContactDetails from "../components/ContactPage/ContactDetails"
4 | import ContactForm from "../components/ContactPage/ContactForm"
5 | import ReviewSlider from "../components/common/ReviewSlider"
6 |
7 | const Contact = () => {
8 | return (
9 |
10 |
11 | {/* Contact Details */}
12 |
13 |
14 |
15 |
16 | {/* Contact Form */}
17 |
18 |
19 |
20 |
21 |
22 | {/* Reviws from Other Learner */}
23 |
24 | Happy & Satisfied Learners!
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default Contact
--------------------------------------------------------------------------------
/src/pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux"
2 | import { Outlet } from "react-router-dom"
3 |
4 | import Sidebar from "../components/core/Dashboard/Sidebar"
5 |
6 | function Dashboard() {
7 | const { loading: profileLoading } = useSelector((state) => state.profile)
8 | const { loading: authLoading } = useSelector((state) => state.auth)
9 |
10 | if (profileLoading || authLoading) {
11 | return (
12 |
15 | )
16 | }
17 |
18 | return (
19 |
27 | )
28 | }
29 |
30 | export default Dashboard
31 |
--------------------------------------------------------------------------------
/src/pages/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CTAButton from "../components/core/HomePage/Button"
3 | import backgroundImage from '../assets/Images/404.png';
4 |
5 | const Error = () => {
6 | return (
7 |
11 |
12 |
Oops! It looks like you're lost.
13 |
14 |
15 | Go to Homepage
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default Error
24 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { BiArrowBack } from "react-icons/bi"
3 | import { useDispatch, useSelector } from "react-redux"
4 | import { Link } from "react-router-dom"
5 |
6 | import { getPasswordResetToken } from "../services/operations/authAPI"
7 |
8 | function ForgotPassword() {
9 | const [email, setEmail] = useState("")
10 | const [emailSent, setEmailSent] = useState(false)
11 | const dispatch = useDispatch()
12 | const { loading } = useSelector((state) => state.auth)
13 |
14 | const handleOnSubmit = (e) => {
15 | e.preventDefault()
16 | dispatch(getPasswordResetToken(email, setEmailSent))
17 | }
18 |
19 | return (
20 |
21 | {loading ? (
22 |
23 | ) : (
24 |
25 |
26 | {!emailSent ? "Reset your password" : "Check email"}
27 |
28 |
29 | {!emailSent
30 | ? "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"
31 | : `We have sent the reset email to ${email}`}
32 |
33 |
57 |
58 |
59 |
60 | Back To Login
61 |
62 |
63 |
64 |
65 | )}
66 |
67 | )
68 | }
69 |
70 | export default ForgotPassword
--------------------------------------------------------------------------------
/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/pages/Rating.css:
--------------------------------------------------------------------------------
1 | /*Chatbot*/
2 |
3 | .chatbot-icon {
4 | position: fixed;
5 | bottom: 4.5rem;
6 | right: 25px;
7 | background-color: #007bff;
8 | color: white;
9 | width: 40px;
10 | height: 40px;
11 | border-radius: 50%;
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | cursor: pointer;
16 | font-size: 24px;
17 | z-index: 1000;
18 | }
19 |
20 | .chatbot {
21 | position: fixed;
22 | bottom:70px;
23 | right: 20px;
24 | width: 300px;
25 | height: 400px;
26 | border: 1px solid #ccc;
27 | display: flex;
28 | flex-direction: column;
29 | justify-content: space-between;
30 | background-color: white;
31 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
32 | z-index: 1000;
33 | }
34 |
35 | .chatbot-header {
36 | display: flex;
37 | justify-content: space-between;
38 | align-items: center;
39 | padding: 10px;
40 | border-bottom: 1px solid #ccc;
41 | background-color: #007bff;
42 | color: white;
43 | }
44 |
45 | .chatbot-messages {
46 | padding: 10px;
47 | flex-grow: 1;
48 | overflow-y: auto;
49 | }
50 |
51 | .message {
52 | margin-bottom: 10px;
53 | padding: 10px;
54 | border-radius: 5px;
55 | }
56 |
57 | .message.user {
58 | background-color: #007bff;
59 | color: white;
60 | align-self: flex-end;
61 | }
62 |
63 | .message.bot {
64 | background-color: #f1f1f1;
65 | align-self: flex-start;
66 | }
67 |
68 | .chatbot-input {
69 | display: flex;
70 | border-top: 1px solid #ccc;
71 | }
72 |
73 | .chatbot-input input {
74 | flex-grow: 1;
75 | border: none;
76 | padding: 10px;
77 | }
78 |
79 | .chatbot-input button {
80 | border: none;
81 | background-color: #007bff;
82 | color: white;
83 | padding: 10px;
84 | cursor: pointer;
85 | }
86 |
87 | .chatbot-input button:hover {
88 | background-color: #0056b3;
89 | }
90 |
--------------------------------------------------------------------------------
/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/pages/VerifyEmail.jsx:
--------------------------------------------------------------------------------
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 | function VerifyEmail() {
11 | const [otp, setOtp] = useState("");
12 | const { signupData, loading } = useSelector((state) => state.auth);
13 | const dispatch = useDispatch();
14 | const navigate = useNavigate();
15 |
16 | useEffect(() => {
17 | // Only allow access of this route when user has filled the signup form
18 | if (!signupData) {
19 | navigate("/signup");
20 | }
21 | // eslint-disable-next-line react-hooks/exhaustive-deps
22 | }, []);
23 |
24 | const handleVerifyAndSignup = (e) => {
25 | e.preventDefault();
26 | const {
27 | accountType,
28 | firstName,
29 | lastName,
30 | email,
31 | password,
32 | confirmPassword,
33 | } = signupData;
34 |
35 | dispatch(
36 | signUp(
37 | accountType,
38 | firstName,
39 | lastName,
40 | email,
41 | password,
42 | confirmPassword,
43 | otp,
44 | navigate
45 | )
46 | );
47 | };
48 |
49 | return (
50 |
51 | {loading ? (
52 |
55 | ) : (
56 |
57 |
58 | Verify Email
59 |
60 |
61 | A verification code has been sent to you. Enter the code below
62 |
63 |
90 |
91 |
92 |
93 | Back To Signup
94 |
95 |
96 |
dispatch(sendOtp(signupData.email))}
99 | >
100 |
101 | Resend it
102 |
103 |
104 |
105 | )}
106 |
107 | );
108 | }
109 |
110 | export default VerifyEmail;
--------------------------------------------------------------------------------
/src/pages/ViewCourse.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import { Outlet, useParams } from "react-router-dom"
4 |
5 | import CourseReviewModal from "../components/core/ViewCourse/CourseReviewModal"
6 | import VideoDetailsSidebar from "../components/core/ViewCourse/VideoDetailsSidebar"
7 | import { getFullDetailsOfCourse } from "../services/operations/courseDetailsAPI"
8 | import {
9 | setCompletedLectures,
10 | setCourseSectionData,
11 | setEntireCourseData,
12 | setTotalNoOfLectures,
13 | } from "../slices/viewCourseSlice"
14 |
15 | export default function ViewCourse() {
16 | const { courseId } = useParams()
17 | const { token } = useSelector((state) => state.auth)
18 | const dispatch = useDispatch()
19 | const [reviewModal, setReviewModal] = useState(false)
20 |
21 | useEffect(() => {
22 | ;(async () => {
23 | const courseData = await getFullDetailsOfCourse(courseId, token)
24 | // console.log("Course Data here... ", courseData.courseDetails)
25 | dispatch(setCourseSectionData(courseData.courseDetails.courseContent))
26 | dispatch(setEntireCourseData(courseData.courseDetails))
27 | dispatch(setCompletedLectures(courseData.completedVideos))
28 | let lectures = 0
29 | courseData?.courseDetails?.courseContent?.forEach((sec) => {
30 | lectures += sec.subSection.length
31 | })
32 | dispatch(setTotalNoOfLectures(lectures))
33 | })()
34 | // eslint-disable-next-line react-hooks/exhaustive-deps
35 | }, [])
36 |
37 | return (
38 | <>
39 |
47 | {reviewModal && }
48 | >
49 | )
50 | }
--------------------------------------------------------------------------------
/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 | profile:profileReducer,
12 | cart:cartReducer,
13 | course:courseReducer,
14 | viewCourse:viewCourseReducer,
15 | })
16 |
17 | export default rootReducer
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/services/apis.js:
--------------------------------------------------------------------------------
1 | const BASE_URL = "http://localhost:4000/api/v1"
2 | // const BASE_URL = process.env.REACT_APP_BASE_URL;
3 |
4 | // AUTH ENDPOINTS
5 | export const endpoints = {
6 | SENDOTP_API: BASE_URL + "/auth/sendotp",
7 | SIGNUP_API: BASE_URL + "/auth/signup",
8 | LOGIN_API: BASE_URL + "/auth/login",
9 | RESETPASSTOKEN_API: BASE_URL + "/auth/reset-password-token",
10 | RESETPASSWORD_API: BASE_URL + "/auth/reset-password",
11 | }
12 |
13 | // PROFILE ENDPOINTS
14 | export const profileEndpoints = {
15 | GET_USER_DETAILS_API: BASE_URL + "/profile/getUserDetails",
16 | GET_USER_ENROLLED_COURSES_API: BASE_URL + "/profile/getEnrolledCourses",
17 | GET_INSTRUCTOR_DATA_API: BASE_URL + "/profile/instructorDashboard",
18 | }
19 |
20 | // STUDENTS ENDPOINTS
21 | export const studentEndpoints = {
22 | COURSE_PAYMENT_API: BASE_URL + "/payment/capturePayment",
23 | COURSE_VERIFY_API: BASE_URL + "/payment/verifyPayment",
24 | SEND_PAYMENT_SUCCESS_EMAIL_API: BASE_URL + "/payment/sendPaymentSuccessEmail",
25 | }
26 |
27 | // COURSE ENDPOINTS
28 | export const courseEndpoints = {
29 | GET_ALL_COURSE_API: BASE_URL + "/course/getAllCourses",
30 | COURSE_DETAILS_API: BASE_URL + "/course/getCourseDetails",
31 | EDIT_COURSE_API: BASE_URL + "/course/editCourse",
32 | COURSE_CATEGORIES_API: BASE_URL + "/course/showAllCategories",
33 | CREATE_COURSE_API: BASE_URL + "/course/createCourse",
34 | CREATE_SECTION_API: BASE_URL + "/course/addSection",
35 | CREATE_SUBSECTION_API: BASE_URL + "/course/addSubSection",
36 | UPDATE_SECTION_API: BASE_URL + "/course/updateSection",
37 | UPDATE_SUBSECTION_API: BASE_URL + "/course/updateSubSection",
38 | GET_ALL_INSTRUCTOR_COURSES_API: BASE_URL + "/course/getInstructorCourses",
39 | DELETE_SECTION_API: BASE_URL + "/course/deleteSection",
40 | DELETE_SUBSECTION_API: BASE_URL + "/course/deleteSubSection",
41 | DELETE_COURSE_API: BASE_URL + "/course/deleteCourse",
42 | GET_FULL_COURSE_DETAILS_AUTHENTICATED:
43 | BASE_URL + "/course/getFullCourseDetails",
44 | LECTURE_COMPLETION_API: BASE_URL + "/course/updateCourseProgress",
45 | CREATE_RATING_API: BASE_URL + "/course/createRating",
46 | }
47 |
48 | // RATINGS AND REVIEWS
49 | export const ratingsEndpoints = {
50 | REVIEWS_DETAILS_API: BASE_URL + "/course/getReviews",
51 | }
52 |
53 | // CATAGORIES API
54 | export const categories = {
55 | CATEGORIES_API: BASE_URL + "/course/showAllCategories",
56 | }
57 |
58 | // CATALOG PAGE DATA
59 | export const catalogData = {
60 | CATALOGPAGEDATA_API: BASE_URL + "/course/getCategoryPageDetails",
61 | }
62 | // CONTACT-US API
63 | export const contactusEndpoint = {
64 | CONTACT_US_API: BASE_URL + "/reach/contact",
65 | }
66 |
67 | // SETTINGS PAGE API
68 | export const settingsEndpoints = {
69 | UPDATE_DISPLAY_PICTURE_API: BASE_URL + "/profile/updateDisplayPicture",
70 | UPDATE_PROFILE_API: BASE_URL + "/profile/updateProfile",
71 | CHANGE_PASSWORD_API: BASE_URL + "/auth/changepassword",
72 | DELETE_PROFILE_API: BASE_URL + "/profile/deleteProfile",
73 | }
--------------------------------------------------------------------------------
/src/services/formatDate.js:
--------------------------------------------------------------------------------
1 | export const formatDate = (dateString) => {
2 | const options = { year: "numeric", month: "long", day: "numeric" }
3 | const date = new Date(dateString)
4 | const formattedDate = date.toLocaleDateString("en-US", options)
5 |
6 | const hour = date.getHours()
7 | const minutes = date.getMinutes()
8 | const period = hour >= 12 ? "PM" : "AM"
9 | const formattedTime = `${hour % 12}:${minutes
10 | .toString()
11 | .padStart(2, "0")} ${period}`
12 |
13 | return `${formattedDate} | ${formattedTime}`
14 | }
--------------------------------------------------------------------------------
/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 |
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/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, GET_USER_ENROLLED_COURSES_API, GET_INSTRUCTOR_DATA_API } = profileEndpoints
9 |
10 | export function getUserDetails(token, navigate) {
11 | return async (dispatch) => {
12 | const toastId = toast.loading("Loading...")
13 | dispatch(setLoading(true))
14 | try {
15 | const response = await apiConnector("GET", GET_USER_DETAILS_API, null, {
16 | Authorization: `Bearer ${token}`,
17 | })
18 | console.log("GET_USER_DETAILS API RESPONSE............", response)
19 |
20 | if (!response.data.success) {
21 | throw new Error(response.data.message)
22 | }
23 | const userImage = response.data.data.image
24 | ? response.data.data.image
25 | : `https://api.dicebear.com/5.x/initials/svg?seed=${response.data.data.firstName} ${response.data.data.lastName}`
26 | dispatch(setUser({ ...response.data.data, image: userImage }))
27 | } catch (error) {
28 | dispatch(logout(navigate))
29 | console.log("GET_USER_DETAILS API ERROR............", error)
30 | toast.error("Could Not Get User Details")
31 | }
32 | toast.dismiss(toastId)
33 | dispatch(setLoading(false))
34 | }
35 | }
36 |
37 | export async function getUserEnrolledCourses(token) {
38 | const toastId = toast.loading("Loading...")
39 | let result = []
40 | try {
41 | console.log("BEFORE Calling BACKEND API FOR ENROLLED COURSES");
42 | const response = await apiConnector(
43 | "GET",
44 | GET_USER_ENROLLED_COURSES_API,
45 | null,
46 | {
47 | Authorization: `Bearer ${token}`,
48 | }
49 | )
50 | console.log("AFTER Calling BACKEND API FOR ENROLLED COURSES");
51 | // console.log(
52 | // "GET_USER_ENROLLED_COURSES_API API RESPONSE............",
53 | // response
54 | // )
55 |
56 | if (!response.data.success) {
57 | throw new Error(response.data.message)
58 | }
59 | result = response.data.data
60 | } catch (error) {
61 | console.log("GET_USER_ENROLLED_COURSES_API API ERROR............", error)
62 | toast.error("Could Not Get Enrolled Courses")
63 | }
64 | toast.dismiss(toastId)
65 | return result
66 | }
67 |
68 | export async function getInstructorData(token) {
69 | const toastId = toast.loading("Loading...");
70 | let result = [];
71 | try{
72 | const response = await apiConnector("GET", GET_INSTRUCTOR_DATA_API, null,
73 | {
74 | Authorization: `Bearer ${token}`,
75 | })
76 |
77 | console.log("GET_INSTRUCTOR_API_RESPONSE", response);
78 | result = response?.data?.courses
79 |
80 | }
81 | catch(error) {
82 | console.log("GET_INSTRUCTOR_API ERROR", error);
83 | toast.error("Could not Get Instructor Data")
84 | }
85 | toast.dismiss(toastId);
86 | return result;
87 | }
--------------------------------------------------------------------------------
/src/slices/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | signupData: null,
5 | loading: false,
6 | token: localStorage.getItem("token") ? JSON.parse(localStorage.getItem("token")) : null,
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/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")
6 | ? JSON.parse(localStorage.getItem("cart"))
7 | : [],
8 | total: localStorage.getItem("total")
9 | ? JSON.parse(localStorage.getItem("total"))
10 | : 0,
11 | totalItems: localStorage.getItem("totalItems")
12 | ? JSON.parse(localStorage.getItem("totalItems"))
13 | : 0,
14 | }
15 |
16 | const cartSlice = createSlice({
17 | name: "cart",
18 | initialState,
19 | reducers: {
20 | addToCart: (state, action) => {
21 | const course = action.payload
22 | const index = state.cart.findIndex((item) => item._id === course._id)
23 |
24 | if (index >= 0) {
25 | // If the course is already in the cart, do not modify the quantity
26 | toast.error("Course already in cart")
27 | return
28 | }
29 | // If the course is not in the cart, add it to the cart
30 | state.cart.push(course)
31 | // Update the total quantity and price
32 | state.totalItems++
33 | state.total += course.price
34 | // Update to localstorage
35 | localStorage.setItem("cart", JSON.stringify(state.cart))
36 | localStorage.setItem("total", JSON.stringify(state.total))
37 | localStorage.setItem("totalItems", JSON.stringify(state.totalItems))
38 | // show toast
39 | toast.success("Course added to cart")
40 | },
41 | removeFromCart: (state, action) => {
42 | const courseId = action.payload
43 | const index = state.cart.findIndex((item) => item._id === courseId)
44 |
45 | if (index >= 0) {
46 | // If the course is found in the cart, remove it
47 | state.totalItems--
48 | state.total -= state.cart[index].price
49 | state.cart.splice(index, 1)
50 | // Update to localstorage
51 | localStorage.setItem("cart", JSON.stringify(state.cart))
52 | localStorage.setItem("total", JSON.stringify(state.total))
53 | localStorage.setItem("totalItems", JSON.stringify(state.totalItems))
54 | // show toast
55 | toast.success("Course removed from cart")
56 | }
57 | },
58 | resetCart: (state) => {
59 | state.cart = []
60 | state.total = 0
61 | state.totalItems = 0
62 | // Update to localstorage
63 | localStorage.removeItem("cart")
64 | localStorage.removeItem("total")
65 | localStorage.removeItem("totalItems")
66 | },
67 | },
68 | })
69 |
70 | export const { addToCart, removeFromCart, resetCart } = cartSlice.actions
71 |
72 | export default cartSlice.reducer
--------------------------------------------------------------------------------
/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/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 | state.user = value.payload;
14 | },
15 | setLoading(state, value) {
16 | state.loading = value.payload;
17 | },
18 | },
19 | });
20 |
21 | export const {setUser, setLoading} = profileSlice.actions;
22 | export default profileSlice.reducer;
--------------------------------------------------------------------------------
/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 | state.courseSectionData = action.payload
16 | },
17 | setEntireCourseData: (state, action) => {
18 | state.courseEntireData = action.payload
19 | },
20 | setTotalNoOfLectures: (state, action) => {
21 | state.totalNoOfLectures = action.payload
22 | },
23 | setCompletedLectures: (state, action) => {
24 | state.completedLectures = action.payload
25 | },
26 | updateCompletedLectures: (state, action) => {
27 | state.completedLectures = [...state.completedLectures, action.payload]
28 | },
29 | },
30 | })
31 |
32 | export const {
33 | setCourseSectionData,
34 | setEntireCourseData,
35 | setTotalNoOfLectures,
36 | setCompletedLectures,
37 | updateCompletedLectures,
38 | } = viewCourseSlice.actions
39 |
40 | export default viewCourseSlice.reducer
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | screens:{
133 | md:'930px',
134 | }
135 | },
136 | },
137 | variants:{},
138 | plugins: [],
139 | };
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {"source": "/(.*)", "destination": "/"}
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------