├── README.md
├── images
├── mainpage.png
└── schema.png
├── package-lock.json
├── package.json
├── public
├── index.html
└── robots.txt
├── server
├── config
│ ├── cloudinary.js
│ ├── database.js
│ └── razorpay.js
├── controllers
│ ├── Auth.js
│ ├── Category.js
│ ├── ContactUs.js
│ ├── Course.js
│ ├── Payments.js
│ ├── Profile.js
│ ├── RatingAndReview.js
│ ├── ResetPassword.js
│ ├── Section.js
│ ├── Subsection.js
│ └── courseProgress.js
├── index.js
├── mail
│ └── templates
│ │ ├── contactFormRes.js
│ │ ├── courseEnrollmentEmail.js
│ │ ├── emailVerificationTemplate.js
│ │ ├── passwordUpdate.js
│ │ └── paymentSuccessEmail.js
├── middlewares
│ └── auth.js
├── models
│ ├── Category.js
│ ├── Course.js
│ ├── CourseProgress.js
│ ├── OTP.js
│ ├── Profile.js
│ ├── RatingAndRaview.js
│ ├── Section.js
│ ├── SubSection.js
│ └── User.js
├── package-lock.json
├── package.json
├── routes
│ ├── Contact.js
│ ├── Course.js
│ ├── Payments.js
│ ├── Profile.js
│ └── User.js
└── utils
│ ├── imageUploader.js
│ ├── mailSender.js
│ └── secToDuration.js
├── src
├── .DS_Store
├── App.css
├── App.js
├── assets
│ ├── Images
│ │ ├── Compare_with_others.png
│ │ ├── Compare_with_others.svg
│ │ ├── FoundingStory.png
│ │ ├── Instructor.png
│ │ ├── Know_your_progress.png
│ │ ├── Know_your_progress.svg
│ │ ├── Plan_your_lessons.png
│ │ ├── Plan_your_lessons.svg
│ │ ├── TimelineImage.png
│ │ ├── aboutus1.webp
│ │ ├── aboutus2.webp
│ │ ├── aboutus3.webp
│ │ ├── banner.mp4
│ │ ├── bghome.svg
│ │ ├── boxoffice.png
│ │ ├── frame.png
│ │ ├── login.webp
│ │ └── signup.webp
│ ├── Logo
│ │ ├── Logo-Full-Dark.png
│ │ ├── Logo-Full-Light.png
│ │ ├── Logo-Small-Dark.png
│ │ ├── Logo-Small-Light.png
│ │ └── rzp_logo.png
│ └── TimeLineLogo
│ │ ├── Logo1.svg
│ │ ├── Logo2.svg
│ │ ├── Logo3.svg
│ │ └── Logo4.svg
├── components
│ ├── ContactPage
│ │ ├── ContactDetails.jsx
│ │ ├── ContactForm.jsx
│ │ └── ContactUsForm.jsx
│ ├── common
│ │ ├── ConfirmationModal.jsx
│ │ ├── Footer.jsx
│ │ ├── IconBtn.jsx
│ │ ├── Navbar.jsx
│ │ ├── RatingStars.jsx
│ │ ├── ReviewSlider.jsx
│ │ └── Tab.jsx
│ └── core
│ │ ├── AboutPage
│ │ ├── ContactFormSection.jsx
│ │ ├── LearningGrid.jsx
│ │ ├── Quote.jsx
│ │ └── Stats.jsx
│ │ ├── Auth
│ │ ├── LoginForm.jsx
│ │ ├── OpenRoute.jsx
│ │ ├── PrivateRoute.jsx
│ │ ├── ProfileDropDown.jsx
│ │ ├── SignupForm.jsx
│ │ └── Template.jsx
│ │ ├── Catalog
│ │ ├── CourseSlider.jsx
│ │ └── Course_Card.jsx
│ │ ├── Course
│ │ ├── CourseAccordionBar.jsx
│ │ ├── CourseDetailsCard.js
│ │ └── CourseSubSectionAccordion.jsx
│ │ ├── Dashboard
│ │ ├── AddCourse
│ │ │ ├── CourseBuilder
│ │ │ │ ├── CourseBuilderForm.jsx
│ │ │ │ ├── NestedView.jsx
│ │ │ │ └── SubSectionModal.jsx
│ │ │ ├── CourseInformation
│ │ │ │ ├── ChipInput.jsx
│ │ │ │ ├── CourseInformationForm.jsx
│ │ │ │ └── RequirementField.jsx
│ │ │ ├── PublishCourse
│ │ │ │ └── index.jsx
│ │ │ ├── RenderSteps.jsx
│ │ │ ├── Upload.jsx
│ │ │ └── index.jsx
│ │ ├── Cart
│ │ │ ├── RenderCartCourses.jsx
│ │ │ ├── RenderTotalAmount.jsx
│ │ │ └── index.jsx
│ │ ├── EditCourse
│ │ │ └── index.js
│ │ ├── EnrolledCourses.jsx
│ │ ├── InstructorCourses
│ │ │ └── CoursesTable.jsx
│ │ ├── InstructorDashboard
│ │ │ ├── Instructor.jsx
│ │ │ └── InstructorChart.jsx
│ │ ├── MyCourses.jsx
│ │ ├── MyProfile.jsx
│ │ ├── Settings
│ │ │ ├── ChangeProfilePicture.jsx
│ │ │ ├── DeleteAccount.jsx
│ │ │ ├── EditProfile.jsx
│ │ │ ├── UpdatePassword.jsx
│ │ │ └── index.jsx
│ │ ├── Sidebar.jsx
│ │ └── SidebarLink.jsx
│ │ ├── HomePage
│ │ ├── Button.jsx
│ │ ├── CodeBlocks.jsx
│ │ ├── CourseCard.jsx
│ │ ├── ExploreMore.jsx
│ │ ├── HighlightText.jsx
│ │ ├── InstructorSection.jsx
│ │ ├── LearningLanguageSection.jsx
│ │ └── TimelineSection.jsx
│ │ └── ViewCourse
│ │ ├── CourseReviewModal.jsx
│ │ ├── VideoDetails.jsx
│ │ └── VideoDetailsSidebar.jsx
├── data
│ ├── countrycode.json
│ ├── dashboard-links.js
│ ├── footer-links.js
│ ├── homepage-explore.js
│ └── navbar-links.js
├── hooks
│ ├── useOnClickOutside.js
│ └── useRouteMatch.js
├── index.css
├── index.js
├── pages
│ ├── About.jsx
│ ├── Catalog.jsx
│ ├── Contact.jsx
│ ├── CourseDetails.jsx
│ ├── Dashboard.jsx
│ ├── Error.jsx
│ ├── ForgotPassword.jsx
│ ├── Home.jsx
│ ├── Login.jsx
│ ├── Signup.jsx
│ ├── UpdatePassword.jsx
│ ├── VerifyEmail.jsx
│ └── ViewCourse.jsx
├── reducer
│ └── index.js
├── services
│ ├── apiconnector.js
│ ├── apis.js
│ ├── formatDate.js
│ └── operations
│ │ ├── SettingsAPI.js
│ │ ├── authAPI.js
│ │ ├── courseDetailsAPI.js
│ │ ├── pageAndComponentData.js
│ │ ├── profileAPI.js
│ │ └── studentFeaturesAPI.js
├── slices
│ ├── authSlice.js
│ ├── cartSlice.js
│ ├── courseSlice.js
│ ├── profileSlice.js
│ └── viewCourseSlice.js
└── utils
│ ├── avgRating.js
│ ├── constants.js
│ └── dateFormatter.js
└── tailwind.config.js
/images/mainpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/images/mainpage.png
--------------------------------------------------------------------------------
/images/schema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/images/schema.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tailwind-css-starter-pack",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ramonak/react-progress-bar": "^5.0.3",
7 | "@reduxjs/toolkit": "^1.9.5",
8 | "axios": "^1.4.0",
9 | "chart.js": "^4.3.0",
10 | "concurrently": "^8.0.1",
11 | "copy-to-clipboard": "^3.3.3",
12 | "react": "^18.2.0",
13 | "react-chartjs-2": "^5.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-dropzone": "^14.2.3",
16 | "react-hook-form": "^7.44.2",
17 | "react-hot-toast": "^2.4.1",
18 | "react-icons": "^4.8.0",
19 | "react-markdown": "^8.0.7",
20 | "react-otp-input": "^3.0.2",
21 | "react-rating-stars-component": "^2.2.0",
22 | "react-redux": "^8.0.5",
23 | "react-router-dom": "^6.11.2",
24 | "react-scripts": "5.0.1",
25 | "react-super-responsive-table": "^5.2.1",
26 | "react-type-animation": "^3.0.1",
27 | "redux-toolkit": "^1.1.2",
28 | "swiper": "^9.4.1",
29 | "video-react": "^0.16.0",
30 | "web-vitals": "^2.1.4"
31 | },
32 | "repository": {
33 | "type": "git",
34 | "url": "git+https://github.com/thepranaygupta/react-tailwind-css-starter-pack.git"
35 | },
36 | "author": "Pranay Gupta",
37 | "bugs": {
38 | "url": "https://github.com/thepranaygupta/react-tailwind-css-starter-pack/issues"
39 | },
40 | "scripts": {
41 | "start": "react-scripts start",
42 | "build": "react-scripts build",
43 | "eject": "react-scripts eject",
44 | "server": "cd server && npm run dev",
45 | "dev": "concurrently -n \"client,server\" -c \"bgBlue,bgYellow\" \"npm start\" \"npm run server\""
46 | },
47 | "eslintConfig": {
48 | "extends": [
49 | "react-app",
50 | "react-app/jest"
51 | ]
52 | },
53 | "browserslist": {
54 | "production": [
55 | ">0.2%",
56 | "not dead",
57 | "not op_mini all"
58 | ],
59 | "development": [
60 | "last 1 chrome version",
61 | "last 1 firefox version",
62 | "last 1 safari version"
63 | ]
64 | },
65 | "devDependencies": {
66 | "tailwindcss": "^3.2.7"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React & Tailwind CSS Starter Pack
8 |
9 |
10 |
11 | You need to enable JavaScript to run this app.
12 |
13 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/server/config/cloudinary.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require("cloudinary").v2; //! Cloudinary is being required
2 |
3 | exports.cloudinaryConnect = () => {
4 | try {
5 | cloudinary.config({
6 | //! ######## Configuring the Cloudinary to Upload MEDIA ########
7 | cloud_name: process.env.CLOUD_NAME,
8 | api_key: process.env.API_KEY,
9 | api_secret: process.env.API_SECRET,
10 | });
11 | } catch (error) {
12 | console.log(error);
13 | }
14 | };
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | require("dotenv").config();
3 |
4 | exports.connect = () => {
5 | mongoose.connect(process.env.MONGODB_URL, {
6 | useNewUrlParser: true,
7 | useUnifiedTopology:true,
8 | })
9 | .then(() => console.log("DB Connected Successfully"))
10 | .catch( (error) => {
11 | console.log("DB Connection Failed");
12 | console.error(error);
13 | process.exit(1);
14 | } )
15 | };
--------------------------------------------------------------------------------
/server/config/razorpay.js:
--------------------------------------------------------------------------------
1 | const Razorpay = require("razorpay");
2 |
3 |
4 | exports.instance = new Razorpay({
5 | key_id: process.env.RAZORPAY_KEY,
6 | key_secret: process.env.RAZORPAY_SECRET,
7 | });
--------------------------------------------------------------------------------
/server/controllers/Category.js:
--------------------------------------------------------------------------------
1 | const { Mongoose } = require("mongoose");
2 | const Category = require("../models/Category");
3 | function getRandomInt(max) {
4 | return Math.floor(Math.random() * max)
5 | }
6 |
7 | exports.createCategory = async (req, res) => {
8 | try {
9 | const { name, description } = req.body;
10 | if (!name) {
11 | return res
12 | .status(400)
13 | .json({ success: false, message: "All fields are required" });
14 | }
15 | const CategorysDetails = await Category.create({
16 | name: name,
17 | description: description,
18 | });
19 | console.log(CategorysDetails);
20 | return res.status(200).json({
21 | success: true,
22 | message: "Categorys Created Successfully",
23 | });
24 | } catch (error) {
25 | return res.status(500).json({
26 | success: true,
27 | message: error.message,
28 | });
29 | }
30 | };
31 |
32 | exports.showAllCategories = async (req, res) => {
33 | try {
34 | console.log("INSIDE SHOW ALL CATEGORIES");
35 | const allCategorys = await Category.find({});
36 | res.status(200).json({
37 | success: true,
38 | data: allCategorys,
39 | });
40 | } catch (error) {
41 | return res.status(500).json({
42 | success: false,
43 | message: error.message,
44 | });
45 | }
46 | };
47 |
48 | //categoryPageDetails
49 |
50 | exports.categoryPageDetails = async (req, res) => {
51 | try {
52 | const { categoryId } = req.body
53 | console.log("PRINTING CATEGORY ID: ", categoryId);
54 | // Get courses for the specified category
55 | const selectedCategory = await Category.findById(categoryId)
56 | .populate({
57 | path: "courses",
58 | match: { status: "Published" },
59 | populate: "ratingAndReviews",
60 | })
61 | .exec()
62 |
63 | //console.log("SELECTED COURSE", selectedCategory)
64 | // Handle the case when the category is not found
65 | if (!selectedCategory) {
66 | console.log("Category not found.")
67 | return res
68 | .status(404)
69 | .json({ success: false, message: "Category not found" })
70 | }
71 | // Handle the case when there are no courses
72 | if (selectedCategory.courses.length === 0) {
73 | console.log("No courses found for the selected category.")
74 | return res.status(404).json({
75 | success: false,
76 | message: "No courses found for the selected category.",
77 | })
78 | }
79 |
80 | // Get courses for other categories
81 | const categoriesExceptSelected = await Category.find({
82 | _id: { $ne: categoryId },
83 | })
84 | let differentCategory = await Category.findOne(
85 | categoriesExceptSelected[getRandomInt(categoriesExceptSelected.length)]
86 | ._id
87 | )
88 | .populate({
89 | path: "courses",
90 | match: { status: "Published" },
91 | })
92 | .exec()
93 | //console.log("Different COURSE", differentCategory)
94 | // Get top-selling courses across all categories
95 | const allCategories = await Category.find()
96 | .populate({
97 | path: "courses",
98 | match: { status: "Published" },
99 | populate: {
100 | path: "instructor",
101 | },
102 | })
103 | .exec()
104 | const allCourses = allCategories.flatMap((category) => category.courses)
105 | const mostSellingCourses = allCourses
106 | .sort((a, b) => b.sold - a.sold)
107 | .slice(0, 10)
108 | // console.log("mostSellingCourses COURSE", mostSellingCourses)
109 | res.status(200).json({
110 | success: true,
111 | data: {
112 | selectedCategory,
113 | differentCategory,
114 | mostSellingCourses,
115 | },
116 | })
117 | } catch (error) {
118 | return res.status(500).json({
119 | success: false,
120 | message: "Internal server error",
121 | error: error.message,
122 | })
123 | }
124 | }
--------------------------------------------------------------------------------
/server/controllers/ContactUs.js:
--------------------------------------------------------------------------------
1 | const { contactUsEmail } = require("../mail/templates/contactFormRes")
2 | const mailSender = require("../utils/mailSender")
3 |
4 | exports.contactUsController = async (req, res) => {
5 | const { email, firstname, lastname, message, phoneNo, countrycode } = req.body
6 | console.log(req.body)
7 | try {
8 | const emailRes = await mailSender(
9 | email,
10 | "Your Data send successfully",
11 | contactUsEmail(email, firstname, lastname, message, phoneNo, countrycode)
12 | )
13 | console.log("Email Res ", emailRes)
14 | return res.json({
15 | success: true,
16 | message: "Email send successfully",
17 | })
18 | } catch (error) {
19 | console.log("Error", error)
20 | console.log("Error message :", error.message)
21 | return res.json({
22 | success: false,
23 | message: "Something went wrong...",
24 | })
25 | }
26 | }
--------------------------------------------------------------------------------
/server/controllers/ResetPassword.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/User");
2 | const mailSender = require("../utils/mailSender");
3 | const bcrypt = require("bcryptjs");
4 | const crypto = require("crypto");
5 |
6 | exports.resetPasswordToken = async (req, res) => {
7 | try {
8 | const email = req.body.email;
9 | const user = await User.findOne({ email: email });
10 | if (!user) {
11 | return res.json({
12 | success: false,
13 | message: `This Email: ${email} is not Registered With Us Enter a Valid Email `,
14 | });
15 | }
16 | const token = crypto.randomBytes(20).toString("hex");
17 |
18 | const updatedDetails = await User.findOneAndUpdate(
19 | { email: email },
20 | {
21 | token: token,
22 | resetPasswordExpires: Date.now() + 3600000,
23 | },
24 | { new: true }
25 | );
26 | console.log("DETAILS", updatedDetails);
27 |
28 | const url = `http://localhost:3000/update-password/${token}`;
29 |
30 | await mailSender(
31 | email,
32 | "Password Reset",
33 | `Your Link for email verification is ${url}. Please click this url to reset your password.`
34 | );
35 |
36 | res.json({
37 | success: true,
38 | message:
39 | "Email Sent Successfully, Please Check Your Email to Continue Further",
40 | });
41 | } catch (error) {
42 | return res.json({
43 | error: error.message,
44 | success: false,
45 | message: `Some Error in Sending the Reset Message`,
46 | });
47 | }
48 | };
49 |
50 | exports.resetPassword = async (req, res) => {
51 | try {
52 | const { password, confirmPassword, token } = req.body;
53 |
54 | if (confirmPassword !== password) {
55 | return res.json({
56 | success: false,
57 | message: "Password and Confirm Password Does not Match",
58 | });
59 | }
60 | const userDetails = await User.findOne({ token: token });
61 | if (!userDetails) {
62 | return res.json({
63 | success: false,
64 | message: "Token is Invalid",
65 | });
66 | }
67 | if (!(userDetails.resetPasswordExpires > Date.now())) {
68 | return res.status(403).json({
69 | success: false,
70 | message: `Token is Expired, Please Regenerate Your Token`,
71 | });
72 | }
73 | const encryptedPassword = await bcrypt.hash(password, 10);
74 | await User.findOneAndUpdate(
75 | { token: token },
76 | { password: encryptedPassword },
77 | { new: true }
78 | );
79 | res.json({
80 | success: true,
81 | message: `Password Reset Successful`,
82 | });
83 | } catch (error) {
84 | return res.json({
85 | error: error.message,
86 | success: false,
87 | message: `Some Error in Updating the Password`,
88 | });
89 | }
90 | };
--------------------------------------------------------------------------------
/server/controllers/Section.js:
--------------------------------------------------------------------------------
1 | const Section = require("../models/Section");
2 | const Course = require("../models/Course");
3 | const SubSection = require("../models/SubSection");
4 | // CREATE a new section
5 | exports.createSection = async (req, res) => {
6 | try {
7 | // Extract the required properties from the request body
8 | const { sectionName, courseId } = req.body;
9 |
10 | // Validate the input
11 | if (!sectionName || !courseId) {
12 | return res.status(400).json({
13 | success: false,
14 | message: "Missing required properties",
15 | });
16 | }
17 |
18 | // Create a new section with the given name
19 | const newSection = await Section.create({ sectionName });
20 |
21 | // Add the new section to the course's content array
22 | const updatedCourse = await Course.findByIdAndUpdate(
23 | courseId,
24 | {
25 | $push: {
26 | courseContent: newSection._id,
27 | },
28 | },
29 | { new: true }
30 | )
31 | .populate({
32 | path: "courseContent",
33 | populate: {
34 | path: "subSection",
35 | },
36 | })
37 | .exec();
38 |
39 | // Return the updated course object in the response
40 | res.status(200).json({
41 | success: true,
42 | message: "Section created successfully",
43 | updatedCourse,
44 | });
45 | } catch (error) {
46 | // Handle errors
47 | res.status(500).json({
48 | success: false,
49 | message: "Internal server error",
50 | error: error.message,
51 | });
52 | }
53 | };
54 |
55 | // UPDATE a section
56 | exports.updateSection = async (req, res) => {
57 | try {
58 | const { sectionName, sectionId,courseId } = req.body;
59 | const section = await Section.findByIdAndUpdate(
60 | sectionId,
61 | { sectionName },
62 | { new: true }
63 | );
64 |
65 | const course = await Course.findById(courseId)
66 | .populate({
67 | path:"courseContent",
68 | populate:{
69 | path:"subSection",
70 | },
71 | })
72 | .exec();
73 |
74 | res.status(200).json({
75 | success: true,
76 | message: section,
77 | data:course,
78 | });
79 | } catch (error) {
80 | console.error("Error updating section:", error);
81 | res.status(500).json({
82 | success: false,
83 | message: "Internal server error",
84 | });
85 | }
86 | };
87 |
88 | // DELETE a section
89 | exports.deleteSection = async (req, res) => {
90 | try {
91 |
92 | const { sectionId, courseId } = req.body;
93 | await Course.findByIdAndUpdate(courseId, {
94 | $pull: {
95 | courseContent: sectionId,
96 | }
97 | })
98 | const section = await Section.findById(sectionId);
99 | console.log(sectionId, courseId);
100 | if(!section) {
101 | return res.status(404).json({
102 | success:false,
103 | message:"Section not Found",
104 | })
105 | }
106 |
107 | //delete sub section
108 | await SubSection.deleteMany({_id: {$in: section.subSection}});
109 |
110 | await Section.findByIdAndDelete(sectionId);
111 |
112 | //find the updated course and return
113 | const course = await Course.findById(courseId).populate({
114 | path:"courseContent",
115 | populate: {
116 | path: "subSection"
117 | }
118 | })
119 | .exec();
120 |
121 | res.status(200).json({
122 | success:true,
123 | message:"Section deleted",
124 | data:course
125 | });
126 | } catch (error) {
127 | console.error("Error deleting section:", error);
128 | res.status(500).json({
129 | success: false,
130 | message: "Internal server error",
131 | });
132 | }
133 | };
--------------------------------------------------------------------------------
/server/controllers/courseProgress.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 | const Section = require("../models/Section")
3 | const SubSection = require("../models/SubSection")
4 | const CourseProgress = require("../models/CourseProgress")
5 | const Course = require("../models/Course")
6 |
7 | exports.updateCourseProgress = async (req, res) => {
8 | const { courseId, subsectionId } = req.body
9 | const userId = req.user.id
10 |
11 | try {
12 | // Check if the subsection is valid
13 | const subsection = await SubSection.findById(subsectionId)
14 | if (!subsection) {
15 | return res.status(404).json({ error: "Invalid subsection" })
16 | }
17 |
18 | // Find the course progress document for the user and course
19 | let courseProgress = await CourseProgress.findOne({
20 | courseID: courseId,
21 | userId: userId,
22 | })
23 |
24 | if (!courseProgress) {
25 | // If course progress doesn't exist, create a new one
26 | return res.status(404).json({
27 | success: false,
28 | message: "Course progress Does Not Exist",
29 | })
30 | } else {
31 | // If course progress exists, check if the subsection is already completed
32 | if (courseProgress.completedVideos.includes(subsectionId)) {
33 | return res.status(400).json({ error: "Subsection already completed" })
34 | }
35 |
36 | // Push the subsection into the completedVideos array
37 | courseProgress.completedVideos.push(subsectionId)
38 | }
39 |
40 | // Save the updated course progress
41 | await courseProgress.save()
42 |
43 | return res.status(200).json({ message: "Course progress updated" })
44 | } catch (error) {
45 | console.error(error)
46 | return res.status(500).json({ error: "Internal server error" })
47 | }
48 | }
49 |
50 | // exports.getProgressPercentage = async (req, res) => {
51 | // const { courseId } = req.body
52 | // const userId = req.user.id
53 |
54 | // if (!courseId) {
55 | // return res.status(400).json({ error: "Course ID not provided." })
56 | // }
57 |
58 | // try {
59 | // // Find the course progress document for the user and course
60 | // let courseProgress = await CourseProgress.findOne({
61 | // courseID: courseId,
62 | // userId: userId,
63 | // })
64 | // .populate({
65 | // path: "courseID",
66 | // populate: {
67 | // path: "courseContent",
68 | // },
69 | // })
70 | // .exec()
71 |
72 | // if (!courseProgress) {
73 | // return res
74 | // .status(400)
75 | // .json({ error: "Can not find Course Progress with these IDs." })
76 | // }
77 | // console.log(courseProgress, userId)
78 | // let lectures = 0
79 | // courseProgress.courseID.courseContent?.forEach((sec) => {
80 | // lectures += sec.subSection.length || 0
81 | // })
82 |
83 | // let progressPercentage =
84 | // (courseProgress.completedVideos.length / lectures) * 100
85 |
86 | // // To make it up to 2 decimal point
87 | // const multiplier = Math.pow(10, 2)
88 | // progressPercentage =
89 | // Math.round(progressPercentage * multiplier) / multiplier
90 |
91 | // return res.status(200).json({
92 | // data: progressPercentage,
93 | // message: "Succesfully fetched Course progress",
94 | // })
95 | // } catch (error) {
96 | // console.error(error)
97 | // return res.status(500).json({ error: "Internal server error" })
98 | // }
99 | // }
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 |
4 | const userRoutes = require("./routes/User");
5 | const profileRoutes = require("./routes/Profile");
6 | const paymentRoutes = require("./routes/Payments");
7 | const courseRoutes = require("./routes/Course");
8 | const contactUsRoute = require("./routes/Contact");
9 | const database = require("./config/database");
10 | const cookieParser = require("cookie-parser");
11 | const cors = require("cors");
12 | const {cloudinaryConnect } = require("./config/cloudinary");
13 | const fileUpload = require("express-fileupload");
14 | const dotenv = require("dotenv");
15 |
16 | dotenv.config();
17 | const PORT = process.env.PORT || 4000;
18 |
19 | //database connect
20 | database.connect();
21 | //middlewares
22 | app.use(express.json());
23 | app.use(cookieParser());
24 | app.use(
25 | cors({
26 | origin:"http://localhost:3000",
27 | credentials:true,
28 | })
29 | )
30 |
31 | app.use(
32 | fileUpload({
33 | useTempFiles:true,
34 | tempFileDir:"/tmp",
35 | })
36 | )
37 | //cloudinary connection
38 | cloudinaryConnect();
39 |
40 | //routes
41 | app.use("/api/v1/auth", userRoutes);
42 | app.use("/api/v1/profile", profileRoutes);
43 | app.use("/api/v1/course", courseRoutes);
44 | app.use("/api/v1/payment", paymentRoutes);
45 | app.use("/api/v1/reach", contactUsRoute);
46 |
47 | //def route
48 |
49 | app.get("/", (req, res) => {
50 | return res.json({
51 | success:true,
52 | message:'Your server is up and running....'
53 | });
54 | });
55 |
56 | app.listen(PORT, () => {
57 | console.log(`App is running at ${PORT}`)
58 | })
59 |
60 |
--------------------------------------------------------------------------------
/server/mail/templates/contactFormRes.js:
--------------------------------------------------------------------------------
1 | exports.contactUsEmail = (
2 | email,
3 | firstname,
4 | lastname,
5 | message,
6 | phoneNo,
7 | countrycode
8 | ) => {
9 | return `
10 |
11 |
12 |
13 |
14 | Contact Form Confirmation
15 |
72 |
73 |
74 |
75 |
76 |
77 |
79 |
Contact Form Confirmation
80 |
81 |
Dear ${firstname} ${lastname},
82 |
Thank you for contacting us. We have received your message and will respond to you as soon as possible.
83 |
84 |
Here are the details you provided:
85 |
Name: ${firstname} ${lastname}
86 |
Email: ${email}
87 |
Phone Number: ${phoneNo}
88 |
Message: ${message}
89 |
We appreciate your interest and will get back to you shortly.
90 |
91 |
If you have any further questions or need immediate assistance, please feel free to reach
92 | out to us at
info@studynotion.com . We are here to help!
93 |
94 |
95 |
96 | `
97 | }
--------------------------------------------------------------------------------
/server/mail/templates/courseEnrollmentEmail.js:
--------------------------------------------------------------------------------
1 | exports.courseEnrollmentEmail = (courseName, name) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Course Registration Confirmation
8 |
65 |
66 |
67 |
68 |
69 |
70 |
72 |
Course Registration Confirmation
73 |
74 |
Dear ${name},
75 |
You have successfully registered for the course "${courseName}" . We
76 | are excited to have you as a participant!
77 |
Please log in to your learning dashboard to access the course materials and start your learning journey.
78 |
79 |
Go to Dashboard
80 |
81 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
83 |
84 |
85 |
86 | `;
87 | };
--------------------------------------------------------------------------------
/server/mail/templates/emailVerificationTemplate.js:
--------------------------------------------------------------------------------
1 | const otpTemplate = (otp) => {
2 | return `
3 |
4 |
5 |
6 |
7 | OTP Verification Email
8 |
64 |
65 |
66 |
67 |
68 |
69 |
71 |
OTP Verification Email
72 |
73 |
Dear User,
74 |
Thank you for registering with StudyNotion. To complete your registration, please use the following OTP
75 | (One-Time Password) to verify your account:
76 |
${otp}
77 |
This OTP is valid for 5 minutes. If you did not request this verification, please disregard this email.
78 | Once your account is verified, you will have access to our platform and its features.
79 |
80 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
82 |
83 |
84 |
85 | `;
86 | };
87 | module.exports = otpTemplate;
--------------------------------------------------------------------------------
/server/mail/templates/passwordUpdate.js:
--------------------------------------------------------------------------------
1 | exports.passwordUpdated = (email, name) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Password Update Confirmation
8 |
53 |
54 |
55 |
56 |
57 |
58 |
60 |
Password Update Confirmation
61 |
62 |
Hey ${name},
63 |
Your password has been successfully updated for the email ${email} .
64 |
65 |
If you did not request this password change, please contact us immediately to secure your account.
66 |
67 |
If you have any questions or need further assistance, please feel free to reach out to us
68 | at
69 |
info@studynotion.com . We are here to help!
70 |
71 |
72 |
73 |
74 | `;
75 | };
--------------------------------------------------------------------------------
/server/mail/templates/paymentSuccessEmail.js:
--------------------------------------------------------------------------------
1 | exports.paymentSuccessEmail = (name, amount, orderId, paymentId) => {
2 | return `
3 |
4 |
5 |
6 |
7 | Payment Confirmation
8 |
65 |
66 |
67 |
68 |
69 |
70 |
72 |
Course Payment Confirmation
73 |
74 |
Dear ${name},
75 |
We have received a payment of ₹${amount}
.
76 |
Your Payment ID is ${paymentId}
77 |
Your Order ID is ${orderId}
78 |
79 |
If you have any questions or need assistance, please feel free to reach out to us at
info@studynotion.com . We are here to help!
81 |
82 |
83 |
84 | `
85 | }
--------------------------------------------------------------------------------
/server/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | // Importing required modules
2 | const jwt = require("jsonwebtoken");
3 | const dotenv = require("dotenv");
4 | const User = require("../models/User");
5 | // Configuring dotenv to load environment variables from .env file
6 | dotenv.config();
7 |
8 | // This function is used as middleware to authenticate user requests
9 | exports.auth = async (req, res, next) => {
10 | try {
11 | // Extracting JWT from request cookies, body or header
12 | const token =
13 | req.cookies.token ||
14 | req.body.token ||
15 | req.header("Authorization").replace("Bearer ", "");
16 |
17 | // If JWT is missing, return 401 Unauthorized response
18 | if (!token) {
19 | return res.status(401).json({ success: false, message: `Token Missing` });
20 | }
21 |
22 | try {
23 | // Verifying the JWT using the secret key stored in environment variables
24 | const decode = await jwt.verify(token, process.env.JWT_SECRET);
25 | console.log(decode);
26 | // Storing the decoded JWT payload in the request object for further use
27 | req.user = decode;
28 | } catch (error) {
29 | // If JWT verification fails, return 401 Unauthorized response
30 | return res
31 | .status(401)
32 | .json({ success: false, message: "token is invalid" });
33 | }
34 |
35 | // If JWT is valid, move on to the next middleware or request handler
36 | next();
37 | } catch (error) {
38 | // If there is an error during the authentication process, return 401 Unauthorized response
39 | return res.status(401).json({
40 | success: false,
41 | message: `Something Went Wrong While Validating the Token`,
42 | });
43 | }
44 | };
45 | exports.isStudent = async (req, res, next) => {
46 | try {
47 | const userDetails = await User.findOne({ email: req.user.email });
48 |
49 | if (userDetails.accountType !== "Student") {
50 | return res.status(401).json({
51 | success: false,
52 | message: "This is a Protected Route for Students",
53 | });
54 | }
55 | next();
56 | } catch (error) {
57 | return res
58 | .status(500)
59 | .json({ success: false, message: `User Role Can't be Verified` });
60 | }
61 | };
62 | exports.isAdmin = async (req, res, next) => {
63 | try {
64 | const userDetails = await User.findOne({ email: req.user.email });
65 |
66 | if (userDetails.accountType !== "Admin") {
67 | return res.status(401).json({
68 | success: false,
69 | message: "This is a Protected Route for Admin",
70 | });
71 | }
72 | next();
73 | } catch (error) {
74 | return res
75 | .status(500)
76 | .json({ success: false, message: `User Role Can't be Verified` });
77 | }
78 | };
79 | exports.isInstructor = async (req, res, next) => {
80 | try {
81 | const userDetails = await User.findOne({ email: req.user.email });
82 | console.log(userDetails);
83 |
84 | console.log(userDetails.accountType);
85 |
86 | if (userDetails.accountType !== "Instructor") {
87 | return res.status(401).json({
88 | success: false,
89 | message: "This is a Protected Route for Instructor",
90 | });
91 | }
92 | next();
93 | } catch (error) {
94 | return res
95 | .status(500)
96 | .json({ success: false, message: `User Role Can't be Verified` });
97 | }
98 | };
--------------------------------------------------------------------------------
/server/models/Category.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Tags schema
4 | const categorySchema = new mongoose.Schema({
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | description: { type: String },
10 | courses: [
11 | {
12 | type: mongoose.Schema.Types.ObjectId,
13 | ref: "Course",
14 | },
15 | ],
16 | });
17 |
18 | // Export the Tags model
19 | module.exports = mongoose.model("Category", categorySchema);
--------------------------------------------------------------------------------
/server/models/Course.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Courses schema
4 | const coursesSchema = new mongoose.Schema({
5 | courseName: { type: String },
6 | courseDescription: { type: String },
7 | instructor: {
8 | type: mongoose.Schema.Types.ObjectId,
9 | required: true,
10 | ref: "user",
11 | },
12 | whatYouWillLearn: {
13 | type: String,
14 | },
15 | courseContent: [
16 | {
17 | type: mongoose.Schema.Types.ObjectId,
18 | ref: "Section",
19 | },
20 | ],
21 | ratingAndReviews: [
22 | {
23 | type: mongoose.Schema.Types.ObjectId,
24 | ref: "RatingAndReview",
25 | },
26 | ],
27 | price: {
28 | type: Number,
29 | },
30 | thumbnail: {
31 | type: String,
32 | },
33 | tag: {
34 | type: [String],
35 | required: true,
36 | },
37 | category: {
38 | type: mongoose.Schema.Types.ObjectId,
39 | // required: true,
40 | ref: "Category",
41 | },
42 | studentsEnrolled: [
43 | {
44 | type: mongoose.Schema.Types.ObjectId,
45 | required: true,
46 | ref: "user",
47 | },
48 | ],
49 | instructions: {
50 | type: [String],
51 | },
52 | status: {
53 | type: String,
54 | enum: ["Draft", "Published"],
55 | },
56 | createdAt: {
57 | type:Date,
58 | default:Date.now
59 | },
60 | });
61 |
62 | // Export the Courses model
63 | module.exports = mongoose.model("Course", coursesSchema);
--------------------------------------------------------------------------------
/server/models/CourseProgress.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose")
2 |
3 | const courseProgress = new mongoose.Schema({
4 | courseID: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "Course",
7 | },
8 | userId: {
9 | type: mongoose.Schema.Types.ObjectId,
10 | ref: "user",
11 | },
12 | completedVideos: [
13 | {
14 | type: mongoose.Schema.Types.ObjectId,
15 | ref: "SubSection",
16 | },
17 | ],
18 | })
19 |
20 | module.exports = mongoose.model("courseProgress", courseProgress)
--------------------------------------------------------------------------------
/server/models/OTP.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const mailSender = require("../utils/mailSender");
3 | const emailTemplate = require("../mail/templates/emailVerificationTemplate");
4 | const OTPSchema = new mongoose.Schema({
5 | email: {
6 | type: String,
7 | required: true,
8 | },
9 | otp: {
10 | type: String,
11 | required: true,
12 | },
13 | createdAt: {
14 | type: Date,
15 | default: Date.now,
16 | expires: 60 * 5, // The document will be automatically deleted after 5 minutes of its creation time
17 | },
18 | });
19 |
20 | // Define a function to send emails
21 | async function sendVerificationEmail(email, otp) {
22 | // Create a transporter to send emails
23 |
24 | // Define the email options
25 |
26 | // Send the email
27 | try {
28 | const mailResponse = await mailSender(
29 | email,
30 | "Verification Email",
31 | emailTemplate(otp)
32 | );
33 | console.log("Email sent successfully: ", mailResponse.response);
34 | } catch (error) {
35 | console.log("Error occurred while sending email: ", error);
36 | throw error;
37 | }
38 | }
39 |
40 | // Define a post-save hook to send email after the document has been saved
41 | OTPSchema.pre("save", async function (next) {
42 | console.log("New document saved to database");
43 |
44 | // Only send an email when a new document is created
45 | if (this.isNew) {
46 | await sendVerificationEmail(this.email, this.otp);
47 | }
48 | next();
49 | });
50 |
51 | const OTP = mongoose.model("OTP", OTPSchema);
52 |
53 | module.exports = OTP;
--------------------------------------------------------------------------------
/server/models/Profile.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Profile schema
4 | const profileSchema = new mongoose.Schema({
5 | gender: {
6 | type: String,
7 | },
8 | dateOfBirth: {
9 | type: String,
10 | },
11 | about: {
12 | type: String,
13 | trim: true,
14 | },
15 | contactNumber: {
16 | type: Number,
17 | trim: true,
18 | },
19 | });
20 |
21 | // Export the Profile model
22 | module.exports = mongoose.model("Profile", profileSchema);
23 |
--------------------------------------------------------------------------------
/server/models/RatingAndRaview.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the RatingAndReview schema
4 | const ratingAndReviewSchema = new mongoose.Schema({
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: "user",
9 | },
10 | rating: {
11 | type: Number,
12 | required: true,
13 | },
14 | review: {
15 | type: String,
16 | required: true,
17 | },
18 | course: {
19 | type: mongoose.Schema.Types.ObjectId,
20 | required: true,
21 | ref: "Course",
22 | index: true,
23 | },
24 | });
25 |
26 | // Export the RatingAndReview model
27 | module.exports = mongoose.model("RatingAndReview", ratingAndReviewSchema);
--------------------------------------------------------------------------------
/server/models/Section.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | // Define the Section schema
4 | const sectionSchema = new mongoose.Schema({
5 | sectionName: {
6 | type: String,
7 | },
8 | subSection: [
9 | {
10 | type: mongoose.Schema.Types.ObjectId,
11 | required: true,
12 | ref: "SubSection",
13 | },
14 | ],
15 | });
16 |
17 | // Export the Section model
18 | module.exports = mongoose.model("Section", sectionSchema);
--------------------------------------------------------------------------------
/server/models/SubSection.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const SubSectionSchema = new mongoose.Schema({
4 | title: { type: String },
5 | timeDuration: { type: String },
6 | description: { type: String },
7 | videoUrl: { type: String },
8 | });
9 |
10 | module.exports = mongoose.model("SubSection", SubSectionSchema);
--------------------------------------------------------------------------------
/server/models/User.js:
--------------------------------------------------------------------------------
1 | // Import the Mongoose library
2 | const mongoose = require("mongoose");
3 |
4 | // Define the user schema using the Mongoose Schema constructor
5 | const userSchema = new mongoose.Schema(
6 | {
7 | // Define the name field with type String, required, and trimmed
8 | firstName: {
9 | type: String,
10 | required: true,
11 | trim: true,
12 | },
13 | lastName: {
14 | type: String,
15 | required: true,
16 | trim: true,
17 | },
18 | // Define the email field with type String, required, and trimmed
19 | email: {
20 | type: String,
21 | required: true,
22 | trim: true,
23 | },
24 |
25 | // Define the password field with type String and required
26 | password: {
27 | type: String,
28 | required: true,
29 | },
30 | // Define the role field with type String and enum values of "Admin", "Student", or "Visitor"
31 | accountType: {
32 | type: String,
33 | enum: ["Admin", "Student", "Instructor"],
34 | required: true,
35 | },
36 | active: {
37 | type: Boolean,
38 | default: true,
39 | },
40 | approved: {
41 | type: Boolean,
42 | default: true,
43 | },
44 | additionalDetails: {
45 | type: mongoose.Schema.Types.ObjectId,
46 | required: true,
47 | ref: "Profile",
48 | },
49 | courses: [
50 | {
51 | type: mongoose.Schema.Types.ObjectId,
52 | ref: "Course",
53 | },
54 | ],
55 | token: {
56 | type: String,
57 | },
58 | resetPasswordExpires: {
59 | type: Date,
60 | },
61 | image: {
62 | type: String,
63 | required: true,
64 | },
65 | courseProgress: [
66 | {
67 | type: mongoose.Schema.Types.ObjectId,
68 | ref: "courseProgress",
69 | },
70 | ],
71 |
72 | // Add timestamps for when the document is created and last modified
73 | },
74 | { timestamps: true }
75 | );
76 |
77 | // Export the Mongoose model for the user schema, using the name "user"
78 | module.exports = mongoose.model("user", userSchema);
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "dev": "nodemon index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcrypt": "^5.1.0",
15 | "bcryptjs": "^2.4.3",
16 | "cloudinary": "^1.36.4",
17 | "cookie-parser": "^1.4.6",
18 | "cors": "^2.8.5",
19 | "crypto-random-string": "^5.0.0",
20 | "dotenv": "^16.0.3",
21 | "express": "^4.18.2",
22 | "express-fileupload": "^1.4.0",
23 | "jsonwebtoken": "^9.0.0",
24 | "mongoose": "^7.0.3",
25 | "node-schedule": "^2.1.1",
26 | "nodemailer": "^6.9.1",
27 | "nodemon": "^2.0.22",
28 | "otp-generator": "^4.0.1",
29 | "razorpay": "^2.8.6"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/routes/Contact.js:
--------------------------------------------------------------------------------
1 | const express = require("express")
2 | const router = express.Router()
3 | const { contactUsController } = require("../controllers/ContactUs")
4 |
5 | router.post("/contact", contactUsController)
6 |
7 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/Course.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | // Import the Controllers
6 |
7 | // Course Controllers Import
8 | const {
9 | createCourse,
10 | getAllCourses,
11 | getCourseDetails,
12 | getFullCourseDetails,
13 | editCourse,
14 | getInstructorCourses,
15 | deleteCourse,
16 | } = require("../controllers/Course")
17 |
18 |
19 | // Categories Controllers Import
20 | const {
21 | showAllCategories,
22 | createCategory,
23 | categoryPageDetails,
24 | } = require("../controllers/Category")
25 |
26 | // Sections Controllers Import
27 | const {
28 | createSection,
29 | updateSection,
30 | deleteSection,
31 | } = require("../controllers/Section")
32 |
33 | // Sub-Sections Controllers Import
34 | const {
35 | createSubSection,
36 | updateSubSection,
37 | deleteSubSection,
38 | } = require("../controllers/Subsection")
39 |
40 | // Rating Controllers Import
41 | const {
42 | createRating,
43 | getAverageRating,
44 | getAllRating,
45 | } = require("../controllers/RatingAndReview")
46 |
47 | const {
48 | updateCourseProgress
49 | } = require("../controllers/courseProgress");
50 |
51 | // Importing Middlewares
52 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth")
53 |
54 | // ********************************************************************************************************
55 | // Course routes
56 | // ********************************************************************************************************
57 |
58 | // Courses can Only be Created by Instructors
59 | router.post("/createCourse", auth, isInstructor, createCourse)
60 | //Add a Section to a Course
61 | router.post("/addSection", auth, isInstructor, createSection)
62 | // Update a Section
63 | router.post("/updateSection", auth, isInstructor, updateSection)
64 | // Delete a Section
65 | router.post("/deleteSection", auth, isInstructor, deleteSection)
66 | // Edit Sub Section
67 | router.post("/updateSubSection", auth, isInstructor, updateSubSection)
68 | // Delete Sub Section
69 | router.post("/deleteSubSection", auth, isInstructor, deleteSubSection)
70 | // Add a Sub Section to a Section
71 | router.post("/addSubSection", auth, isInstructor, createSubSection)
72 | // Get all Registered Courses
73 | router.get("/getAllCourses", getAllCourses)
74 | // Get Details for a Specific Courses
75 | router.post("/getCourseDetails", getCourseDetails)
76 | // Get Details for a Specific Courses
77 | router.post("/getFullCourseDetails", auth, getFullCourseDetails)
78 | // Edit Course routes
79 | router.post("/editCourse", auth, isInstructor, editCourse)
80 | // Get all Courses Under a Specific Instructor
81 | router.get("/getInstructorCourses", auth, isInstructor, getInstructorCourses)
82 | // Delete a Course
83 | router.delete("/deleteCourse", deleteCourse)
84 |
85 | router.post("/updateCourseProgress", auth, isStudent, updateCourseProgress);
86 |
87 | // ********************************************************************************************************
88 | // Category routes (Only by Admin)
89 | // ********************************************************************************************************
90 | // Category can Only be Created by Admin
91 | // TODO: Put IsAdmin Middleware here
92 | router.post("/createCategory", auth, isAdmin, createCategory)
93 | router.get("/showAllCategories", showAllCategories)
94 | router.post("/getCategoryPageDetails", categoryPageDetails)
95 |
96 | // ********************************************************************************************************
97 | // Rating and Review
98 | // ********************************************************************************************************
99 | router.post("/createRating", auth, isStudent, createRating)
100 | router.get("/getAverageRating", getAverageRating)
101 | router.get("/getReviews", getAllRating)
102 |
103 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/Payments.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | const { capturePayment, verifyPayment, sendPaymentSuccessEmail } = require("../controllers/Payments")
6 | const { auth, isInstructor, isStudent, isAdmin } = require("../middlewares/auth")
7 | router.post("/capturePayment", auth, isStudent, capturePayment)
8 | router.post("/verifyPayment",auth, isStudent, verifyPayment)
9 | router.post("/sendPaymentSuccessEmail", auth, isStudent, sendPaymentSuccessEmail);
10 |
11 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/Profile.js:
--------------------------------------------------------------------------------
1 | const express = require("express")
2 | const router = express.Router()
3 | const { auth, isInstructor } = require("../middlewares/auth")
4 | const {
5 | deleteAccount,
6 | updateProfile,
7 | getAllUserDetails,
8 | updateDisplayPicture,
9 | getEnrolledCourses,
10 | instructorDashboard,
11 | } = require("../controllers/Profile")
12 |
13 | // ********************************************************************************************************
14 | // Profile routes
15 | // ********************************************************************************************************
16 | // Delet User Account
17 | router.delete("/deleteProfile", auth, deleteAccount)
18 | router.put("/updateProfile", auth, updateProfile)
19 | router.get("/getUserDetails", auth, getAllUserDetails)
20 | // Get Enrolled Courses
21 | router.get("/getEnrolledCourses", auth, getEnrolledCourses)
22 | router.put("/updateDisplayPicture", auth, updateDisplayPicture)
23 | router.get("/instructorDashboard", auth, isInstructor, instructorDashboard)
24 |
25 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/User.js:
--------------------------------------------------------------------------------
1 | // Import the required modules
2 | const express = require("express")
3 | const router = express.Router()
4 |
5 | // Import the required controllers and middleware functions
6 | const {
7 | login,
8 | signup,
9 | sendotp,
10 | changePassword,
11 | } = require("../controllers/Auth")
12 | const {
13 | resetPasswordToken,
14 | resetPassword,
15 | } = require("../controllers/ResetPassword")
16 |
17 | const { auth } = require("../middlewares/auth")
18 |
19 | // Routes for Login, Signup, and Authentication
20 |
21 | // ********************************************************************************************************
22 | // Authentication routes
23 | // ********************************************************************************************************
24 |
25 | // Route for user login
26 | router.post("/login", login)
27 |
28 | // Route for user signup
29 | router.post("/signup", signup)
30 |
31 | // Route for sending OTP to the user's email
32 | router.post("/sendotp", sendotp)
33 |
34 | // Route for Changing the password
35 | router.post("/changepassword", auth, changePassword)
36 |
37 | // ********************************************************************************************************
38 | // Reset Password
39 | // ********************************************************************************************************
40 |
41 | // Route for generating a reset password token
42 | router.post("/reset-password-token", resetPasswordToken)
43 |
44 | // Route for resetting user's password after verification
45 | router.post("/reset-password", resetPassword)
46 |
47 | // Export the router for use in the main application
48 | module.exports = router
--------------------------------------------------------------------------------
/server/utils/imageUploader.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require('cloudinary').v2
2 |
3 |
4 | exports.uploadImageToCloudinary = async (file, folder, height, quality) => {
5 | const options = {folder};
6 | if(height) {
7 | options.height = height;
8 | }
9 | if(quality) {
10 | options.quality = quality;
11 | }
12 | options.resource_type = "auto";
13 |
14 | return await cloudinary.uploader.upload(file.tempFilePath, options);
15 | }
--------------------------------------------------------------------------------
/server/utils/mailSender.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 |
3 | const mailSender = async (email, title, body) => {
4 | try{
5 | let transporter = nodemailer.createTransport({
6 | host:process.env.MAIL_HOST,
7 | auth:{
8 | user: process.env.MAIL_USER,
9 | pass: process.env.MAIL_PASS,
10 | }
11 | })
12 |
13 |
14 | let info = await transporter.sendMail({
15 | from: 'StudyNotion || CodeHelp - by Babbar',
16 | to:`${email}`,
17 | subject: `${title}`,
18 | html: `${body}`,
19 | })
20 | console.log(info);
21 | return info;
22 | }
23 | catch(error) {
24 | console.log(error.message);
25 | }
26 | }
27 |
28 |
29 | module.exports = mailSender;
--------------------------------------------------------------------------------
/server/utils/secToDuration.js:
--------------------------------------------------------------------------------
1 | // Helper function to convert total seconds to the duration format
2 | function convertSecondsToDuration(totalSeconds) {
3 | const hours = Math.floor(totalSeconds / 3600)
4 | const minutes = Math.floor((totalSeconds % 3600) / 60)
5 | const seconds = Math.floor((totalSeconds % 3600) % 60)
6 |
7 | if (hours > 0) {
8 | return `${hours}h ${minutes}m`
9 | } else if (minutes > 0) {
10 | return `${minutes}m ${seconds}s`
11 | } else {
12 | return `${seconds}s`
13 | }
14 | }
15 |
16 | module.exports = {
17 | convertSecondsToDuration,
18 | }
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/.DS_Store
--------------------------------------------------------------------------------
/src/assets/Images/Compare_with_others.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Compare_with_others.png
--------------------------------------------------------------------------------
/src/assets/Images/FoundingStory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/FoundingStory.png
--------------------------------------------------------------------------------
/src/assets/Images/Instructor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Instructor.png
--------------------------------------------------------------------------------
/src/assets/Images/Know_your_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Know_your_progress.png
--------------------------------------------------------------------------------
/src/assets/Images/Plan_your_lessons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/Plan_your_lessons.png
--------------------------------------------------------------------------------
/src/assets/Images/TimelineImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/TimelineImage.png
--------------------------------------------------------------------------------
/src/assets/Images/aboutus1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/aboutus1.webp
--------------------------------------------------------------------------------
/src/assets/Images/aboutus2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/aboutus2.webp
--------------------------------------------------------------------------------
/src/assets/Images/aboutus3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/aboutus3.webp
--------------------------------------------------------------------------------
/src/assets/Images/banner.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/banner.mp4
--------------------------------------------------------------------------------
/src/assets/Images/boxoffice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/boxoffice.png
--------------------------------------------------------------------------------
/src/assets/Images/frame.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/frame.png
--------------------------------------------------------------------------------
/src/assets/Images/login.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/login.webp
--------------------------------------------------------------------------------
/src/assets/Images/signup.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Images/signup.webp
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Full-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Full-Dark.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Full-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Full-Light.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Small-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Small-Dark.png
--------------------------------------------------------------------------------
/src/assets/Logo/Logo-Small-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/Logo-Small-Light.png
--------------------------------------------------------------------------------
/src/assets/Logo/rzp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahildahake2003/studynotion/6eaf7f2eac8cd638d08ed26d3b5cb37675f658a3/src/assets/Logo/rzp_logo.png
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TimeLineLogo/Logo4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/ContactPage/ContactDetails.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import * as Icon1 from "react-icons/bi"
3 | import * as Icon3 from "react-icons/hi2"
4 | import * as Icon2 from "react-icons/io5"
5 |
6 | const contactDetails = [
7 | {
8 | icon: "HiChatBubbleLeftRight",
9 | heading: "Chat on us",
10 | description: "Our friendly team is here to help.",
11 | details: "info@studynotion.com",
12 | },
13 | {
14 | icon: "BiWorld",
15 | heading: "Visit us",
16 | description: "Come and say hello at our office HQ.",
17 | details:
18 | "Akshya Nagar 1st Block 1st Cross, Rammurthy nagar, Bangalore-560016",
19 | },
20 | {
21 | icon: "IoCall",
22 | heading: "Call us",
23 | description: "Mon - Fri From 8am to 5pm",
24 | details: "+123 456 7869",
25 | },
26 | ]
27 |
28 | const ContactDetails = () => {
29 | return (
30 |
31 | {contactDetails.map((ele, i) => {
32 | let Icon = Icon1[ele.icon] || Icon2[ele.icon] || Icon3[ele.icon]
33 | return (
34 |
38 |
39 |
40 |
41 | {ele?.heading}
42 |
43 |
44 |
{ele?.description}
45 |
{ele?.details}
46 |
47 | )
48 | })}
49 |
50 | )
51 | }
52 |
53 | export default ContactDetails
--------------------------------------------------------------------------------
/src/components/ContactPage/ContactForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ContactUsForm from "./ContactUsForm";
3 |
4 | const ContactForm = () => {
5 | return (
6 |
7 |
8 | Got a Idea? We've got the skills. Let's team up
9 |
10 |
11 | Tell us more about yourself and what you're got in mind.
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default ContactForm;
--------------------------------------------------------------------------------
/src/components/common/ConfirmationModal.jsx:
--------------------------------------------------------------------------------
1 | import IconBtn from "./IconBtn"
2 |
3 | export default function ConfirmationModal({ modalData }) {
4 | return (
5 |
6 |
7 |
8 | {modalData?.text1}
9 |
10 |
11 | {modalData?.text2}
12 |
13 |
14 |
18 |
22 | {modalData?.btn2Text}
23 |
24 |
25 |
26 |
27 | )
28 | }
--------------------------------------------------------------------------------
/src/components/common/IconBtn.jsx:
--------------------------------------------------------------------------------
1 | export default function IconBtn({
2 | text,
3 | onclick,
4 | children,
5 | disabled,
6 | outline = false,
7 | customClasses,
8 | type,
9 | }) {
10 | return (
11 |
19 | {children ? (
20 | <>
21 | {text}
22 | {children}
23 | >
24 | ) : (
25 | text
26 | )}
27 |
28 | )
29 | }
--------------------------------------------------------------------------------
/src/components/common/RatingStars.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import {
3 | TiStarFullOutline,
4 | TiStarHalfOutline,
5 | TiStarOutline,
6 | } from "react-icons/ti"
7 |
8 | function RatingStars({ Review_Count, Star_Size }) {
9 | const [starCount, SetStarCount] = useState({
10 | full: 0,
11 | half: 0,
12 | empty: 0,
13 | })
14 |
15 | useEffect(() => {
16 | const wholeStars = Math.floor(Review_Count) || 0
17 | SetStarCount({
18 | full: wholeStars,
19 | half: Number.isInteger(Review_Count) ? 0 : 1,
20 | empty: Number.isInteger(Review_Count) ? 5 - wholeStars : 4 - wholeStars,
21 | })
22 | }, [Review_Count])
23 | return (
24 |
25 | {[...new Array(starCount.full)].map((_, i) => {
26 | return
27 | })}
28 | {[...new Array(starCount.half)].map((_, i) => {
29 | return
30 | })}
31 | {[...new Array(starCount.empty)].map((_, i) => {
32 | return
33 | })}
34 |
35 | )
36 | }
37 |
38 | export default RatingStars
--------------------------------------------------------------------------------
/src/components/common/ReviewSlider.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import ReactStars from "react-rating-stars-component"
3 | // Import Swiper React components
4 | import { Swiper, SwiperSlide } from "swiper/react"
5 |
6 | // Import Swiper styles
7 | import "swiper/css"
8 | import "swiper/css/free-mode"
9 | import "swiper/css/pagination"
10 | import "../../App.css"
11 | // Icons
12 | import { FaStar } from "react-icons/fa"
13 | // Import required modules
14 | import { Autoplay, FreeMode, Pagination } from "swiper"
15 |
16 | // Get apiFunction and the endpoint
17 | import { apiConnector } from "../../services/apiconnector"
18 | import { ratingsEndpoints } from "../../services/apis"
19 |
20 | function ReviewSlider() {
21 | const [reviews, setReviews] = useState([])
22 | const truncateWords = 15
23 |
24 | useEffect(() => {
25 | ;(async () => {
26 | const { data } = await apiConnector(
27 | "GET",
28 | ratingsEndpoints.REVIEWS_DETAILS_API
29 | )
30 | if (data?.success) {
31 | setReviews(data?.data)
32 | }
33 | })()
34 | }, [])
35 |
36 | // console.log(reviews)
37 |
38 | return (
39 |
40 |
41 |
53 | {reviews.map((review, i) => {
54 | return (
55 |
56 |
57 |
58 |
67 |
68 |
{`${review?.user?.firstName} ${review?.user?.lastName}`}
69 |
70 | {review?.course?.courseName}
71 |
72 |
73 |
74 |
75 | {review?.review.split(" ").length > truncateWords
76 | ? `${review?.review
77 | .split(" ")
78 | .slice(0, truncateWords)
79 | .join(" ")} ...`
80 | : `${review?.review}`}
81 |
82 |
83 |
84 | {review.rating.toFixed(1)}
85 |
86 | }
93 | fullIcon={ }
94 | />
95 |
96 |
97 |
98 | )
99 | })}
100 | {/* Slide 1 */}
101 |
102 |
103 |
104 | )
105 | }
106 |
107 | export default ReviewSlider
--------------------------------------------------------------------------------
/src/components/common/Tab.jsx:
--------------------------------------------------------------------------------
1 | export default function Tab({ tabData, field, setField }) {
2 | return (
3 |
9 | {tabData.map((tab) => (
10 | 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/core/AboutPage/ContactFormSection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ContactUsForm from "../../ContactPage/ContactUsForm";
3 |
4 | const ContactFormSection = () => {
5 | return (
6 |
7 |
Get in Touch
8 |
9 | We'd love to here for you, Please fill out this form.
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default ContactFormSection;
--------------------------------------------------------------------------------
/src/components/core/AboutPage/LearningGrid.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import HighlightText from "../../../components/core/HomePage/HighlightText";
3 | import CTAButton from "../../../components/core/HomePage/Button";
4 |
5 | const LearningGridArray = [
6 | {
7 | order: -1,
8 | heading: "World-Class Learning for",
9 | highlightText: "Anyone, Anywhere",
10 | description:
11 | "Studynotion partners with more than 275+ leading universities and companies to bring flexible, affordable, job-relevant online learning to individuals and organizations worldwide.",
12 | BtnText: "Learn More",
13 | BtnLink: "/",
14 | },
15 | {
16 | order: 1,
17 | heading: "Curriculum Based on Industry Needs",
18 | description:
19 | "Save time and money! The Belajar curriculum is made to be easier to understand and in line with industry needs.",
20 | },
21 | {
22 | order: 2,
23 | heading: "Our Learning Methods",
24 | description:
25 | "Studynotion partners with more than 275+ leading universities and companies to bring",
26 | },
27 | {
28 | order: 3,
29 | heading: "Certification",
30 | description:
31 | "Studynotion partners with more than 275+ leading universities and companies to bring",
32 | },
33 | {
34 | order: 4,
35 | heading: `Rating "Auto-grading"`,
36 | description:
37 | "Studynotion partners with more than 275+ leading universities and companies to bring",
38 | },
39 | {
40 | order: 5,
41 | heading: "Ready to Work",
42 | description:
43 | "Studynotion partners with more than 275+ leading universities and companies to bring",
44 | },
45 | ];
46 |
47 | const LearningGrid = () => {
48 | return (
49 |
50 | {LearningGridArray.map((card, i) => {
51 | return (
52 |
62 | {card.order < 0 ? (
63 |
64 |
65 | {card.heading}
66 |
67 |
68 |
69 | {card.description}
70 |
71 |
72 |
73 |
74 | {card.BtnText}
75 |
76 |
77 |
78 | ) : (
79 |
80 |
{card.heading}
81 |
82 |
83 | {card.description}
84 |
85 |
86 | )}
87 |
88 | );
89 | })}
90 |
91 | );
92 | };
93 |
94 | export default LearningGrid;
--------------------------------------------------------------------------------
/src/components/core/AboutPage/Quote.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from '../HomePage/HighlightText'
3 |
4 | const Quote = () => {
5 | return (
6 |
7 | We are passionate about revolutionizing the way we learn. Our
8 | innovative platform ,{" "}
9 |
10 | {" "}
11 | expertise
12 |
13 | , and community to create an
14 |
15 | {" "}
16 | unparalleled educational
17 | experience.
18 |
19 |
20 | )
21 | }
22 |
23 | export default Quote
--------------------------------------------------------------------------------
/src/components/core/AboutPage/Stats.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Stats = [
4 | { count: "5K", label: "Active Students" },
5 | { count: "10+", label: "Mentors" },
6 | { count: "200+", label: "Courses" },
7 | { count: "50+", label: "Awards" },
8 | ];
9 |
10 | const StatsComponenet = () => {
11 | return (
12 |
13 | {/* Stats */}
14 |
15 |
16 | {Stats.map((data, index) => {
17 | return (
18 |
19 |
20 | {data.count}
21 |
22 |
23 | {data.label}
24 |
25 |
26 | );
27 | })}
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default StatsComponenet;
--------------------------------------------------------------------------------
/src/components/core/Auth/LoginForm.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai"
3 | import { useDispatch } from "react-redux"
4 | import { Link, useNavigate } from "react-router-dom"
5 |
6 | import { login } from "../../../services/operations/authAPI"
7 |
8 | function LoginForm() {
9 | const navigate = useNavigate()
10 | const dispatch = useDispatch()
11 | const [formData, setFormData] = useState({
12 | email: "",
13 | password: "",
14 | })
15 |
16 | const [showPassword, setShowPassword] = useState(false)
17 |
18 | const { email, password } = formData
19 |
20 | const handleOnChange = (e) => {
21 | setFormData((prevData) => ({
22 | ...prevData,
23 | [e.target.name]: e.target.value,
24 | }))
25 | }
26 |
27 | const handleOnSubmit = (e) => {
28 | e.preventDefault()
29 | dispatch(login(email, password, navigate))
30 | }
31 |
32 | return (
33 |
93 | )
94 | }
95 |
96 | export default LoginForm
--------------------------------------------------------------------------------
/src/components/core/Auth/OpenRoute.jsx:
--------------------------------------------------------------------------------
1 | // This will prevent authenticated users from accessing this route
2 | import { useSelector } from "react-redux"
3 | import { Navigate } from "react-router-dom"
4 |
5 | function OpenRoute({ children }) {
6 | const { token } = useSelector((state) => state.auth)
7 |
8 | if (token === null) {
9 | return children
10 | } else {
11 | return
12 | }
13 | }
14 |
15 | export default OpenRoute
--------------------------------------------------------------------------------
/src/components/core/Auth/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { Navigate } from 'react-router-dom';
4 |
5 | const PrivateRoute = ({children}) => {
6 |
7 | const {token} = useSelector((state) => state.auth);
8 |
9 | if(token !== null)
10 | return children
11 | else
12 | return
13 |
14 | }
15 |
16 | export default PrivateRoute
17 |
--------------------------------------------------------------------------------
/src/components/core/Auth/ProfileDropDown.jsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react"
2 | import { AiOutlineCaretDown } from "react-icons/ai"
3 | import { VscDashboard, VscSignOut } from "react-icons/vsc"
4 | import { useDispatch, useSelector } from "react-redux"
5 | import { Link, useNavigate } from "react-router-dom"
6 |
7 | import useOnClickOutside from "../../../hooks/useOnClickOutside"
8 | import { logout } from "../../../services/operations/authAPI"
9 |
10 | export default function ProfileDropdown() {
11 | const { user } = useSelector((state) => state.profile)
12 | const dispatch = useDispatch()
13 | const navigate = useNavigate()
14 | const [open, setOpen] = useState(false)
15 | const ref = useRef(null)
16 |
17 | useOnClickOutside(ref, () => setOpen(false))
18 |
19 | if (!user) return null
20 |
21 | return (
22 | 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 |
3 | import {Swiper, SwiperSlide} from "swiper/react"
4 | import "swiper/css"
5 | import "swiper/css/free-mode"
6 | import "swiper/css/pagination"
7 | import { Autoplay,FreeMode,Navigation, Pagination} from 'swiper'
8 |
9 | import Course_Card from './Course_Card'
10 |
11 | const CourseSlider = ({Courses}) => {
12 | return (
13 | <>
14 | {Courses?.length ? (
15 |
27 | {Courses?.map((course, i) => (
28 |
29 |
30 |
31 | ))}
32 |
33 | ) : (
34 | No Course Found
35 | )}
36 | >
37 | )
38 | }
39 |
40 | export default CourseSlider
41 |
--------------------------------------------------------------------------------
/src/components/core/Catalog/Course_Card.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import RatingStars from '../../common/RatingStars'
3 | import GetAvgRating from '../../../utils/avgRating';
4 | import { Link } from 'react-router-dom';
5 |
6 | const Course_Card = ({course, Height}) => {
7 |
8 |
9 | const [avgReviewCount, setAvgReviewCount] = useState(0);
10 |
11 | useEffect(()=> {
12 | const count = GetAvgRating(course.ratingAndReviews);
13 | setAvgReviewCount(count);
14 | },[course])
15 |
16 |
17 |
18 | return (
19 | <>
20 |
21 |
22 |
23 |
28 |
29 |
30 |
{course?.courseName}
31 |
32 | {course?.instructor?.firstName} {course?.instructor?.lastName}
33 |
34 |
35 | {avgReviewCount || 0}
36 |
37 |
38 | {course?.ratingAndReviews?.length} Ratings
39 |
40 |
41 |
Rs. {course?.price}
42 |
43 |
44 |
45 | >
46 | )
47 | }
48 |
49 | export default Course_Card
50 |
--------------------------------------------------------------------------------
/src/components/core/Course/CourseAccordionBar.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react"
2 | import { AiOutlineDown } from "react-icons/ai"
3 |
4 | import CourseSubSectionAccordion from "./CourseSubSectionAccordion"
5 |
6 | export default function CourseAccordionBar({ course, isActive, handleActive }) {
7 | const contentEl = useRef(null)
8 |
9 | // Accordian state
10 | const [active, setActive] = useState(false)
11 | useEffect(() => {
12 | setActive(isActive?.includes(course._id))
13 | }, [isActive])
14 | const [sectionHeight, setSectionHeight] = useState(0)
15 | useEffect(() => {
16 | setSectionHeight(active ? contentEl.current.scrollHeight : 0)
17 | }, [active])
18 |
19 | return (
20 |
21 |
22 |
{
25 | handleActive(course._id)
26 | }}
27 | >
28 |
29 |
34 |
35 |
36 |
{course?.sectionName}
37 |
38 |
39 |
40 | {`${course.subSection.length || 0} lecture(s)`}
41 |
42 |
43 |
44 |
45 |
52 |
53 | {course?.subSection?.map((subSec, i) => {
54 | return
55 | })}
56 |
57 |
58 |
59 | )
60 | }
--------------------------------------------------------------------------------
/src/components/core/Course/CourseDetailsCard.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import copy from "copy-to-clipboard"
3 | import { toast } from "react-hot-toast"
4 | import { BsFillCaretRightFill } from "react-icons/bs"
5 | import { FaShareSquare } from "react-icons/fa"
6 | import { useDispatch, useSelector } from "react-redux"
7 | import { useNavigate } from "react-router-dom"
8 |
9 | import { addToCart } from "../../../slices/cartSlice"
10 | import { ACCOUNT_TYPE } from "../../../utils/constants"
11 |
12 |
13 | function CourseDetailsCard({ course, setConfirmationModal, handleBuyCourse }) {
14 | const { user } = useSelector((state) => state.profile)
15 | const { token } = useSelector((state) => state.auth)
16 | const navigate = useNavigate()
17 | const dispatch = useDispatch()
18 |
19 | const {
20 | thumbnail: ThumbnailImage,
21 | price: CurrentPrice,
22 | _id: courseId,
23 | } = course
24 |
25 | const handleShare = () => {
26 | copy(window.location.href)
27 | toast.success("Link copied to clipboard")
28 | }
29 |
30 | const handleAddToCart = () => {
31 | if (user && user?.accountType === ACCOUNT_TYPE.INSTRUCTOR) {
32 | toast.error("You are an Instructor. You can't buy a course.")
33 | return
34 | }
35 | if (token) {
36 | dispatch(addToCart(course))
37 | return
38 | }
39 | setConfirmationModal({
40 | text1: "You are not logged in!",
41 | text2: "Please login to add To Cart",
42 | btn1Text: "Login",
43 | btn2Text: "Cancel",
44 | btn1Handler: () => navigate("/login"),
45 | btn2Handler: () => setConfirmationModal(null),
46 | })
47 | }
48 |
49 | // console.log("Student already enrolled ", course?.studentsEnroled, user?._id)
50 |
51 | return (
52 | <>
53 |
56 | {/* Course Image */}
57 |
62 |
63 |
64 |
65 | Rs. {CurrentPrice}
66 |
67 |
68 | navigate("/dashboard/enrolled-courses")
73 | : handleBuyCourse
74 | }
75 | >
76 | {user && course?.studentsEnrolled.includes(user?._id)
77 | ? "Go To Course"
78 | : "Buy Now"}
79 |
80 | {(!user || !course?.studentsEnrolled.includes(user?._id)) && (
81 |
82 | Add to Cart
83 |
84 | )}
85 |
86 |
87 |
88 | 30-Day Money-Back Guarantee
89 |
90 |
91 |
92 |
93 |
94 | This Course Includes :
95 |
96 |
97 | {course?.instructions?.map((item, i) => {
98 | return (
99 |
100 |
101 | {item}
102 |
103 | )
104 | })}
105 |
106 |
107 |
108 |
112 | Share
113 |
114 |
115 |
116 |
117 | >
118 | )
119 | }
120 |
121 | export default CourseDetailsCard
--------------------------------------------------------------------------------
/src/components/core/Course/CourseSubSectionAccordion.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react"
2 | import { AiOutlineDown } from "react-icons/ai"
3 | import { HiOutlineVideoCamera } from "react-icons/hi"
4 |
5 | function CourseSubSectionAccordion({ subSec }) {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
{subSec?.title}
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default CourseSubSectionAccordion
--------------------------------------------------------------------------------
/src/components/core/Dashboard/AddCourse/CourseInformation/ChipInput.jsx:
--------------------------------------------------------------------------------
1 | // Importing React hook for managing component state
2 | import { useEffect, useState } from "react"
3 | // Importing React icon component
4 | import { MdClose } from "react-icons/md"
5 | import { useSelector } from "react-redux"
6 |
7 | // Defining a functional component ChipInput
8 | export default function ChipInput({
9 | // Props to be passed to the component
10 | label,
11 | name,
12 | placeholder,
13 | register,
14 | errors,
15 | setValue,
16 | getValues,
17 | }) {
18 | const { editCourse, course } = useSelector((state) => state.course)
19 |
20 | // Setting up state for managing chips array
21 | const [chips, setChips] = useState([])
22 |
23 | useEffect(() => {
24 | if (editCourse) {
25 | // console.log(course)
26 | setChips(course?.tag)
27 | }
28 | register(name, { required: true, validate: (value) => value.length > 0 })
29 | // eslint-disable-next-line react-hooks/exhaustive-deps
30 | }, [])
31 |
32 | useEffect(() => {
33 | setValue(name, chips)
34 | // eslint-disable-next-line react-hooks/exhaustive-deps
35 | }, [chips])
36 |
37 | // Function to handle user input when chips are added
38 | const handleKeyDown = (event) => {
39 | // Check if user presses "Enter" or ","
40 | if (event.key === "Enter" || event.key === ",") {
41 | // Prevent the default behavior of the event
42 | event.preventDefault()
43 | // Get the input value and remove any leading/trailing spaces
44 | const chipValue = event.target.value.trim()
45 | // Check if the input value exists and is not already in the chips array
46 | if (chipValue && !chips.includes(chipValue)) {
47 | // Add the chip to the array and clear the input
48 | const newChips = [...chips, chipValue]
49 | setChips(newChips)
50 | event.target.value = ""
51 | }
52 | }
53 | }
54 |
55 | // Function to handle deletion of a chip
56 | const handleDeleteChip = (chipIndex) => {
57 | // Filter the chips array to remove the chip with the given index
58 | const newChips = chips.filter((_, index) => index !== chipIndex)
59 | setChips(newChips)
60 | }
61 |
62 | // Render the component
63 | return (
64 |
65 | {/* Render the label for the input */}
66 |
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/Upload.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react"
2 | import { useDropzone } from "react-dropzone"
3 | import { FiUploadCloud } from "react-icons/fi"
4 | import { useSelector } from "react-redux"
5 |
6 | import "video-react/dist/video-react.css"
7 | import { Player } from "video-react"
8 |
9 | export default function Upload({
10 | name,
11 | label,
12 | register,
13 | setValue,
14 | errors,
15 | video = false,
16 | viewData = null,
17 | editData = null,
18 | }) {
19 | const { course } = useSelector((state) => state.course)
20 | const [selectedFile, setSelectedFile] = useState(null)
21 | const [previewSource, setPreviewSource] = useState(
22 | viewData ? viewData : editData ? editData : ""
23 | )
24 | const inputRef = useRef(null)
25 |
26 | const onDrop = (acceptedFiles) => {
27 | const file = acceptedFiles[0]
28 | if (file) {
29 | previewFile(file)
30 | setSelectedFile(file)
31 | }
32 | }
33 |
34 | const { getRootProps, getInputProps, isDragActive } = useDropzone({
35 | accept: !video
36 | ? { "image/*": [".jpeg", ".jpg", ".png"] }
37 | : { "video/*": [".mp4"] },
38 | onDrop,
39 | })
40 |
41 | const previewFile = (file) => {
42 | // console.log(file)
43 | const reader = new FileReader()
44 | reader.readAsDataURL(file)
45 | reader.onloadend = () => {
46 | setPreviewSource(reader.result)
47 | }
48 | }
49 |
50 | useEffect(() => {
51 | register(name, { required: true })
52 | // eslint-disable-next-line react-hooks/exhaustive-deps
53 | }, [register])
54 |
55 | useEffect(() => {
56 | setValue(name, selectedFile)
57 | // eslint-disable-next-line react-hooks/exhaustive-deps
58 | }, [selectedFile, setValue])
59 |
60 | return (
61 |
62 |
63 | {label} {!viewData && * }
64 |
65 |
70 | {previewSource ? (
71 |
72 | {!video ? (
73 |
78 | ) : (
79 |
80 | )}
81 | {!viewData && (
82 |
{
85 | setPreviewSource("")
86 | setSelectedFile(null)
87 | setValue(name, null)
88 | }}
89 | className="mt-3 text-richblack-400 underline"
90 | >
91 | Cancel
92 |
93 | )}
94 |
95 | ) : (
96 |
100 |
101 |
102 |
103 |
104 |
105 | Drag and drop an {!video ? "image" : "video"}, or click to{" "}
106 | Browse a
107 | file
108 |
109 |
110 | Aspect ratio 16:9
111 | Recommended size 1024x576
112 |
113 |
114 | )}
115 |
116 | {errors[name] && (
117 |
118 | {label} is required
119 |
120 | )}
121 |
122 | )
123 | }
--------------------------------------------------------------------------------
/src/components/core/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/UpdatePassword.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { useForm } from "react-hook-form"
3 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai"
4 | import { useSelector } from "react-redux"
5 | import { useNavigate } from "react-router-dom"
6 |
7 | import { changePassword } from "../../../../services/operations/SettingsAPI"
8 | import IconBtn from "../../../common/IconBtn"
9 |
10 | export default function UpdatePassword() {
11 | const { token } = useSelector((state) => state.auth)
12 | const navigate = useNavigate()
13 |
14 | const [showOldPassword, setShowOldPassword] = useState(false)
15 | const [showNewPassword, setShowNewPassword] = useState(false)
16 |
17 | const {
18 | register,
19 | handleSubmit,
20 | formState: { errors },
21 | } = useForm()
22 |
23 | const submitPasswordForm = async (data) => {
24 | // console.log("password Data - ", data)
25 | try {
26 | await changePassword(token, data)
27 | } catch (error) {
28 | console.log("ERROR MESSAGE - ", error.message)
29 | }
30 | }
31 |
32 | return (
33 | <>
34 |
108 | >
109 | )
110 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Settings/index.jsx:
--------------------------------------------------------------------------------
1 | import ChangeProfilePicture from "./ChangeProfilePicture"
2 | import DeleteAccount from "./DeleteAccount"
3 | import EditProfile from "./EditProfile"
4 | import UpdatePassword from "./UpdatePassword"
5 |
6 | export default function Settings() {
7 | return (
8 | <>
9 |
10 | Edit Profile
11 |
12 | {/* Change Profile Picture */}
13 |
14 | {/* Profile */}
15 |
16 | {/* Password */}
17 |
18 | {/* Delete Account */}
19 |
20 | >
21 | )
22 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { VscSignOut } from "react-icons/vsc"
3 | import { useDispatch, useSelector } from "react-redux"
4 | import { useNavigate } from "react-router-dom"
5 |
6 | import { sidebarLinks } from "../../../data/dashboard-links"
7 | import { logout } from "../../../services/operations/authAPI"
8 | import ConfirmationModal from "../../common/ConfirmationModal"
9 | import SidebarLink from "./SidebarLink"
10 |
11 | export default function Sidebar() {
12 | const { user, loading: profileLoading } = useSelector(
13 | (state) => state.profile
14 | )
15 | const { loading: authLoading } = useSelector((state) => state.auth)
16 | const dispatch = useDispatch()
17 | const navigate = useNavigate()
18 | // to keep track of confirmation modal
19 | const [confirmationModal, setConfirmationModal] = useState(null)
20 |
21 | if (profileLoading || authLoading) {
22 | return (
23 |
26 | )
27 | }
28 |
29 | return (
30 | <>
31 |
32 |
33 | {sidebarLinks.map((link) => {
34 | if (link.type && user?.accountType !== link.type) return null
35 | return (
36 |
37 | )
38 | })}
39 |
40 |
41 |
42 |
46 |
48 | setConfirmationModal({
49 | text1: "Are you sure?",
50 | text2: "You will be logged out of your account.",
51 | btn1Text: "Logout",
52 | btn2Text: "Cancel",
53 | btn1Handler: () => dispatch(logout(navigate)),
54 | btn2Handler: () => setConfirmationModal(null),
55 | })
56 | }
57 | className="px-8 py-2 text-sm font-medium text-richblack-300"
58 | >
59 |
60 |
61 | Logout
62 |
63 |
64 |
65 |
66 | {confirmationModal && }
67 | >
68 | )
69 | }
--------------------------------------------------------------------------------
/src/components/core/Dashboard/SidebarLink.jsx:
--------------------------------------------------------------------------------
1 | import * as Icons from "react-icons/vsc"
2 | import { useDispatch } from "react-redux"
3 | import { NavLink, matchPath, useLocation } from "react-router-dom"
4 |
5 | import { resetCourseState } from "../../../slices/courseSlice"
6 |
7 | export default function SidebarLink({ link, iconName }) {
8 | const Icon = Icons[iconName]
9 | const location = useLocation()
10 | const dispatch = useDispatch()
11 |
12 | const matchRoute = (route) => {
13 | return matchPath({ path: route }, location.pathname)
14 | }
15 |
16 | return (
17 | dispatch(resetCourseState())}
20 | className={`relative px-8 py-2 text-sm font-medium ${
21 | matchRoute(link.path)
22 | ? "bg-yellow-800 text-yellow-50"
23 | : "bg-opacity-0 text-richblack-300"
24 | } transition-all duration-200`}
25 | >
26 |
31 |
32 | {/* Icon Goes Here */}
33 |
34 | {link.name}
35 |
36 |
37 | )
38 | }
--------------------------------------------------------------------------------
/src/components/core/HomePage/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | const Button = ({ children, active, linkto }) => {
5 | return (
6 |
7 |
12 | {children}
13 |
14 |
15 | );
16 | };
17 |
18 | export default Button;
--------------------------------------------------------------------------------
/src/components/core/HomePage/CodeBlocks.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CTAButton from "./Button";
3 | import { TypeAnimation } from "react-type-animation";
4 | import { FaArrowRight } from "react-icons/fa";
5 |
6 | const CodeBlocks = ({
7 | position,
8 | heading,
9 | subheading,
10 | ctabtn1,
11 | ctabtn2,
12 | codeblock,
13 | backgroundGradient,
14 | codeColor,
15 | }) => {
16 | return (
17 |
18 |
19 |
20 | {/* Section 1 */}
21 |
22 | {heading}
23 |
24 | {/* Sub Heading */}
25 |
26 | {subheading}
27 |
28 |
29 | {/* Button Group */}
30 |
31 |
32 |
33 | {ctabtn1.btnText}
34 |
35 |
36 |
37 |
38 | {ctabtn2.btnText}
39 |
40 |
41 |
42 |
43 | {/* Section 2 */}
44 |
45 | {backgroundGradient}
46 | {/* Indexing */}
47 |
48 |
1
49 |
2
50 |
3
51 |
4
52 |
5
53 |
6
54 |
7
55 |
8
56 |
9
57 |
10
58 |
11
59 |
60 |
61 | {/* Codes */}
62 |
65 |
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export default CodeBlocks;
--------------------------------------------------------------------------------
/src/components/core/HomePage/CourseCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | // Importing React Icons
4 | import { HiUsers } from "react-icons/hi";
5 | import { ImTree } from "react-icons/im";
6 |
7 | const CourseCard = ({cardData, currentCard, setCurrentCard}) => {
8 | return (
9 | setCurrentCard(cardData?.heading)}
16 | >
17 |
18 |
23 | {cardData?.heading}
24 |
25 |
26 |
{cardData?.description}
27 |
28 |
29 |
34 | {/* Level */}
35 |
36 |
37 |
{cardData?.level}
38 |
39 |
40 | {/* Flow Chart */}
41 |
42 |
43 |
{cardData?.lessionNumber} Lession
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default CourseCard;
--------------------------------------------------------------------------------
/src/components/core/HomePage/ExploreMore.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { HomePageExplore } from "../../../data/homepage-explore";
3 | import CourseCard from "./CourseCard";
4 | import HighlightText from "./HighlightText";
5 |
6 | const tabsName = [
7 | "Free",
8 | "New to coding",
9 | "Most popular",
10 | "Skills paths",
11 | "Career paths",
12 | ];
13 |
14 | const ExploreMore = () => {
15 | const [currentTab, setCurrentTab] = useState(tabsName[0]);
16 | const [courses, setCourses] = useState(HomePageExplore[0].courses);
17 | const [currentCard, setCurrentCard] = useState(
18 | HomePageExplore[0].courses[0].heading
19 | );
20 |
21 | const setMyCards = (value) => {
22 | setCurrentTab(value);
23 | const result = HomePageExplore.filter((course) => course.tag === value);
24 | setCourses(result[0].courses);
25 | setCurrentCard(result[0].courses[0].heading);
26 | };
27 |
28 | return (
29 |
30 | {/* Explore more section */}
31 |
32 |
33 | Unlock the
34 |
35 |
36 | Learn to Build Anything You Can Imagine
37 |
38 |
39 |
40 |
41 | {/* Tabs Section */}
42 |
43 | {tabsName.map((ele, index) => {
44 | return (
45 |
setMyCards(ele)}
53 | >
54 | {ele}
55 |
56 | );
57 | })}
58 |
59 |
60 |
61 | {/* Cards Group */}
62 |
63 | {courses.map((ele, index) => {
64 | return (
65 |
71 | );
72 | })}
73 |
74 |
75 | );
76 | };
77 |
78 | export default ExploreMore;
--------------------------------------------------------------------------------
/src/components/core/HomePage/HighlightText.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const HighlightText = ({text}) => {
4 | return (
5 |
6 | {" "}
7 | {text}
8 |
9 | );
10 | };
11 |
12 | export default HighlightText;
--------------------------------------------------------------------------------
/src/components/core/HomePage/InstructorSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CTAButton from "../../../components/core/HomePage/Button";
3 | import { FaArrowRight } from "react-icons/fa";
4 | import Instructor from "../../../assets/Images/Instructor.png";
5 | import HighlightText from './HighlightText';
6 |
7 | const InstructorSection = () => {
8 | return (
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 | Become an
21 |
22 |
23 |
24 |
25 | Instructors from around the world teach millions of students on
26 | StudyNotion. We provide the tools and skills to teach what you
27 | love.
28 |
29 |
30 |
31 |
32 |
33 | Start Teaching Today
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default InstructorSection
--------------------------------------------------------------------------------
/src/components/core/HomePage/LearningLanguageSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HighlightText from './HighlightText'
3 | import CTAButton from "../../../components/core/HomePage/Button";
4 | import Know_your_progress from "../../../assets/Images/Know_your_progress.png";
5 | import Compare_with_others from "../../../assets/Images/Compare_with_others.svg";
6 | import Plan_your_lessons from "../../../assets/Images/Plan_your_lessons.svg";
7 |
8 | const LearningLanguageSection = () => {
9 | return (
10 |
11 |
12 | Your swiss knife for
13 |
14 |
15 | Using spin making learning multiple languages easy. with 20+
16 | languages realistic voice-over, progress tracking, custom schedule
17 | and more.
18 |
19 |
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/components/core/ViewCourse/CourseReviewModal.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 | import { useForm } from "react-hook-form"
3 | import { RxCross2 } from "react-icons/rx"
4 | import ReactStars from "react-rating-stars-component"
5 | import { useSelector } from "react-redux"
6 |
7 | import { createRating } from "../../../services/operations/courseDetailsAPI"
8 | import IconBtn from "../../common/IconBtn"
9 |
10 | export default function CourseReviewModal({ setReviewModal }) {
11 | const { user } = useSelector((state) => state.profile)
12 | const { token } = useSelector((state) => state.auth)
13 | const { courseEntireData } = useSelector((state) => state.viewCourse)
14 |
15 | const {
16 | register,
17 | handleSubmit,
18 | setValue,
19 | formState: { errors },
20 | } = useForm()
21 |
22 | useEffect(() => {
23 | setValue("courseExperience", "")
24 | setValue("courseRating", 0)
25 | // eslint-disable-next-line react-hooks/exhaustive-deps
26 | }, [])
27 |
28 | const ratingChanged = (newRating) => {
29 | // console.log(newRating)
30 | setValue("courseRating", newRating)
31 | }
32 |
33 | const onSubmit = async (data) => {
34 | await createRating(
35 | {
36 | courseId: courseEntireData._id,
37 | rating: data.courseRating,
38 | review: data.courseExperience,
39 | },
40 | token
41 | )
42 | setReviewModal(false)
43 | }
44 |
45 | return (
46 |
47 |
48 | {/* Modal Header */}
49 |
50 |
Add Review
51 |
setReviewModal(false)}>
52 |
53 |
54 |
55 | {/* Modal Body */}
56 |
57 |
58 |
63 |
64 |
65 | {user?.firstName} {user?.lastName}
66 |
67 |
Posting Publicly
68 |
69 |
70 |
109 |
110 |
111 |
112 | )
113 | }
--------------------------------------------------------------------------------
/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: "-", link: "/hi"},
51 | {title: "Full Catalog", link: "/full-catalog"},
52 | {title: "Beta Content", link: "/beta-content"}
53 | ]
54 | }
55 | ];
56 |
--------------------------------------------------------------------------------
/src/data/navbar-links.js:
--------------------------------------------------------------------------------
1 | export const NavbarLinks = [
2 | {
3 | title: "Home",
4 | path: "/",
5 | },
6 | {
7 | title: "Catalog",
8 | // path: '/catalog',
9 | },
10 | {
11 | title: "About Us",
12 | path: "/about",
13 | },
14 | {
15 | title: "Contact Us",
16 | path: "/contact",
17 | },
18 | ];
19 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/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/Contact.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import Footer from "../components/common/Footer"
4 | import ContactDetails from "../components/ContactPage/ContactDetails"
5 | import ContactForm from "../components/ContactPage/ContactForm"
6 | import ReviewSlider from "../components/common/ReviewSlider"
7 |
8 | const Contact = () => {
9 | return (
10 |
11 |
12 | {/* Contact Details */}
13 |
14 |
15 |
16 |
17 | {/* Contact Form */}
18 |
19 |
20 |
21 |
22 |
23 | {/* Reviws from Other Learner */}
24 |
25 | Reviews from other learners
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | 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
--------------------------------------------------------------------------------
/src/pages/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Error = () => {
4 | return (
5 |
6 | Error - 404 Not found
7 |
8 | )
9 | }
10 |
11 | export default Error
12 |
--------------------------------------------------------------------------------
/src/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 |
34 | {!emailSent && (
35 |
36 |
37 | Email Address *
38 |
39 | setEmail(e.target.value)}
45 | placeholder="Enter email address"
46 | className="form-style w-full"
47 | />
48 |
49 | )}
50 |
54 | {!emailSent ? "Sumbit" : "Resend Email"}
55 |
56 |
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/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 |
64 | (
69 |
77 | )}
78 | containerStyle={{
79 | justifyContent: "space-between",
80 | gap: "0 6px",
81 | }}
82 | />
83 |
87 | Verify Email
88 |
89 |
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 = "https://localhost:4000/api/v1"
2 |
3 | // AUTH ENDPOINTS
4 | export const endpoints = {
5 | SENDOTP_API: BASE_URL + "/auth/sendotp",
6 | SIGNUP_API: BASE_URL + "/auth/signup",
7 | LOGIN_API: BASE_URL + "/auth/login",
8 | RESETPASSTOKEN_API: BASE_URL + "/auth/reset-password-token",
9 | RESETPASSWORD_API: BASE_URL + "/auth/reset-password",
10 | }
11 |
12 | // PROFILE ENDPOINTS
13 | export const profileEndpoints = {
14 | GET_USER_DETAILS_API: BASE_URL + "/profile/getUserDetails",
15 | GET_USER_ENROLLED_COURSES_API: BASE_URL + "/profile/getEnrolledCourses",
16 | GET_INSTRUCTOR_DATA_API: BASE_URL + "/profile/instructorDashboard",
17 | }
18 |
19 | // STUDENTS ENDPOINTS
20 | export const studentEndpoints = {
21 | COURSE_PAYMENT_API: BASE_URL + "/payment/capturePayment",
22 | COURSE_VERIFY_API: BASE_URL + "/payment/verifyPayment",
23 | SEND_PAYMENT_SUCCESS_EMAIL_API: BASE_URL + "/payment/sendPaymentSuccessEmail",
24 | }
25 |
26 | // COURSE ENDPOINTS
27 | export const courseEndpoints = {
28 | GET_ALL_COURSE_API: BASE_URL + "/course/getAllCourses",
29 | COURSE_DETAILS_API: BASE_URL + "/course/getCourseDetails",
30 | EDIT_COURSE_API: BASE_URL + "/course/editCourse",
31 | COURSE_CATEGORIES_API: BASE_URL + "/course/showAllCategories",
32 | CREATE_COURSE_API: BASE_URL + "/course/createCourse",
33 | CREATE_SECTION_API: BASE_URL + "/course/addSection",
34 | CREATE_SUBSECTION_API: BASE_URL + "/course/addSubSection",
35 | UPDATE_SECTION_API: BASE_URL + "/course/updateSection",
36 | UPDATE_SUBSECTION_API: BASE_URL + "/course/updateSubSection",
37 | GET_ALL_INSTRUCTOR_COURSES_API: BASE_URL + "/course/getInstructorCourses",
38 | DELETE_SECTION_API: BASE_URL + "/course/deleteSection",
39 | DELETE_SUBSECTION_API: BASE_URL + "/course/deleteSubSection",
40 | DELETE_COURSE_API: BASE_URL + "/course/deleteCourse",
41 | GET_FULL_COURSE_DETAILS_AUTHENTICATED:
42 | BASE_URL + "/course/getFullCourseDetails",
43 | LECTURE_COMPLETION_API: BASE_URL + "/course/updateCourseProgress",
44 | CREATE_RATING_API: BASE_URL + "/course/createRating",
45 | }
46 |
47 | // RATINGS AND REVIEWS
48 | export const ratingsEndpoints = {
49 | REVIEWS_DETAILS_API: BASE_URL + "/course/getReviews",
50 | }
51 |
52 | // CATAGORIES API
53 | export const categories = {
54 | CATEGORIES_API: BASE_URL + "/course/showAllCategories",
55 | }
56 |
57 | // CATALOG PAGE DATA
58 | export const catalogData = {
59 | CATALOGPAGEDATA_API: BASE_URL + "/course/getCategoryPageDetails",
60 | }
61 | // CONTACT-US API
62 | export const contactusEndpoint = {
63 | CONTACT_US_API: BASE_URL + "/reach/contact",
64 | }
65 |
66 | // SETTINGS PAGE API
67 | export const settingsEndpoints = {
68 | UPDATE_DISPLAY_PICTURE_API: BASE_URL + "/profile/updateDisplayPicture",
69 | UPDATE_PROFILE_API: BASE_URL + "/profile/updateProfile",
70 | CHANGE_PASSWORD_API: BASE_URL + "/auth/changepassword",
71 | DELETE_PROFILE_API: BASE_URL + "/profile/deleteProfile",
72 | }
--------------------------------------------------------------------------------
/src/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/SettingsAPI.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-hot-toast"
2 |
3 | import { setUser } from "../../slices/profileSlice"
4 | import { apiConnector } from "../apiconnector"
5 | import { settingsEndpoints } from "../apis"
6 | import { logout } from "./authAPI"
7 |
8 | const {
9 | UPDATE_DISPLAY_PICTURE_API,
10 | UPDATE_PROFILE_API,
11 | CHANGE_PASSWORD_API,
12 | DELETE_PROFILE_API,
13 | } = settingsEndpoints
14 |
15 | export function updateDisplayPicture(token, formData) {
16 | return async (dispatch) => {
17 | const toastId = toast.loading("Loading...")
18 | try {
19 | const response = await apiConnector(
20 | "PUT",
21 | UPDATE_DISPLAY_PICTURE_API,
22 | formData,
23 | {
24 | "Content-Type": "multipart/form-data",
25 | Authorization: `Bearer ${token}`,
26 | }
27 | )
28 | console.log(
29 | "UPDATE_DISPLAY_PICTURE_API API RESPONSE............",
30 | response
31 | )
32 |
33 | if (!response.data.success) {
34 | throw new Error(response.data.message)
35 | }
36 | toast.success("Display Picture Updated Successfully")
37 | dispatch(setUser(response.data.data))
38 | } catch (error) {
39 | console.log("UPDATE_DISPLAY_PICTURE_API API ERROR............", error)
40 | toast.error("Could Not Update Display Picture")
41 | }
42 | toast.dismiss(toastId)
43 | }
44 | }
45 |
46 | export function updateProfile(token, formData) {
47 | return async (dispatch) => {
48 | const toastId = toast.loading("Loading...")
49 | try {
50 | const response = await apiConnector("PUT", UPDATE_PROFILE_API, formData, {
51 | Authorization: `Bearer ${token}`,
52 | })
53 | console.log("UPDATE_PROFILE_API API RESPONSE............", response)
54 |
55 | if (!response.data.success) {
56 | throw new Error(response.data.message)
57 | }
58 | const userImage = response.data.updatedUserDetails.image
59 | ? response.data.updatedUserDetails.image
60 | : `https://api.dicebear.com/5.x/initials/svg?seed=${response.data.updatedUserDetails.firstName} ${response.data.updatedUserDetails.lastName}`
61 | dispatch(
62 | setUser({ ...response.data.updatedUserDetails, image: userImage })
63 | )
64 | toast.success("Profile Updated Successfully")
65 | } catch (error) {
66 | console.log("UPDATE_PROFILE_API API ERROR............", error)
67 | toast.error("Could Not Update Profile")
68 | }
69 | toast.dismiss(toastId)
70 | }
71 | }
72 |
73 | export async function changePassword(token, formData) {
74 | const toastId = toast.loading("Loading...")
75 | try {
76 | const response = await apiConnector("POST", CHANGE_PASSWORD_API, formData, {
77 | Authorization: `Bearer ${token}`,
78 | })
79 | console.log("CHANGE_PASSWORD_API API RESPONSE............", response)
80 |
81 | if (!response.data.success) {
82 | throw new Error(response.data.message)
83 | }
84 | toast.success("Password Changed Successfully")
85 | } catch (error) {
86 | console.log("CHANGE_PASSWORD_API API ERROR............", error)
87 | toast.error(error.response.data.message)
88 | }
89 | toast.dismiss(toastId)
90 | }
91 |
92 | export function deleteProfile(token, navigate) {
93 | return async (dispatch) => {
94 | const toastId = toast.loading("Loading...")
95 | try {
96 | const response = await apiConnector("DELETE", DELETE_PROFILE_API, null, {
97 | Authorization: `Bearer ${token}`,
98 | })
99 | console.log("DELETE_PROFILE_API API RESPONSE............", response)
100 |
101 | if (!response.data.success) {
102 | throw new Error(response.data.message)
103 | }
104 | toast.success("Profile Deleted Successfully")
105 | dispatch(logout(navigate))
106 | } catch (error) {
107 | console.log("DELETE_PROFILE_API API ERROR............", error)
108 | toast.error("Could Not Delete Profile")
109 | }
110 | toast.dismiss(toastId)
111 | }
112 | }
--------------------------------------------------------------------------------
/src/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/services/operations/studentFeaturesAPI.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-hot-toast";
2 | import { studentEndpoints } from "../apis";
3 | import { apiConnector } from "../apiconnector";
4 | import rzpLogo from "../../assets/Logo/rzp_logo.png"
5 | import { setPaymentLoading } from "../../slices/courseSlice";
6 | import { resetCart } from "../../slices/cartSlice";
7 |
8 |
9 | const {COURSE_PAYMENT_API, COURSE_VERIFY_API, SEND_PAYMENT_SUCCESS_EMAIL_API} = studentEndpoints;
10 |
11 | function loadScript(src) {
12 | return new Promise((resolve) => {
13 | const script = document.createElement("script");
14 | script.src = src;
15 |
16 | script.onload = () => {
17 | resolve(true);
18 | }
19 | script.onerror= () =>{
20 | resolve(false);
21 | }
22 | document.body.appendChild(script);
23 | })
24 | }
25 |
26 |
27 | export async function buyCourse(token, courses, userDetails, navigate, dispatch) {
28 | const toastId = toast.loading("Loading...");
29 | try{
30 | //load the script
31 | const res = await loadScript("https://checkout.razorpay.com/v1/checkout.js");
32 |
33 | if(!res) {
34 | toast.error("RazorPay SDK failed to load");
35 | return;
36 | }
37 |
38 | //initiate the order
39 | const orderResponse = await apiConnector("POST", COURSE_PAYMENT_API,
40 | {courses},
41 | {
42 | Authorization: `Bearer ${token}`,
43 | })
44 |
45 | if(!orderResponse.data.success) {
46 | throw new Error(orderResponse.data.message);
47 | }
48 | console.log("PRINTING orderResponse", orderResponse);
49 | //options
50 | const options = {
51 | key: process.env.RAZORPAY_KEY,
52 | currency: orderResponse.data.message.currency,
53 | amount: `${orderResponse.data.message.amount}`,
54 | order_id:orderResponse.data.message.id,
55 | name:"StudyNotion",
56 | description: "Thank You for Purchasing the Course",
57 | image:rzpLogo,
58 | prefill: {
59 | name:`${userDetails.firstName}`,
60 | email:userDetails.email
61 | },
62 | handler: function(response) {
63 | //send successful wala mail
64 | sendPaymentSuccessEmail(response, orderResponse.data.message.amount,token );
65 | //verifyPayment
66 | verifyPayment({...response, courses}, token, navigate, dispatch);
67 | }
68 | }
69 | //miss hogya tha
70 | const paymentObject = new window.Razorpay(options);
71 | paymentObject.open();
72 | paymentObject.on("payment.failed", function(response) {
73 | toast.error("oops, payment failed");
74 | console.log(response.error);
75 | })
76 |
77 | }
78 | catch(error) {
79 | console.log("PAYMENT API ERROR.....", error);
80 | toast.error("Could not make Payment");
81 | }
82 | toast.dismiss(toastId);
83 | }
84 |
85 | async function sendPaymentSuccessEmail(response, amount, token) {
86 | try{
87 | await apiConnector("POST", SEND_PAYMENT_SUCCESS_EMAIL_API, {
88 | orderId: response.razorpay_order_id,
89 | paymentId: response.razorpay_payment_id,
90 | amount,
91 | },{
92 | Authorization: `Bearer ${token}`
93 | })
94 | }
95 | catch(error) {
96 | console.log("PAYMENT SUCCESS EMAIL ERROR....", error);
97 | }
98 | }
99 |
100 | //verify payment
101 | async function verifyPayment(bodyData, token, navigate, dispatch) {
102 | const toastId = toast.loading("Verifying Payment....");
103 | dispatch(setPaymentLoading(true));
104 | try{
105 | const response = await apiConnector("POST", COURSE_VERIFY_API, bodyData, {
106 | Authorization:`Bearer ${token}`,
107 | })
108 |
109 | if(!response.data.success) {
110 | throw new Error(response.data.message);
111 | }
112 | toast.success("payment Successful, ypou are addded to the course");
113 | navigate("/dashboard/enrolled-courses");
114 | dispatch(resetCart());
115 | }
116 | catch(error) {
117 | console.log("PAYMENT VERIFY ERROR....", error);
118 | toast.error("Could not verify Payment");
119 | }
120 | toast.dismiss(toastId);
121 | dispatch(setPaymentLoading(false));
122 | }
--------------------------------------------------------------------------------
/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 | },
133 | },
134 | plugins: [],
135 | };
--------------------------------------------------------------------------------