├── .gitattributes
├── .gitignore
├── README.md
├── e-learning
├── .gitattributes
├── .gitignore
├── .prettierrc
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.js
│ ├── ColorModeSwitcher.js
│ ├── assets
│ ├── docs
│ │ └── termsAndCondition.js
│ ├── images
│ │ ├── CBR.jpg
│ │ ├── Cloudinary.png
│ │ ├── Render.png
│ │ ├── bg.png
│ │ ├── cursor.png
│ │ ├── icon.png
│ │ ├── logo.png
│ │ ├── mailtrap.png
│ │ └── razorpay.png
│ └── videos
│ │ └── intro.mp4
│ ├── components
│ ├── About
│ │ └── About.jsx
│ ├── Admin
│ │ ├── AdminCourses
│ │ │ ├── AdminCourses.jsx
│ │ │ └── CourseModal.jsx
│ │ ├── CreateCourse
│ │ │ └── CreateCourse.jsx
│ │ ├── Dashboard
│ │ │ ├── Chart.jsx
│ │ │ └── Dashboard.jsx
│ │ ├── Sidebar.jsx
│ │ └── Users
│ │ │ └── Users.jsx
│ ├── Auth
│ │ ├── ForgetPassword.jsx
│ │ ├── Login.jsx
│ │ ├── Register.jsx
│ │ └── ResetPassword.jsx
│ ├── Contact
│ │ └── Contact.jsx
│ ├── CoursePage
│ │ └── CoursePage.jsx
│ ├── Courses
│ │ ├── Courses.jsx
│ │ └── RecommendedCourses.jsx
│ ├── Home
│ │ ├── Home.jsx
│ │ └── home.css
│ ├── Layout
│ │ ├── Footer
│ │ │ └── Footer.jsx
│ │ ├── Header
│ │ │ └── Header.jsx
│ │ ├── Loader
│ │ │ └── Loader.jsx
│ │ └── NotFound
│ │ │ └── NotFound.jsx
│ ├── Payments
│ │ ├── PaymentFail.jsx
│ │ ├── PaymentSuccess.jsx
│ │ └── Subscribe.jsx
│ ├── Profile
│ │ ├── ChangePassword.jsx
│ │ ├── Profile.jsx
│ │ └── UpdateProfile.jsx
│ └── Request
│ │ └── Request.jsx
│ ├── index.js
│ └── redux
│ ├── actions
│ ├── admin.js
│ ├── course.js
│ ├── other.js
│ ├── profile.js
│ ├── recommendedCourse.js
│ └── user.js
│ ├── reducers
│ ├── adminReducer.js
│ ├── courseReducer.js
│ ├── otherReducer.js
│ ├── recommendedCourseReducer.js
│ └── userReducer.js
│ └── store.js
└── e-learningserver
├── app.js
├── config
└── database.js
├── controllers
├── courseController.js
├── otherController.js
├── paymentController.js
├── recommendationController.js
└── userController.js
├── middlewares
├── Error.js
├── auth.js
├── catchAsyncError.js
└── multer.js
├── models
├── Course.js
├── Payment.js
├── Stats.js
└── User.js
├── package-lock.json
├── package.json
├── routes
├── courseRoutes.js
├── otherRoutes.js
├── paymentRoutes.js
└── userRoutes.js
├── server.js
└── utils
├── dataUri.js
├── errorHandler.js
├── sendEmail.js
└── sendToken.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /e-learning/node_modules
5 | /e-learningserver/node_modules
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | /e-learningserver/config/config.env
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 📢 E-Learning
3 |
4 |
5 |
6 |
7 |
8 | This is a **MERN** stack Subscription based Online Learning Platform with content based recommendation system.
9 |
10 | [Project Demo Video Link](https://drive.google.com/file/d/18vreMcZctCm2B5kgurNnIZDMwJrNGuz8/view?usp=sharing)
11 |
12 |
13 |
14 |
15 | # 🛠️ Tech stack
16 |
17 | ## Frontend :
18 |
19 |   
20 |
21 |
22 | ## Backend :
23 |
24 |    
25 |
26 |
27 |
28 |
29 | ## Media Management :
30 |
31 |
32 |
33 | ## Payment Management :
34 |
35 |
36 |
37 | ## Deployed On :
38 |
39 |
40 |
41 |
42 |
43 |
44 | # 🚀 Features
45 |
46 | ## Any User
47 |
48 | - can search courses with title and filter it with categories.
49 | - can see the top recommended course's title.
50 | - can request course and contact us for feedbacks.
51 | - Register themselves with name, email, password and avatar.
52 |
53 | ## Registered User
54 |
55 | - Can reset their password via mail by selecting forget password and entering their email.
56 | - Can Log in to website.
57 |
58 | ## Logged-In User
59 |
60 | - Can change their profile info or password.
61 | - Can add or remove courses from their playlist.
62 | - can see the top recommended course's title according to their playlist.
63 | - Can subscribe (by making payment of Rs 299) to the website for accessing any courses.
64 | - Can logout.
65 |
66 | ## Subscribed User
67 |
68 | - Can access any courses and watch their lectures.
69 | - Can cancel the subscription before 7 days.
70 |
71 | ## Admin User
72 |
73 | - Can has access to Admin dashboard and analyze the stats of everything in real time.
74 | - Can create courses, watch any course's lectures, add or delete lectures in Courses, and delete any courses.
75 | - Can change the user role and delete any user.
76 |
77 |
--------------------------------------------------------------------------------
/e-learning/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/e-learning/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/e-learning/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "semi": true
6 | }
7 |
--------------------------------------------------------------------------------
/e-learning/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-learning",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@chakra-ui/react": "^2.4.8",
7 | "@emotion/react": "^11.10.5",
8 | "@emotion/styled": "^11.10.5",
9 | "@reduxjs/toolkit": "^1.9.1",
10 | "@testing-library/jest-dom": "^5.16.5",
11 | "@testing-library/react": "^13.4.0",
12 | "@testing-library/user-event": "^14.4.3",
13 | "axios": "^1.2.2",
14 | "chart.js": "^4.1.2",
15 | "framer-motion": "^6.5.1",
16 | "protected-route-react": "^1.0.1",
17 | "react": "^18.2.0",
18 | "react-chartjs-2": "^5.2.0",
19 | "react-dom": "^18.2.0",
20 | "react-hot-toast": "^2.4.0",
21 | "react-icons": "^3.11.0",
22 | "react-redux": "^8.0.5",
23 | "react-router-dom": "^6.6.2",
24 | "react-scripts": "5.0.1",
25 | "web-vitals": "^2.1.4"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject"
32 | },
33 | "eslintConfig": {
34 | "extends": "react-app"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/e-learning/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/public/favicon.ico
--------------------------------------------------------------------------------
/e-learning/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
25 | E-Learning
26 |
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/e-learning/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/public/logo192.png
--------------------------------------------------------------------------------
/e-learning/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/public/logo512.png
--------------------------------------------------------------------------------
/e-learning/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/e-learning/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/e-learning/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
3 | import ForgetPassword from './components/Auth/ForgetPassword';
4 | import Login from './components/Auth/Login';
5 | import Register from './components/Auth/Register';
6 | import ResetPassword from './components/Auth/ResetPassword';
7 | import Contact from './components/Contact/Contact';
8 | import Courses from './components/Courses/Courses';
9 | import Home from './components/Home/Home';
10 | import Footer from './components/Layout/Footer/Footer';
11 | import Header from './components/Layout/Header/Header';
12 | import Request from './components/Request/Request';
13 | import About from './components/About/About';
14 | import Subscribe from './components/Payments/Subscribe';
15 | import PaymentFail from './components/Payments/PaymentFail';
16 | import PaymentSuccess from './components/Payments/PaymentSuccess';
17 | import NotFound from './components/Layout/NotFound/NotFound';
18 | import CoursePage from './components/CoursePage/CoursePage';
19 | import Profile from './components/Profile/Profile';
20 | import ChangePassword from './components/Profile/ChangePassword';
21 | import UpdateProfile from './components/Profile/UpdateProfile';
22 | import Dashboard from './components/Admin/Dashboard/Dashboard';
23 | import CreateCourse from './components/Admin/CreateCourse/CreateCourse';
24 | import AdminCourses from './components/Admin/AdminCourses/AdminCourses';
25 | import Users from './components/Admin/Users/Users';
26 | import { useDispatch, useSelector } from 'react-redux';
27 | import toast, { Toaster } from 'react-hot-toast';
28 | import { loadUser } from './redux/actions/user';
29 | import { ProtectedRoute } from 'protected-route-react';
30 | import Loader from './components/Layout/Loader/Loader';
31 | import RecommendedCourses from './components/Courses/RecommendedCourses';
32 |
33 | function App() {
34 | // window.addEventListener('contextmenu', e => {
35 | // e.preventDefault();
36 | // });
37 |
38 | const { isAuthenticated, user, message, error, loading } = useSelector(
39 | state => state.user
40 | );
41 |
42 | const dispatch = useDispatch();
43 | useEffect(() => {
44 | if (error) {
45 | toast.error(error);
46 | dispatch({ type: 'clearError' });
47 | }
48 |
49 | if (message) {
50 | toast.success(message);
51 | dispatch({ type: 'clearMessage' });
52 | }
53 | }, [dispatch, error, message]);
54 |
55 | useEffect(() => {
56 | dispatch(loadUser());
57 | }, [dispatch]);
58 |
59 | return (
60 |
61 | {loading ? (
62 |
63 | ) : (
64 | <>
65 |
66 |
67 | } />
68 | } />
69 | } />
70 |
71 |
75 |
76 |
77 | }
78 | />
79 |
80 | } />
81 | } />
82 | } />
83 |
84 |
88 |
89 |
90 | }
91 | />
92 |
96 |
97 |
98 | }
99 | />
100 |
104 |
105 |
106 | }
107 | />
108 |
109 |
116 |
117 |
118 | }
119 | />
120 |
127 |
128 |
129 | }
130 | />
131 |
138 |
139 |
140 | }
141 | />
142 |
143 |
150 |
151 |
152 | }
153 | />
154 |
155 |
159 |
160 |
161 | }
162 | />
163 |
164 | } />
165 |
166 | } />
167 |
168 | } />
169 |
170 | {/* Admin Routes */}
171 |
179 |
180 |
181 | }
182 | />
183 |
191 |
192 |
193 | }
194 | />
195 |
203 |
204 |
205 | }
206 | />
207 |
215 |
216 |
217 | }
218 | />
219 |
220 |
221 |
222 |
223 | >
224 | )}
225 |
226 | );
227 | }
228 |
229 | export default App;
230 |
231 | // Admin admincourses
232 |
--------------------------------------------------------------------------------
/e-learning/src/ColorModeSwitcher.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useColorMode, useColorModeValue, IconButton } from '@chakra-ui/react';
3 | import { FaMoon, FaSun } from 'react-icons/fa';
4 |
5 | export const ColorModeSwitcher = props => {
6 | const { toggleColorMode } = useColorMode();
7 | const text = useColorModeValue('dark', 'light');
8 | const SwitchIcon = useColorModeValue(FaMoon, FaSun);
9 |
10 | return (
11 | }
23 | {...props}
24 | />
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/e-learning/src/assets/images/CBR.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/CBR.jpg
--------------------------------------------------------------------------------
/e-learning/src/assets/images/Cloudinary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/Cloudinary.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/Render.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/Render.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/bg.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/cursor.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/icon.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/logo.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/mailtrap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/mailtrap.png
--------------------------------------------------------------------------------
/e-learning/src/assets/images/razorpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/images/razorpay.png
--------------------------------------------------------------------------------
/e-learning/src/assets/videos/intro.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mananghetia/E-Learning/2a440e9e56b1b030c7e0e05af528ba0704f16ef4/e-learning/src/assets/videos/intro.mp4
--------------------------------------------------------------------------------
/e-learning/src/components/About/About.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Avatar,
3 | Box,
4 | Button,
5 | Container,
6 | Heading,
7 | HStack,
8 | Stack,
9 | Text,
10 | VStack,
11 | } from '@chakra-ui/react'
12 | import React from 'react'
13 | import { RiSecurePaymentFill } from 'react-icons/ri'
14 | import { Link } from 'react-router-dom'
15 | import introVideo from '../../assets/videos/intro.mp4'
16 | import termsAndCondition from '../../assets/docs/termsAndCondition'
17 |
18 | const Founder = () => (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 | )
34 |
35 | const VideoPlayer = () => (
36 |
37 |
47 |
48 | )
49 |
50 | const TandC = ({ termsAndCondition }) => (
51 |
52 |
58 |
59 |
60 |
65 | {termsAndCondition}
66 |
67 |
72 |
73 |
74 | )
75 | const About = () => {
76 | return (
77 |
78 |
79 |
80 |
81 |
82 | We are a video streaming platform with some premium courses available
83 | only for premium users.
84 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
105 |
106 |
107 | )
108 | }
109 |
110 | export default About
111 |
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/AdminCourses/AdminCourses.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Grid,
5 | Heading,
6 | HStack,
7 | Image,
8 | Table,
9 | TableCaption,
10 | TableContainer,
11 | Tbody,
12 | Td,
13 | Th,
14 | Thead,
15 | Tr,
16 | useDisclosure,
17 | } from '@chakra-ui/react';
18 | import React, { useState } from 'react';
19 | import { useEffect } from 'react';
20 | import { RiDeleteBin7Fill } from 'react-icons/ri';
21 | import { useDispatch, useSelector } from 'react-redux';
22 | import cursor from '../../../assets/images/cursor.png';
23 | import Sidebar from '../Sidebar';
24 | import CourseModal from './CourseModal';
25 | import {
26 | getAllCourses,
27 | getCourseLectures,
28 | } from '../../../redux/actions/course';
29 | import {
30 | addLecture,
31 | deleteCourse,
32 | deleteLecture,
33 | } from '../../../redux/actions/admin';
34 | import toast from 'react-hot-toast';
35 |
36 | const AdminCourses = () => {
37 | const { courses, lectures } = useSelector(state => state.course);
38 |
39 | const { loading, error, message } = useSelector(state => state.admin);
40 |
41 | const dispatch = useDispatch();
42 |
43 | const { isOpen, onClose, onOpen } = useDisclosure();
44 |
45 | const [courseId, setCourseId] = useState('');
46 | const [courseTitle, setCourseTitle] = useState('');
47 |
48 | const coureDetailsHandler = (courseId, title) => {
49 | dispatch(getCourseLectures(courseId));
50 | onOpen();
51 | setCourseId(courseId);
52 | setCourseTitle(title);
53 | };
54 | const deleteButtonHandler = courseId => {
55 | console.log(courseId);
56 | dispatch(deleteCourse(courseId));
57 | };
58 |
59 | const deleteLectureButtonHandler = async (courseId, lectureId) => {
60 | await dispatch(deleteLecture(courseId, lectureId));
61 | dispatch(getCourseLectures(courseId));
62 | };
63 |
64 | const addLectureHandler = async (e, courseId, title, description, video) => {
65 | e.preventDefault();
66 | const myForm = new FormData();
67 |
68 | myForm.append('title', title);
69 | myForm.append('description', description);
70 | myForm.append('file', video);
71 |
72 | await dispatch(addLecture(courseId, myForm));
73 | dispatch(getCourseLectures(courseId));
74 | };
75 |
76 | useEffect(() => {
77 | if (error) {
78 | toast.error(error);
79 | dispatch({ type: 'clearError' });
80 | }
81 |
82 | if (message) {
83 | toast.success(message);
84 | dispatch({ type: 'clearMessage' });
85 | }
86 |
87 | dispatch(getAllCourses());
88 | }, [dispatch, error, message, onClose]);
89 |
90 | return (
91 |
98 |
99 |
105 |
106 |
107 |
108 | All available courses in the database
109 |
110 |
111 |
112 | Id |
113 | Poster |
114 | Title |
115 | Category |
116 | Creator |
117 | Views |
118 | Lectures |
119 | Action |
120 |
121 |
122 |
123 |
124 | {courses.map(item => (
125 |
132 | ))}
133 |
134 |
135 |
136 |
137 |
147 |
148 |
149 |
150 |
151 | );
152 | };
153 |
154 | function Row({ item, coureDetailsHandler, deleteButtonHandler, loading }) {
155 | return (
156 |
157 | #{item._id} |
158 |
159 |
160 |
161 | |
162 |
163 | {item.title} |
164 | {item.category} |
165 | {item.createdBy} |
166 | {item.views} |
167 | {item.numOfVideos} |
168 |
169 |
170 |
171 |
179 |
180 |
187 |
188 | |
189 |
190 | );
191 | }
192 | export default AdminCourses;
193 |
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/AdminCourses/CourseModal.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Grid,
5 | Heading,
6 | Input,
7 | Modal,
8 | ModalBody,
9 | ModalCloseButton,
10 | ModalContent,
11 | ModalFooter,
12 | ModalHeader,
13 | ModalOverlay,
14 | Stack,
15 | Text,
16 | VStack,
17 | } from '@chakra-ui/react';
18 | import React from 'react';
19 | import { useState } from 'react';
20 | import { RiDeleteBin7Fill } from 'react-icons/ri';
21 | import { fileUploadCss } from '../../Auth/Register';
22 |
23 | const CourseModal = ({
24 | isOpen,
25 | onClose,
26 | id,
27 | deleteButtonHandler,
28 | addLectureHandler,
29 | courseTitle,
30 | lectures = [],
31 | loading,
32 | }) => {
33 | const [title, setTitle] = useState('');
34 | const [description, setDescription] = useState('');
35 | const [video, setVideo] = useState('');
36 | const [videoPrev, setVideoPrev] = useState('');
37 |
38 | const changeVideoHandler = e => {
39 | const file = e.target.files[0];
40 | const reader = new FileReader();
41 |
42 | reader.readAsDataURL(file);
43 |
44 | reader.onloadend = () => {
45 | setVideoPrev(reader.result);
46 | setVideo(file);
47 | };
48 | };
49 |
50 | const handleClose = () => {
51 | setTitle('');
52 | setDescription('');
53 | setVideo('');
54 | setVideoPrev('');
55 | onClose();
56 | };
57 | return (
58 |
64 |
65 |
66 |
67 | {courseTitle}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {lectures.map((item, i) => (
81 |
91 | ))}
92 |
93 |
94 |
95 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | );
162 | };
163 |
164 | export default CourseModal;
165 |
166 | function VideoCard({
167 | title,
168 | description,
169 | num,
170 | lectureId,
171 | courseId,
172 | deleteButtonHandler,
173 | loading,
174 | }) {
175 | return (
176 |
184 |
185 |
186 |
187 |
188 |
189 |
196 |
197 | );
198 | }
199 |
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/CreateCourse/CreateCourse.jsx:
--------------------------------------------------------------------------------
1 | import { categories } from '../../Courses/Courses'
2 | import {
3 | Button,
4 | Container,
5 | Grid,
6 | Heading,
7 | Image,
8 | Input,
9 | Select,
10 | VStack,
11 | } from '@chakra-ui/react';
12 | import React from 'react';
13 | import { useEffect } from 'react';
14 | import { useState } from 'react';
15 | import { useDispatch, useSelector } from 'react-redux';
16 | import cursor from '../../../assets/images/cursor.png';
17 | import { createCourse } from '../../../redux/actions/admin';
18 | import { fileUploadCss } from '../../Auth/Register';
19 | import Sidebar from '../Sidebar';
20 | import toast from 'react-hot-toast';
21 |
22 | const CreateCourse = () => {
23 | const [title, setTitle] = useState('');
24 | const [description, setDescription] = useState('');
25 | const [createdBy, setCreatedBy] = useState('');
26 | const [category, setCategory] = useState('');
27 | const [image, setImage] = useState('');
28 | const [imagePrev, setImagePrev] = useState('');
29 |
30 | const dispatch = useDispatch();
31 | const { loading, error, message } = useSelector(state => state.admin);
32 |
33 | const changeImageHandler = e => {
34 | const file = e.target.files[0];
35 | const reader = new FileReader();
36 |
37 | reader.readAsDataURL(file);
38 |
39 | reader.onloadend = () => {
40 | setImagePrev(reader.result);
41 | setImage(file);
42 | };
43 | };
44 |
45 | const submitHandler = e => {
46 | e.preventDefault();
47 | const myForm = new FormData();
48 | myForm.append('title', title);
49 | myForm.append('description', description);
50 | myForm.append('category', category);
51 | myForm.append('createdBy', createdBy);
52 | myForm.append('file', image);
53 | dispatch(createCourse(myForm));
54 | };
55 |
56 | useEffect(() => {
57 | if (error) {
58 | toast.error(error);
59 | dispatch({ type: 'clearError' });
60 | }
61 |
62 | if (message) {
63 | toast.success(message);
64 | dispatch({ type: 'clearMessage' });
65 | }
66 | }, [dispatch, error, message]);
67 |
68 | return (
69 |
76 |
77 |
146 |
147 |
148 |
149 |
150 | );
151 | };
152 |
153 | export default CreateCourse;
154 |
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/Dashboard/Chart.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Chart as ChartJS,
4 | CategoryScale,
5 | LinearScale,
6 | PointElement,
7 | LineElement,
8 | Title,
9 | Tooltip,
10 | ArcElement,
11 | Legend,
12 | } from 'chart.js';
13 | import { Line, Doughnut } from 'react-chartjs-2';
14 |
15 | ChartJS.register(
16 | CategoryScale,
17 | LinearScale,
18 | PointElement,
19 | LineElement,
20 | Title,
21 | Tooltip,
22 | ArcElement,
23 | Legend
24 | );
25 |
26 | export const LineChart = ({ views = [] }) => {
27 | const labels = getLastYearMonths();
28 |
29 | const options = {
30 | responsive: true,
31 | plugins: {
32 | legend: {
33 | position: 'bottom',
34 | },
35 | title: {
36 | display: true,
37 | text: 'Yearly Views',
38 | },
39 | },
40 | };
41 |
42 | const data = {
43 | labels,
44 | datasets: [
45 | {
46 | label: 'Views',
47 | data: views,
48 | borderColor: 'rgba(107,70,193,0.5)',
49 | backgroundColor: '#6b46c1',
50 | },
51 | ],
52 | };
53 |
54 | return ;
55 | };
56 |
57 | export const DoughnutChart = ({ users = [] }) => {
58 | const data = {
59 | labels: ['Subscribed', 'Not Subscribed'],
60 | datasets: [
61 | {
62 | label: 'Views',
63 | data: users,
64 | borderColor: ['rgb(62,12,171)', 'rgb(214,43,129)'],
65 | backgroundColor: ['rgba(62,12,171,0.3)', 'rgba(214,43,129,0.3)'],
66 | borderWidth: 1,
67 | },
68 | ],
69 | };
70 |
71 | return ;
72 | };
73 |
74 | function getLastYearMonths() {
75 | const labels = [];
76 |
77 | const months = [
78 | 'January',
79 | 'February',
80 | 'March',
81 | 'April',
82 | 'May',
83 | 'June',
84 | 'July',
85 | 'August',
86 | 'September',
87 | 'October',
88 | 'November',
89 | 'December',
90 | ];
91 |
92 | const currentMonth = new Date().getMonth();
93 |
94 | for (let i = currentMonth; i >= 0; i--) {
95 | const element = months[i];
96 | labels.unshift(element);
97 | if (i === 0) break;
98 | }
99 |
100 | for (let i = 11; i > currentMonth; i--) {
101 | if (i === currentMonth) break;
102 | const element = months[i];
103 | labels.unshift(element);
104 | }
105 |
106 | return labels;
107 | }
108 |
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/Dashboard/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Grid,
4 | Heading,
5 | HStack,
6 | Progress,
7 | Stack,
8 | Text,
9 | } from '@chakra-ui/react';
10 | import React, { useEffect } from 'react';
11 | import { RiArrowDownLine, RiArrowUpLine } from 'react-icons/ri';
12 | import cursor from '../../../assets/images/cursor.png';
13 | import Sidebar from '../Sidebar';
14 | import { DoughnutChart, LineChart } from './Chart';
15 | import { useDispatch, useSelector } from 'react-redux';
16 | import { getDashboardStats } from '../../../redux/actions/admin';
17 | import Loader from '../../Layout/Loader/Loader';
18 |
19 | const Databox = ({ title, qty, qtyPercentage, profit }) => (
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {profit ? (
34 |
35 | ) : (
36 |
37 | )}
38 |
39 |
40 |
41 |
42 | );
43 |
44 | const Bar = ({ title, value, profit }) => (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 100 ? value : 100}%`} />
53 |
54 |
55 | );
56 |
57 | const Dashboard = () => {
58 | const dispatch = useDispatch();
59 |
60 | const {
61 | loading,
62 | stats,
63 | viewsCount,
64 | subscriptionCount,
65 | usersCount,
66 | subscriptionPercentage,
67 | viewsPercentage,
68 | usersPercentage,
69 | subscriptionProfit,
70 | viewsProfit,
71 | usersProfit,
72 | } = useSelector(state => state.admin);
73 |
74 | useEffect(() => {
75 | dispatch(getDashboardStats());
76 | }, [dispatch]);
77 |
78 | return (
79 |
86 | {loading || !stats ? (
87 |
88 | ) : (
89 |
90 |
96 |
97 |
103 |
104 |
109 |
115 |
121 |
127 |
128 |
129 |
136 |
143 |
144 | item.views)} />
145 |
146 |
147 |
148 |
149 |
156 |
157 |
158 |
163 |
168 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
182 |
183 |
184 |
185 | )}
186 |
187 |
188 |
189 | );
190 | };
191 |
192 | export default Dashboard;
193 |
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import { Button, VStack } from '@chakra-ui/react'
2 | import React from 'react'
3 | import { RiAddCircleFill, RiDashboardFill, RiEyeFill, RiUser3Fill } from 'react-icons/ri'
4 | import { Link, useLocation } from 'react-router-dom'
5 | const Sidebar = () => {
6 | const location = useLocation()
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default Sidebar
18 |
19 | function LinkButton({ url, Icon, text, active }) {
20 | return (
21 |
22 |
26 |
27 | )
28 | }
--------------------------------------------------------------------------------
/e-learning/src/components/Admin/Users/Users.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Grid,
5 | Heading,
6 | HStack,
7 | Table,
8 | TableCaption,
9 | TableContainer,
10 | Tbody,
11 | Td,
12 | Th,
13 | Thead,
14 | Tr,
15 | } from '@chakra-ui/react';
16 | import React, { useEffect } from 'react';
17 | import { RiDeleteBin7Fill } from 'react-icons/ri';
18 | import cursor from '../../../assets/images/cursor.png';
19 | import Sidebar from '../Sidebar';
20 | import { useDispatch, useSelector } from 'react-redux';
21 | import {
22 | deleteUser,
23 | getAllUsers,
24 | updateUserRole,
25 | } from '../../../redux/actions/admin';
26 | import toast from 'react-hot-toast';
27 |
28 | const Users = () => {
29 | const { users, loading, error, message } = useSelector(state => state.admin);
30 |
31 | const dispatch = useDispatch();
32 |
33 | const updateHandler = userId => {
34 | dispatch(updateUserRole(userId));
35 | };
36 | const deleteButtonHandler = userId => {
37 | dispatch(deleteUser(userId));
38 | };
39 |
40 | useEffect(() => {
41 | if (error) {
42 | toast.error(error);
43 | dispatch({ type: 'clearError' });
44 | }
45 |
46 | if (message) {
47 | toast.success(message);
48 | dispatch({ type: 'clearMessage' });
49 | }
50 |
51 | dispatch(getAllUsers());
52 | }, [dispatch, error, message]);
53 |
54 | return (
55 |
62 |
63 |
69 |
70 |
71 |
72 | All available users in the database
73 |
74 |
75 |
76 | Id |
77 | Name |
78 | Email |
79 | Role |
80 | Subscription |
81 | Action |
82 |
83 |
84 |
85 |
86 | {users &&
87 | users.map(item => (
88 |
95 | ))}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default Users;
107 |
108 | function Row({ item, updateHandler, deleteButtonHandler, loading }) {
109 | return (
110 |
111 | #{item._id} |
112 | {item.name} |
113 | {item.email} |
114 | {item.role} |
115 |
116 | {item.subscription && item.subscription.status === 'active'
117 | ? 'Active'
118 | : 'Not Active'}
119 | |
120 |
121 |
122 |
123 |
131 |
132 |
139 |
140 | |
141 |
142 | );
143 | }
144 |
--------------------------------------------------------------------------------
/e-learning/src/components/Auth/ForgetPassword.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Heading, Input, VStack } from '@chakra-ui/react';
2 | import React, { useState } from 'react';
3 | import { useEffect } from 'react';
4 | import toast from 'react-hot-toast';
5 | import { useDispatch, useSelector } from 'react-redux';
6 | import { forgetPassword } from '../../redux/actions/profile';
7 |
8 | const ForgetPassword = () => {
9 | const [email, setEmail] = useState('');
10 |
11 | const { loading, message, error } = useSelector(state => state.profile);
12 |
13 | const dispatch = useDispatch();
14 | const submitHandler = e => {
15 | e.preventDefault();
16 | dispatch(forgetPassword(email));
17 | };
18 |
19 | useEffect(() => {
20 | if (error) {
21 | toast.error(error);
22 | dispatch({ type: 'clearError' });
23 | }
24 | if (message) {
25 | toast.success(message);
26 | dispatch({ type: 'clearMessage' });
27 | }
28 | }, [dispatch, error, message]);
29 |
30 | return (
31 |
32 |
60 |
61 | );
62 | };
63 |
64 | export default ForgetPassword;
65 |
--------------------------------------------------------------------------------
/e-learning/src/components/Auth/Login.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Container, FormLabel, Heading, Input, VStack } from '@chakra-ui/react'
2 | import React from 'react'
3 | import { useState } from 'react'
4 | import { Link } from 'react-router-dom'
5 | import { useDispatch } from 'react-redux';
6 | import { login } from '../../redux/actions/user';
7 |
8 |
9 | const Login = () => {
10 | const [email, setEmail] = useState("")
11 | const [password, setPassword] = useState("")
12 |
13 | const dispatch = useDispatch();
14 |
15 | const submitHandler = e => {
16 | e.preventDefault();
17 | dispatch(login(email, password));
18 | };
19 |
20 | return (
21 |
22 |
23 |
24 |
49 |
50 |
51 | )
52 | }
53 |
54 | export default Login
--------------------------------------------------------------------------------
/e-learning/src/components/Auth/Register.jsx:
--------------------------------------------------------------------------------
1 | import { Avatar, Box, Button, Container, FormLabel, Heading, Input, VStack } from '@chakra-ui/react'
2 | import React from 'react'
3 | import { useState } from 'react'
4 | import { useDispatch } from 'react-redux';
5 | import { Link } from 'react-router-dom'
6 | import { register } from '../../redux/actions/user'
7 |
8 | export const fileUploadCss = {
9 | cursor: 'pointer',
10 | marginLeft: '-5%',
11 | width: '110%',
12 | border: 'none',
13 | height: '100%',
14 | color: '#ECC94B',
15 | backgroundColor: 'white',
16 | };
17 |
18 | const fileUploadStyle = {
19 | '&::file-selector-button': fileUploadCss,
20 | };
21 | function Register() {
22 | const [email, setEmail] = useState("")
23 | const [name, setName] = useState("")
24 | const [password, setPassword] = useState("")
25 | const [imagePrev, setImagePrev] = useState()
26 | const [image, setImage] = useState()
27 |
28 | const dispatch = useDispatch();
29 |
30 | const changeImageHandler = (e) => {
31 | const file = e.target.files[0]
32 | const reader = new FileReader()
33 | reader.readAsDataURL(file)
34 | reader.onloadend = () => {
35 | setImagePrev(reader.result)
36 | setImage(file)
37 | }
38 | }
39 | const submitHandler = e => {
40 | e.preventDefault();
41 | const myForm = new FormData();
42 |
43 | myForm.append('name', name);
44 | myForm.append('email', email);
45 | myForm.append('password', password);
46 | myForm.append('file', image);
47 |
48 | dispatch(register(myForm));
49 | };
50 | return (
51 |
52 |
53 |
54 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default Register
--------------------------------------------------------------------------------
/e-learning/src/components/Auth/ResetPassword.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Heading, Input, VStack } from '@chakra-ui/react';
2 | import React, { useEffect, useState } from 'react';
3 | import toast from 'react-hot-toast';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { useNavigate, useParams } from 'react-router-dom';
6 | import { resetPassword } from '../../redux/actions/profile';
7 |
8 | const ResetPassword = () => {
9 | const [password, setPassword] = useState('');
10 |
11 | const params = useParams();
12 | const navigate = useNavigate();
13 |
14 | const { loading, message, error } = useSelector(state => state.profile);
15 |
16 | const dispatch = useDispatch();
17 | const submitHandler = e => {
18 | e.preventDefault();
19 | dispatch(resetPassword(params.token, password));
20 | };
21 |
22 | useEffect(() => {
23 | if (error) {
24 | toast.error(error);
25 | dispatch({ type: 'clearError' });
26 | }
27 | if (message) {
28 | toast.success(message);
29 | dispatch({ type: 'clearMessage' });
30 | navigate('/login');
31 | }
32 | }, [dispatch, error, message, navigate]);
33 |
34 | return (
35 |
36 |
64 |
65 | );
66 | };
67 |
68 | export default ResetPassword;
69 |
--------------------------------------------------------------------------------
/e-learning/src/components/Contact/Contact.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Container,
5 | FormLabel,
6 | Heading,
7 | Input,
8 | Textarea,
9 | VStack,
10 | } from '@chakra-ui/react';
11 | import React from 'react';
12 | import { useState } from 'react';
13 | import { Link } from 'react-router-dom';
14 | import { useDispatch, useSelector } from 'react-redux';
15 | import { contactUs } from '../../redux/actions/other';
16 | import { useEffect } from 'react';
17 | import toast from 'react-hot-toast';
18 |
19 | const Contact = () => {
20 | const [name, setName] = useState('');
21 | const [email, setEmail] = useState('');
22 | const [message, setMessage] = useState('');
23 |
24 | const dispatch = useDispatch();
25 |
26 | const {
27 | loading,
28 | error,
29 | message: stateMessage,
30 | } = useSelector(state => state.other);
31 |
32 | const submitHandler = e => {
33 | e.preventDefault();
34 | dispatch(contactUs(name, email, message));
35 | };
36 |
37 | useEffect(() => {
38 | if (error) {
39 | toast.error(error);
40 | dispatch({ type: 'clearError' });
41 | }
42 |
43 | if (stateMessage) {
44 | toast.success(stateMessage);
45 | dispatch({ type: 'clearMessage' });
46 | }
47 | }, [dispatch, error, stateMessage]);
48 |
49 | return (
50 |
51 |
52 |
53 |
54 |
112 |
113 |
114 | );
115 | };
116 |
117 | export default Contact;
118 |
--------------------------------------------------------------------------------
/e-learning/src/components/CoursePage/CoursePage.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Grid, Heading, Text, VStack } from '@chakra-ui/react';
2 | import React, { useEffect, useState } from 'react';
3 | import { useDispatch, useSelector } from 'react-redux';
4 | import { Navigate, useParams } from 'react-router-dom';
5 | import { getCourseLectures } from '../../redux/actions/course';
6 | import Loader from '../Layout/Loader/Loader';
7 |
8 | const CoursePage = ({ user }) => {
9 | const [lectureNumber, setLectureNumber] = useState(0);
10 |
11 | const { lectures, loading } = useSelector(state => state.course);
12 |
13 | const dispatch = useDispatch();
14 | const params = useParams();
15 |
16 | useEffect(() => {
17 | dispatch(getCourseLectures(params.id));
18 | }, [dispatch, params.id]);
19 |
20 | if (
21 | user.role !== 'admin' &&
22 | (user.subscription === undefined || user.subscription.status !== 'active')
23 | ) {
24 | return ;
25 | }
26 |
27 | return loading ? (
28 |
29 | ) : (
30 |
31 | {lectures && lectures.length > 0 ? (
32 | <>
33 |
34 |
42 |
43 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {lectures.map((element, index) => (
55 |
70 | ))}
71 |
72 | >
73 | ) : (
74 |
75 | )}
76 |
77 | );
78 | };
79 |
80 | export default CoursePage;
81 |
--------------------------------------------------------------------------------
/e-learning/src/components/Courses/Courses.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Container,
4 | Heading,
5 | HStack,
6 | Image,
7 | Input,
8 | Stack,
9 | Text,
10 | VStack,
11 | } from '@chakra-ui/react';
12 | import React, { useEffect, useState } from 'react';
13 | import { Link, useNavigate } from 'react-router-dom';
14 | import { useDispatch, useSelector } from 'react-redux';
15 | import { getAllCourses } from '../../redux/actions/course';
16 | import toast from 'react-hot-toast';
17 | import { addToPlaylist } from '../../redux/actions/profile';
18 | import { loadUser } from '../../redux/actions/user';
19 | export const categories = [
20 | "Web Development",
21 | "Data Science",
22 | "Data Structure & Algorithm",
23 | "App Development",
24 | "Game Development"
25 | ]
26 | export const Course = ({
27 | views,
28 | title,
29 | imageSrc,
30 | id,
31 | addToPlaylistHandler,
32 | creator,
33 | description,
34 | lectureCount,
35 | loading,
36 | }) => {
37 | return (
38 |
39 |
40 |
48 |
49 |
50 |
51 |
56 |
57 |
62 |
63 |
64 |
70 |
71 |
76 |
77 |
78 |
79 |
80 |
81 |
89 |
90 |
91 | );
92 | };
93 |
94 | const Courses = () => {
95 | const [keyword, setKeyword] = useState('');
96 | const [category, setCategory] = useState('');
97 | const dispatch = useDispatch();
98 | const navigate = useNavigate();
99 |
100 |
101 | const addToPlaylistHandler = async courseId => {
102 | await dispatch(addToPlaylist(courseId));
103 | dispatch(loadUser());
104 | navigate('/profile');
105 | };
106 |
107 | const { loading, courses, error, message } = useSelector(
108 | state => state.course
109 | );
110 |
111 | useEffect(() => {
112 | dispatch(getAllCourses(category, keyword));
113 |
114 | if (error) {
115 | toast.error(error);
116 | dispatch({ type: 'clearError' });
117 | }
118 |
119 | if (message) {
120 | toast.success(message);
121 | dispatch({ type: 'clearMessage' });
122 | }
123 | }, [category, keyword, dispatch, error, message]);
124 |
125 | return (
126 |
127 |
128 |
129 | setKeyword(e.target.value)}
132 | placeholder="Search a course..."
133 | type={'text'}
134 | focusBorderColor="yellow.500"
135 | />
136 |
137 |
146 | {categories.map((item, index) => (
147 |
150 | ))}
151 |
152 |
153 |
159 | {courses.length > 0 ? (
160 | courses.map(item => (
161 |
173 | ))
174 | ) : (
175 |
176 | )}
177 |
178 |
179 | );
180 | };
181 |
182 | export default Courses;
183 |
--------------------------------------------------------------------------------
/e-learning/src/components/Courses/RecommendedCourses.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Container,
3 | Heading,
4 | Stack,
5 | } from '@chakra-ui/react';
6 | import React, { useEffect } from 'react';
7 | import { useDispatch, useSelector } from 'react-redux';
8 | import { useNavigate } from 'react-router-dom';
9 | import { createRecommendationCourses } from '../../redux/actions/recommendedCourse';
10 | import toast from 'react-hot-toast';
11 | import { addToPlaylist } from '../../redux/actions/profile';
12 | import { loadUser } from '../../redux/actions/user';
13 | import { Course } from './Courses';
14 | import Loader from '../Layout/Loader/Loader';
15 |
16 | const RecommendedCourses = () => {
17 | const dispatch = useDispatch();
18 | const navigate = useNavigate();
19 | const { loading, courses, error, message } = useSelector(
20 | state => state.recommendedCourse
21 | );
22 |
23 | const addToPlaylistHandler = async courseId => {
24 | await dispatch(addToPlaylist(courseId));
25 | dispatch(loadUser());
26 | navigate('/profile');
27 | };
28 |
29 |
30 | useEffect(() => {
31 | dispatch(createRecommendationCourses());
32 |
33 | if (error) {
34 | toast.error(error);
35 | dispatch({ type: 'clearError' });
36 | }
37 |
38 | if (message) {
39 | toast.success(message);
40 | dispatch({ type: 'clearMessage' });
41 | }
42 | }, [dispatch, error, message]);
43 | // if (!user) {
44 | // return ;
45 | // }
46 | return loading ? (
47 |
48 | ) : (
49 |
50 |
51 |
57 | {courses.length > 0 ? (
58 | courses.map(item => (
59 |
71 | ))
72 | ) : (
73 |
74 | )}
75 |
76 |
77 | );
78 | }
79 |
80 | export default RecommendedCourses
--------------------------------------------------------------------------------
/e-learning/src/components/Home/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button, Text, Heading, Stack, VStack, Image, Box, HStack } from '@chakra-ui/react'
3 | import "./home.css"
4 | import vg from "../../assets/images/bg.png"
5 | import introVideo from "../../assets/videos/intro.mp4"
6 | import { Link } from 'react-router-dom'
7 | import { CgGoogle, CgYoutube } from 'react-icons/cg'
8 | import { SiCoursera, SiUdemy } from 'react-icons/si'
9 | import { DiAws } from 'react-icons/di'
10 |
11 | const Home = () => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default Home
--------------------------------------------------------------------------------
/e-learning/src/components/Home/home.css:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100vh;
3 | padding: 5rem;
4 | box-sizing: border-box;
5 | }
6 |
7 | .vector-graphics {
8 | filter: drop-shadow(0 40px 10px rgba(0, 0, 0, 0.3));
9 | animation: vectorAnimation 1s infinite ease-out alternate;
10 | }
11 |
12 | @keyframes vectorAnimation {
13 | to {
14 | transform: translateY(-10px);
15 | }
16 | }
17 |
18 | .brandsBanner>svg {
19 | color: white;
20 | font-size: 3rem;
21 | transition: color 0.5s;
22 | }
23 |
24 | .brandsBanner>svg :hover {
25 | color: rgb(255, 221, 0);
26 | }
27 |
28 | .container2 {
29 | height: 100vh;
30 | width: 100%;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | }
35 |
36 | .container2 video {
37 | border: 1px solid rgba(0, 0, 0, 0.18);
38 | width: 60%;
39 | border-radius: 5px;
40 | outline: none;
41 | }
42 |
43 | @media screen and (max-width:600px) {
44 | .container2 video {
45 | width: 100%;
46 | }
47 | }
48 |
49 | .course {
50 | width: 250px;
51 | /* margin-bottom: 250px; */
52 | transition: all 0.5s;
53 | }
54 |
55 | .course:hover {
56 | transform: translateY(-10px);
57 | }
--------------------------------------------------------------------------------
/e-learning/src/components/Layout/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Heading, HStack, Stack, VStack } from '@chakra-ui/react'
2 | import React from 'react'
3 | import { TiSocialYoutubeCircular, TiSocialInstagramCircular } from 'react-icons/ti'
4 | import { DiGithub } from 'react-icons/di'
5 | const Footer = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default Footer
--------------------------------------------------------------------------------
/e-learning/src/components/Layout/Header/Header.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Drawer, DrawerBody, DrawerContent, DrawerHeader, DrawerOverlay, HStack, useDisclosure, VStack } from '@chakra-ui/react'
2 | import React from 'react'
3 | import { ColorModeSwitcher } from '../../../ColorModeSwitcher'
4 | import { RiDashboardFill, RiLogoutBoxLine, RiMenu5Fill } from 'react-icons/ri'
5 | import { Link } from 'react-router-dom'
6 | import { useDispatch } from 'react-redux';
7 | import { logout } from '../../../redux/actions/user';
8 |
9 |
10 | const LinkButton = ({ url = "/", title = "Home", onClose }) => {
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | const Header = ({ isAuthenticated = false, user }) => {
19 | const { onOpen, onClose, isOpen } = useDisclosure()
20 | const dispatch = useDispatch();
21 |
22 | const logoutHandler = () => {
23 | onClose();
24 | dispatch(logout());
25 | };
26 | return (
27 | <>
28 |
29 |
30 |
31 |
32 |
33 |
34 | E-LEARNING
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {isAuthenticated ? (
45 | <>
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 | {user && user.role === "admin" && (
56 |
57 |
61 |
62 | )}
63 |
64 | >) : (
65 | <>
66 |
67 |
68 |
69 | OR
70 |
71 |
72 |
73 | >
74 | )}
75 |
76 |
77 |
78 |
79 |
80 |
81 | >
82 | )
83 | }
84 |
85 | export default Header
86 |
--------------------------------------------------------------------------------
/e-learning/src/components/Layout/Loader/Loader.jsx:
--------------------------------------------------------------------------------
1 | import { Spinner, VStack } from '@chakra-ui/react';
2 | import React from 'react';
3 |
4 | const Loader = ({ color = 'yellow.500' }) => {
5 | return (
6 |
7 |
8 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default Loader;
21 |
--------------------------------------------------------------------------------
/e-learning/src/components/Layout/NotFound/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Heading, VStack } from '@chakra-ui/react';
2 | import React from 'react';
3 | import { RiErrorWarningFill } from 'react-icons/ri';
4 | import { Link } from 'react-router-dom';
5 |
6 | const NotFound = () => {
7 | return (
8 |
9 |
10 |
11 | Page Not Found
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default NotFound;
21 |
--------------------------------------------------------------------------------
/e-learning/src/components/Payments/PaymentFail.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Heading, VStack } from '@chakra-ui/react';
2 | import React from 'react';
3 | import { RiErrorWarningFill } from 'react-icons/ri';
4 | import { Link } from 'react-router-dom';
5 |
6 | const PaymentFail = () => {
7 | return (
8 |
9 |
10 |
11 | Payment Fail
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default PaymentFail;
21 |
--------------------------------------------------------------------------------
/e-learning/src/components/Payments/PaymentSuccess.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Container,
5 | Heading,
6 | Text,
7 | VStack,
8 | } from '@chakra-ui/react';
9 | import React from 'react';
10 | import { RiCheckboxCircleFill } from 'react-icons/ri';
11 | import { Link, useSearchParams } from 'react-router-dom';
12 |
13 | const PaymentSuccess = () => {
14 | const reference = useSearchParams()[0].get('reference');
15 |
16 | return (
17 |
18 |
19 | You have Pro Pack
20 |
21 |
22 |
23 |
29 | Payment Success
30 |
31 |
32 |
33 |
34 |
35 | Congratulation you're a pro member. You have access to premium
36 | content.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Reference: {reference}
50 |
51 |
52 | );
53 | };
54 |
55 | export default PaymentSuccess;
56 |
--------------------------------------------------------------------------------
/e-learning/src/components/Payments/Subscribe.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Container,
5 | Heading,
6 | Text,
7 | VStack,
8 | } from '@chakra-ui/react';
9 | import axios from 'axios';
10 | import React, { useState } from 'react';
11 | import { useEffect } from 'react';
12 | import { useDispatch, useSelector } from 'react-redux';
13 | import { buySubscription } from '../../redux/actions/user';
14 | import { server } from '../../redux/store';
15 | import toast from 'react-hot-toast';
16 | import logo from '../../assets/images/logo.png';
17 |
18 | const Subscribe = ({ user }) => {
19 | const dispatch = useDispatch();
20 | const [key, setKey] = useState('');
21 |
22 | const { loading, error, subscriptionId } = useSelector(
23 | state => state.subscription
24 | );
25 | const { error: courseError } = useSelector(state => state.course);
26 |
27 | const subscribeHandler = async () => {
28 | const {
29 | data: { key },
30 | } = await axios.get(`${server}/razorpaykey`);
31 |
32 | setKey(key);
33 | dispatch(buySubscription());
34 | };
35 |
36 | useEffect(() => {
37 | if (error) {
38 | toast.error(error);
39 | dispatch({ type: 'clearError' });
40 | }
41 | if (courseError) {
42 | toast.error(courseError);
43 | dispatch({ type: 'clearError' });
44 | }
45 | //checkout razorpay integration (https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/build-integration/#12-integrate-with-checkout-on-client-side) and add script in public/index.html
46 | if (subscriptionId) {
47 | const openPopUp = () => {
48 | const options = {
49 | key,
50 | name: 'E-Learning',
51 | description: 'Get access to all premium content',
52 | image: logo,
53 | subscription_id: subscriptionId,
54 | callback_url: `${server}/paymentverification`,
55 | prefill: {
56 | name: user.name,
57 | email: user.email,
58 | contact: '',
59 | },
60 | notes: {
61 | address: 'Manan Ghetia',
62 | },
63 | theme: {
64 | color: '#FFC800',
65 | },
66 | };
67 |
68 | const razor = new window.Razorpay(options);
69 | razor.open();
70 | };
71 | openPopUp();
72 | }
73 | }, [
74 | dispatch,
75 | error,
76 | courseError,
77 | user.name,
78 | user.email,
79 | key,
80 | subscriptionId,
81 | ]);
82 |
83 | return (
84 |
85 |
86 |
87 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
111 |
112 |
113 |
114 |
120 |
121 |
126 |
127 |
128 |
129 | );
130 | };
131 |
132 | export default Subscribe;
133 |
--------------------------------------------------------------------------------
/e-learning/src/components/Profile/ChangePassword.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Heading, Input, VStack } from '@chakra-ui/react';
2 | import React, { useState } from 'react';
3 | import { useEffect } from 'react';
4 | import toast from 'react-hot-toast';
5 | import { useDispatch, useSelector } from 'react-redux';
6 | import { changePassword } from '../../redux/actions/profile';
7 | import { useNavigate } from 'react-router-dom';
8 |
9 | const ChangePassword = () => {
10 | const [oldPassword, setOldPassword] = useState('');
11 | const [newPassword, setNewPassword] = useState('');
12 |
13 | const dispatch = useDispatch();
14 | const navigate = useNavigate();
15 |
16 | const submitHandler = async e => {
17 | e.preventDefault();
18 | await dispatch(changePassword(oldPassword, newPassword));
19 | navigate('/profile');
20 | };
21 |
22 | const { loading, message, error } = useSelector(state => state.profile);
23 |
24 | useEffect(() => {
25 | if (error) {
26 | toast.error(error);
27 | dispatch({ type: 'clearError' });
28 | }
29 | if (message) {
30 | toast.success(message);
31 | dispatch({ type: 'clearMessage' });
32 | }
33 | }, [dispatch, error, message]);
34 |
35 | return (
36 |
37 |
74 |
75 | );
76 | };
77 |
78 | export default ChangePassword;
79 |
--------------------------------------------------------------------------------
/e-learning/src/components/Profile/Profile.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Avatar,
3 | Button,
4 | Container,
5 | Heading,
6 | HStack,
7 | Image,
8 | Input,
9 | Modal,
10 | ModalBody,
11 | ModalCloseButton,
12 | ModalContent,
13 | ModalFooter,
14 | ModalHeader,
15 | ModalOverlay,
16 | Stack,
17 | Text,
18 | useDisclosure,
19 | VStack,
20 | } from '@chakra-ui/react';
21 | import React, { useEffect } from 'react';
22 | import { useState } from 'react';
23 | import toast from 'react-hot-toast';
24 | import { RiDeleteBin7Fill } from 'react-icons/ri';
25 | import { useDispatch, useSelector } from 'react-redux';
26 | import { Link } from 'react-router-dom';
27 | import {
28 | removeFromPlaylist,
29 | updateProfilePicture,
30 | } from '../../redux/actions/profile';
31 | import { cancelSubscription, loadUser } from '../../redux/actions/user';
32 | import { fileUploadCss } from '../Auth/Register';
33 |
34 | const Profile = ({ user }) => {
35 | const dispatch = useDispatch();
36 | const { loading, message, error } = useSelector(state => state.profile);
37 | const {
38 | loading: subscriptionLoading,
39 | message: subscriptionMessage,
40 | error: subscriptionError,
41 | } = useSelector(state => state.subscription);
42 |
43 | const removeFromPlaylistHandler = async id => {
44 | await dispatch(removeFromPlaylist(id));
45 | dispatch(loadUser());
46 | };
47 |
48 | const changeImageSubmitHandler = async (e, image) => {
49 | e.preventDefault();
50 | const myForm = new FormData();
51 | myForm.append('file', image);
52 | await dispatch(updateProfilePicture(myForm));
53 | dispatch(loadUser());
54 | };
55 |
56 | const cancelSubscriptionHandler = () => {
57 | dispatch(cancelSubscription());
58 | };
59 |
60 | useEffect(() => {
61 | if (error) {
62 | toast.error(error);
63 | dispatch({ type: 'clearError' });
64 | }
65 | if (message) {
66 | toast.success(message);
67 | dispatch({ type: 'clearMessage' });
68 | }
69 | if (subscriptionMessage) {
70 | toast.success(subscriptionMessage);
71 | dispatch({ type: 'clearMessage' });
72 | dispatch(loadUser());
73 | }
74 |
75 | if (subscriptionError) {
76 | toast.error(subscriptionError);
77 | dispatch({ type: 'clearError' });
78 | }
79 | }, [dispatch, error, message, subscriptionError, subscriptionMessage]);
80 |
81 | const { isOpen, onClose, onOpen } = useDisclosure();
82 |
83 | return (
84 |
85 |
86 |
87 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | {' '}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | {user.role !== 'admin' && (
115 |
116 |
117 | {user.subscription && user.subscription.status === 'active' ? (
118 |
126 | ) : (
127 |
128 |
129 |
130 | )}
131 |
132 | )}
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {user.playlist.length > 0 && (
148 |
154 | {user.playlist.map(element => (
155 |
156 |
161 |
162 |
163 |
164 |
167 |
168 |
169 |
175 |
176 |
177 | ))}
178 |
179 | )}
180 |
181 |
187 |
188 | );
189 | };
190 |
191 | export default Profile;
192 |
193 | function ChangePhotoBox({
194 | isOpen,
195 | onClose,
196 | changeImageSubmitHandler,
197 | loading,
198 | }) {
199 | const [image, setImage] = useState('');
200 | const [imagePrev, setImagePrev] = useState('');
201 |
202 | const changeImage = e => {
203 | const file = e.target.files[0];
204 | const reader = new FileReader();
205 |
206 | reader.readAsDataURL(file);
207 |
208 | reader.onloadend = () => {
209 | setImagePrev(reader.result);
210 | setImage(file);
211 | };
212 | };
213 |
214 | const closeHandler = () => {
215 | onClose();
216 | setImagePrev('');
217 | setImage('');
218 | };
219 | return (
220 |
221 |
222 |
223 | Change Photo
224 |
225 |
226 |
227 |
247 |
248 |
249 |
250 |
251 |
254 |
255 |
256 |
257 | );
258 | }
259 |
--------------------------------------------------------------------------------
/e-learning/src/components/Profile/UpdateProfile.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Heading, Input, VStack } from '@chakra-ui/react';
2 | import React, { useState } from 'react';
3 | import { useDispatch, useSelector } from 'react-redux';
4 | import { useNavigate } from 'react-router-dom';
5 | import { updateProfile } from '../../redux/actions/profile';
6 | import { loadUser } from '../../redux/actions/user';
7 |
8 | const UpdateProfile = ({ user }) => {
9 | const [name, setName] = useState(user.name);
10 | const [email, setEmail] = useState(user.email);
11 |
12 | const navigate = useNavigate();
13 | const dispatch = useDispatch();
14 | const submitHandler = async e => {
15 | e.preventDefault();
16 | await dispatch(updateProfile(name, email));
17 | dispatch(loadUser());
18 | navigate('/profile');
19 | };
20 |
21 | const { loading } = useSelector(state => state.profile);
22 | return (
23 |
24 |
57 |
58 | );
59 | };
60 |
61 | export default UpdateProfile;
62 |
--------------------------------------------------------------------------------
/e-learning/src/components/Request/Request.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Container,
5 | FormLabel,
6 | Heading,
7 | Input,
8 | Textarea,
9 | VStack,
10 | } from '@chakra-ui/react';
11 | import React from 'react';
12 | import { useState } from 'react';
13 | import { Link } from 'react-router-dom';
14 | import { useDispatch, useSelector } from 'react-redux';
15 | import { courseRequest } from '../../redux/actions/other';
16 | import toast from 'react-hot-toast';
17 | import { useEffect } from 'react';
18 |
19 | const Request = () => {
20 | const [name, setName] = useState('');
21 | const [email, setEmail] = useState('');
22 | const [course, setCourse] = useState('');
23 |
24 | const dispatch = useDispatch();
25 | const {
26 | loading,
27 | error,
28 | message: stateMessage,
29 | } = useSelector(state => state.other);
30 |
31 | const submitHandler = e => {
32 | e.preventDefault();
33 | dispatch(courseRequest(name, email, course));
34 | };
35 |
36 | useEffect(() => {
37 | if (error) {
38 | toast.error(error);
39 | dispatch({ type: 'clearError' });
40 | }
41 |
42 | if (stateMessage) {
43 | toast.success(stateMessage);
44 | dispatch({ type: 'clearMessage' });
45 | }
46 | }, [dispatch, error, stateMessage]);
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
111 |
112 |
113 | );
114 | };
115 | export default Request;
116 |
--------------------------------------------------------------------------------
/e-learning/src/index.js:
--------------------------------------------------------------------------------
1 | import { ColorModeScript, ChakraProvider, theme } from '@chakra-ui/react';
2 | import React from 'react';
3 | import * as ReactDOM from 'react-dom/client';
4 | import App from './App';
5 | import { Provider as ReduxProvider } from 'react-redux';
6 | import store from './redux/store';
7 |
8 | const container = document.getElementById('root');
9 | const root = ReactDOM.createRoot(container);
10 |
11 | root.render(
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 |
--------------------------------------------------------------------------------
/e-learning/src/redux/actions/admin.js:
--------------------------------------------------------------------------------
1 | import { server } from '../store';
2 | import axios from 'axios';
3 |
4 | export const createCourse = formData => async dispatch => {
5 | try {
6 | const config = {
7 | headers: {
8 | 'Content-type': 'multipart/form-data',
9 | },
10 | withCredentials: true,
11 | };
12 | dispatch({ type: 'createCourseRequest' });
13 |
14 | const { data } = await axios.post(
15 | `${server}/createcourse`,
16 | formData,
17 | config
18 | );
19 |
20 | dispatch({ type: 'createCourseSuccess', payload: data.message });
21 | } catch (error) {
22 | dispatch({
23 | type: 'createCourseFail',
24 | payload: error.response.data.message,
25 | });
26 | }
27 | };
28 |
29 | export const deleteCourse = id => async dispatch => {
30 | try {
31 | const config = {
32 | withCredentials: true,
33 | };
34 | dispatch({ type: 'deleteCourseRequest' });
35 |
36 | const { data } = await axios.delete(`${server}/course/${id}`, config);
37 |
38 | dispatch({ type: 'deleteCourseSuccess', payload: data.message });
39 | } catch (error) {
40 | dispatch({
41 | type: 'deleteCourseFail',
42 | payload: error.response.data.message,
43 | });
44 | }
45 | };
46 |
47 | export const addLecture = (id, formdata) => async dispatch => {
48 | try {
49 | const config = {
50 | headers: {
51 | 'Content-type': 'multipart/form-data',
52 | },
53 | withCredentials: true,
54 | };
55 | dispatch({ type: 'addLectureRequest' });
56 |
57 | const { data } = await axios.post(
58 | `${server}/course/${id}`,
59 | formdata,
60 | config
61 | );
62 |
63 | dispatch({ type: 'addLectureSuccess', payload: data.message });
64 | } catch (error) {
65 | dispatch({
66 | type: 'addLectureFail',
67 | payload: error.response.data.message,
68 | });
69 | }
70 | };
71 |
72 | export const deleteLecture = (courseId, lectureId) => async dispatch => {
73 | try {
74 | const config = {
75 | withCredentials: true,
76 | };
77 | dispatch({ type: 'deleteLectureRequest' });
78 |
79 | const { data } = await axios.delete(
80 | `${server}/lecture?courseId=${courseId}&lectureId=${lectureId}`,
81 | config
82 | );
83 |
84 | dispatch({ type: 'deleteLectureSuccess', payload: data.message });
85 | } catch (error) {
86 | dispatch({
87 | type: 'deleteLectureFail',
88 | payload: error.response.data.message,
89 | });
90 | }
91 | };
92 |
93 | export const getAllUsers = () => async dispatch => {
94 | try {
95 | const config = {
96 | withCredentials: true,
97 | };
98 | dispatch({ type: 'getAllUsersRequest' });
99 |
100 | const { data } = await axios.get(`${server}/admin/users`, config);
101 |
102 | dispatch({ type: 'getAllUsersSuccess', payload: data.users });
103 | } catch (error) {
104 | dispatch({
105 | type: 'getAllUsersFail',
106 | payload: error.response.data.message,
107 | });
108 | }
109 | };
110 |
111 | export const updateUserRole = id => async dispatch => {
112 | try {
113 | const config = {
114 | withCredentials: true,
115 | };
116 | dispatch({ type: 'updateUserRoleRequest' });
117 |
118 | const { data } = await axios.put(`${server}/admin/user/${id}`, {}, config);
119 |
120 | dispatch({ type: 'updateUserRoleSuccess', payload: data.message });
121 | } catch (error) {
122 | dispatch({
123 | type: 'updateUserRoleFail',
124 | payload: error.response.data.message,
125 | });
126 | }
127 | };
128 |
129 | export const deleteUser = id => async dispatch => {
130 | try {
131 | const config = {
132 | withCredentials: true,
133 | };
134 | dispatch({ type: 'deleteUserRequest' });
135 |
136 | const { data } = await axios.delete(`${server}/admin/user/${id}`, config);
137 |
138 | dispatch({ type: 'deleteUserSuccess', payload: data.message });
139 | } catch (error) {
140 | dispatch({
141 | type: 'deleteUserFail',
142 | payload: error.response.data.message,
143 | });
144 | }
145 | };
146 |
147 | export const getDashboardStats = () => async dispatch => {
148 | try {
149 | const config = {
150 | withCredentials: true,
151 | };
152 | dispatch({ type: 'getAdminStatsRequest' });
153 |
154 | const { data } = await axios.get(`${server}/admin/stats`, config);
155 |
156 | dispatch({ type: 'getAdminStatsSuccess', payload: data });
157 | } catch (error) {
158 | dispatch({
159 | type: 'getAdminStatsFail',
160 | payload: error.response.data.message,
161 | });
162 | }
163 | };
164 |
--------------------------------------------------------------------------------
/e-learning/src/redux/actions/course.js:
--------------------------------------------------------------------------------
1 | import { server } from '../store';
2 | import axios from 'axios';
3 |
4 | export const getAllCourses =
5 | (category = '', keyword = '') =>
6 | async dispatch => {
7 | try {
8 | dispatch({ type: 'allCoursesRequest' });
9 | const { data } = await axios.get(
10 | `${server}/courses?keyword=${keyword}&category=${category}`
11 | );
12 |
13 | dispatch({ type: 'allCoursesSuccess', payload: data.courses });
14 | } catch (error) {
15 | dispatch({
16 | type: 'allCoursesFail',
17 | payload: error.response.data.message,
18 | });
19 | }
20 | };
21 |
22 | export const getCourseLectures = id => async dispatch => {
23 | try {
24 | dispatch({ type: 'getCourseRequest' });
25 |
26 | const { data } = await axios.get(`${server}/course/${id}`, {
27 | withCredentials: true,
28 | });
29 |
30 | dispatch({ type: 'getCourseSuccess', payload: data.lectures });
31 | } catch (error) {
32 | dispatch({
33 | type: 'getCourseFail',
34 | payload: error.response.data.message,
35 | });
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/e-learning/src/redux/actions/other.js:
--------------------------------------------------------------------------------
1 | import { server } from '../store';
2 | import axios from 'axios';
3 |
4 | export const contactUs = (name, email, message) => async dispatch => {
5 | try {
6 | const config = {
7 | headers: {
8 | 'Content-type': 'application/json',
9 | },
10 | withCredentials: true,
11 | };
12 |
13 | dispatch({ type: 'contactRequest' });
14 |
15 | const { data } = await axios.post(
16 | `${server}/contact`,
17 | { name, email, message },
18 | config
19 | );
20 |
21 | dispatch({ type: 'contactSuccess', payload: data.message });
22 | } catch (error) {
23 | dispatch({
24 | type: 'contactFail',
25 | payload: error.response.data.message,
26 | });
27 | }
28 | };
29 |
30 | export const courseRequest = (name, email, course) => async dispatch => {
31 | try {
32 | const config = {
33 | headers: {
34 | 'Content-type': 'application/json',
35 | },
36 | withCredentials: true,
37 | };
38 |
39 | dispatch({ type: 'courseRequestRequest' });
40 |
41 | const { data } = await axios.post(
42 | `${server}/courserequest`,
43 | { name, email, course },
44 | config
45 | );
46 |
47 | dispatch({ type: 'courseRequestSuccess', payload: data.message });
48 | } catch (error) {
49 | dispatch({
50 | type: 'courseRequestFail',
51 | payload: error.response.data.message,
52 | });
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/e-learning/src/redux/actions/profile.js:
--------------------------------------------------------------------------------
1 | import { server } from '../store';
2 | import axios from 'axios';
3 |
4 | export const updateProfile = (name, email) => async dispatch => {
5 | try {
6 | dispatch({ type: 'updateProfileRequest' });
7 |
8 | const { data } = await axios.put(
9 | `${server}/updateprofile`,
10 | {
11 | name,
12 | email,
13 | },
14 | {
15 | headers: {
16 | 'Content-type': 'application/json',
17 | },
18 |
19 | withCredentials: true,
20 | }
21 | );
22 |
23 | dispatch({ type: 'updateProfileSuccess', payload: data.message });
24 | } catch (error) {
25 | dispatch({
26 | type: 'updateProfileFail',
27 | payload: error.response.data.message,
28 | });
29 | }
30 | };
31 |
32 | export const updateProfilePicture = formdata => async dispatch => {
33 | try {
34 | dispatch({ type: 'updateProfilePictureRequest' });
35 |
36 | const { data } = await axios.put(
37 | `${server}/updateprofilepicture`,
38 | formdata,
39 | {
40 | headers: {
41 | 'Content-type': 'multipart/form-data',
42 | },
43 |
44 | withCredentials: true,
45 | }
46 | );
47 |
48 | dispatch({ type: 'updateProfilePictureSuccess', payload: data.message });
49 | } catch (error) {
50 | dispatch({
51 | type: 'updateProfilePictureFail',
52 | payload: error.response.data.message,
53 | });
54 | }
55 | };
56 |
57 | export const changePassword = (oldPassword, newPassword) => async dispatch => {
58 | try {
59 | dispatch({ type: 'changePasswordRequest' });
60 |
61 | const { data } = await axios.put(
62 | `${server}/changepassword`,
63 | {
64 | oldPassword,
65 | newPassword,
66 | },
67 | {
68 | headers: {
69 | 'Content-type': 'application/json',
70 | },
71 |
72 | withCredentials: true,
73 | }
74 | );
75 |
76 | dispatch({ type: 'changePasswordSuccess', payload: data.message });
77 | } catch (error) {
78 | dispatch({
79 | type: 'changePasswordFail',
80 | payload: error.response.data.message,
81 | });
82 | }
83 | };
84 |
85 | export const forgetPassword = email => async dispatch => {
86 | try {
87 | dispatch({ type: 'forgetPasswordRequest' });
88 |
89 | const config = {
90 | headers: {
91 | 'Content-type': 'application/json',
92 | },
93 |
94 | withCredentials: true,
95 | };
96 |
97 | const { data } = await axios.post(
98 | `${server}/forgetpassword`,
99 | {
100 | email,
101 | },
102 | config
103 | );
104 |
105 | dispatch({ type: 'forgetPasswordSuccess', payload: data.message });
106 | } catch (error) {
107 | dispatch({
108 | type: 'forgetPasswordFail',
109 | payload: error.response.data.message,
110 | });
111 | }
112 | };
113 |
114 | export const resetPassword = (token, password) => async dispatch => {
115 | try {
116 | dispatch({ type: 'resetPasswordRequest' });
117 | const config = {
118 | headers: {
119 | 'Content-type': 'application/json',
120 | },
121 |
122 | withCredentials: true,
123 | };
124 |
125 | const { data } = await axios.put(
126 | `${server}/resetpassword/${token}`,
127 | {
128 | password,
129 | },
130 | config
131 | );
132 |
133 | dispatch({ type: 'resetPasswordSuccess', payload: data.message });
134 | } catch (error) {
135 | dispatch({
136 | type: 'resetPasswordFail',
137 | payload: error.response.data.message,
138 | });
139 | }
140 | };
141 |
142 | export const addToPlaylist = id => async dispatch => {
143 | try {
144 | dispatch({ type: 'addToPlaylistRequest' });
145 |
146 | const config = {
147 | headers: {
148 | 'Content-type': 'application/json',
149 | },
150 |
151 | withCredentials: true,
152 | };
153 | const { data } = await axios.post(
154 | `${server}/addtoplaylist`,
155 | {
156 | id,
157 | },
158 | config
159 | );
160 |
161 | dispatch({ type: 'addToPlaylistSuccess', payload: data.message });
162 | } catch (error) {
163 | dispatch({
164 | type: 'addToPlaylistFail',
165 | payload: error.response.data.message,
166 | });
167 | }
168 | };
169 |
170 | export const removeFromPlaylist = id => async dispatch => {
171 | try {
172 | dispatch({ type: 'removeFromPlaylistRequest' });
173 |
174 | const config = {
175 | withCredentials: true,
176 | };
177 |
178 | const { data } = await axios.delete(
179 | `${server}/removefromplaylist?id=${id}`,
180 | config
181 | );
182 |
183 | dispatch({ type: 'removeFromPlaylistSuccess', payload: data.message });
184 | } catch (error) {
185 | dispatch({
186 | type: 'removeFromPlaylistFail',
187 | payload: error.response.data.message,
188 | });
189 | }
190 | };
191 |
--------------------------------------------------------------------------------
/e-learning/src/redux/actions/recommendedCourse.js:
--------------------------------------------------------------------------------
1 | import { server } from '../store';
2 | import axios from 'axios';
3 |
4 | export const createRecommendationCourses = () => async dispatch => {
5 | try {
6 | dispatch({ type: 'RecommendedCoursesRequest' });
7 | const { data } = await axios.post(
8 | `${server}/recommend`, {},
9 | {
10 | headers: {
11 | 'Content-type': 'application/json',
12 | },
13 |
14 | withCredentials: true,
15 | }
16 | );
17 |
18 | dispatch({ type: 'RecommendedCoursesSuccess', payload: data.courses });
19 | } catch (error) {
20 | dispatch({
21 | type: 'RecommendedCoursesFail',
22 | payload: error.response.data.message,
23 | });
24 | }
25 | };
--------------------------------------------------------------------------------
/e-learning/src/redux/actions/user.js:
--------------------------------------------------------------------------------
1 | import { server } from '../store';
2 | import axios from 'axios';
3 |
4 | export const login = (email, password) => async dispatch => {
5 | try {
6 | dispatch({ type: 'loginRequest' });
7 |
8 | const { data } = await axios.post(
9 | `${server}/login`,
10 | { email, password },
11 | {
12 | headers: {
13 | 'Content-type': 'application/json',
14 | },
15 |
16 | withCredentials: true,
17 | }
18 | );
19 |
20 | dispatch({ type: 'loginSuccess', payload: data });
21 | } catch (error) {
22 | dispatch({ type: 'loginFail', payload: error.response.data.message });
23 | }
24 | };
25 |
26 | export const register = formdata => async dispatch => {
27 | try {
28 | dispatch({ type: 'registerRequest' });
29 |
30 | const { data } = await axios.post(`${server}/register`, formdata, {
31 | headers: {
32 | 'Content-type': 'multipart/form-data',
33 | },
34 |
35 | withCredentials: true,
36 | });
37 |
38 | dispatch({ type: 'registerSuccess', payload: data });
39 | } catch (error) {
40 | dispatch({ type: 'registerFail', payload: error.response.data.message });
41 | }
42 | };
43 |
44 | export const loadUser = () => async dispatch => {
45 | try {
46 | dispatch({ type: 'loadUserRequest' });
47 |
48 | const { data } = await axios.get(
49 | `${server}/me`,
50 |
51 | {
52 | withCredentials: true,
53 | }
54 | );
55 | dispatch({ type: 'loadUserSuccess', payload: data.user });
56 | } catch (error) {
57 | dispatch({ type: 'loadUserFail', payload: error.response.data.message });
58 | }
59 | };
60 |
61 | export const logout = () => async dispatch => {
62 | try {
63 | dispatch({ type: 'logoutRequest' });
64 |
65 | const { data } = await axios.get(`${server}/logout`, {
66 | withCredentials: true,
67 | });
68 | dispatch({ type: 'logoutSuccess', payload: data.message });
69 | } catch (error) {
70 | dispatch({ type: 'logoutFail', payload: error.response.data.message });
71 | }
72 | };
73 |
74 | export const buySubscription = () => async dispatch => {
75 | try {
76 | dispatch({ type: 'buySubscriptionRequest' });
77 |
78 | const { data } = await axios.get(`${server}/subscribe`, {
79 | withCredentials: true,
80 | });
81 |
82 | dispatch({ type: 'buySubscriptionSuccess', payload: data.subscriptionId });
83 | } catch (error) {
84 | dispatch({
85 | type: 'buySubscriptionFail',
86 | payload: error.response.data.message,
87 | });
88 | }
89 | };
90 |
91 | export const cancelSubscription = () => async dispatch => {
92 | try {
93 | dispatch({ type: 'cancelSubscriptionRequest' });
94 |
95 | const { data } = await axios.delete(`${server}/subscribe/cancel`, {
96 | withCredentials: true,
97 | });
98 |
99 | dispatch({ type: 'cancelSubscriptionSuccess', payload: data.message });
100 | } catch (error) {
101 | dispatch({
102 | type: 'cancelSubscriptionFail',
103 | payload: error.response.data.message,
104 | });
105 | }
106 | };
107 |
--------------------------------------------------------------------------------
/e-learning/src/redux/reducers/adminReducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit';
2 |
3 | export const adminReducer = createReducer(
4 | {},
5 | {
6 | getAdminStatsRequest: state => {
7 | state.loading = true;
8 | },
9 | getAdminStatsSuccess: (state, action) => {
10 | state.loading = false;
11 | state.stats = action.payload.stats;
12 | state.viewsCount = action.payload.viewsCount;
13 | state.subscriptionCount = action.payload.subscriptionCount;
14 | state.usersCount = action.payload.usersCount;
15 | state.subscriptionPercentage = action.payload.subscriptionPercentage;
16 | state.viewsPercentage = action.payload.viewsPercentage;
17 | state.usersPercentage = action.payload.usersPercentage;
18 | state.subscriptionProfit = action.payload.subscriptionProfit;
19 | state.viewsProfit = action.payload.viewsProfit;
20 | state.usersProfit = action.payload.usersProfit;
21 | },
22 | getAdminStatsFail: (state, action) => {
23 | state.loading = false;
24 | state.error = action.payload;
25 | },
26 |
27 | getAllUsersRequest: state => {
28 | state.loading = true;
29 | },
30 | getAllUsersSuccess: (state, action) => {
31 | state.loading = false;
32 | state.users = action.payload;
33 | },
34 | getAllUsersFail: (state, action) => {
35 | state.loading = false;
36 | state.error = action.payload;
37 | },
38 | updateUserRoleRequest: state => {
39 | state.loading = true;
40 | },
41 | updateUserRoleSuccess: (state, action) => {
42 | state.loading = false;
43 | state.message = action.payload;
44 | },
45 | updateUserRoleFail: (state, action) => {
46 | state.loading = false;
47 | state.error = action.payload;
48 | },
49 | deleteUserRequest: state => {
50 | state.loading = true;
51 | },
52 | deleteUserSuccess: (state, action) => {
53 | state.loading = false;
54 | state.message = action.payload;
55 | },
56 | deleteUserFail: (state, action) => {
57 | state.loading = false;
58 | state.error = action.payload;
59 | },
60 |
61 | createCourseRequest: state => {
62 | state.loading = true;
63 | },
64 | createCourseSuccess: (state, action) => {
65 | state.loading = false;
66 | state.message = action.payload;
67 | },
68 | createCourseFail: (state, action) => {
69 | state.loading = false;
70 | state.error = action.payload;
71 | },
72 | deleteCourseRequest: state => {
73 | state.loading = true;
74 | },
75 | deleteCourseSuccess: (state, action) => {
76 | state.loading = false;
77 | state.message = action.payload;
78 | },
79 | deleteCourseFail: (state, action) => {
80 | state.loading = false;
81 | state.error = action.payload;
82 | },
83 |
84 | addLectureRequest: state => {
85 | state.loading = true;
86 | },
87 | addLectureSuccess: (state, action) => {
88 | state.loading = false;
89 | state.message = action.payload;
90 | },
91 | addLectureFail: (state, action) => {
92 | state.loading = false;
93 | state.error = action.payload;
94 | },
95 |
96 | deleteLectureRequest: state => {
97 | state.loading = true;
98 | },
99 | deleteLectureSuccess: (state, action) => {
100 | state.loading = false;
101 | state.message = action.payload;
102 | },
103 | deleteLectureFail: (state, action) => {
104 | state.loading = false;
105 | state.error = action.payload;
106 | },
107 | clearError: state => {
108 | state.error = null;
109 | },
110 | clearMessage: state => {
111 | state.message = null;
112 | },
113 | }
114 | );
115 |
--------------------------------------------------------------------------------
/e-learning/src/redux/reducers/courseReducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit';
2 |
3 | export const courseReducer = createReducer(
4 | { courses: [], lectures: [] },
5 | {
6 | allCoursesRequest: state => {
7 | state.loading = true;
8 | },
9 | allCoursesSuccess: (state, action) => {
10 | state.loading = false;
11 | state.courses = action.payload;
12 | },
13 | allCoursesFail: (state, action) => {
14 | state.loading = false;
15 | state.error = action.payload;
16 | },
17 |
18 | getCourseRequest: state => {
19 | state.loading = true;
20 | },
21 | getCourseSuccess: (state, action) => {
22 | state.loading = false;
23 | state.lectures = action.payload;
24 | },
25 | getCourseFail: (state, action) => {
26 | state.loading = false;
27 | state.error = action.payload;
28 | },
29 |
30 | addToPlaylistRequest: state => {
31 | state.loading = true;
32 | },
33 | addToPlaylistSuccess: (state, action) => {
34 | state.loading = false;
35 | state.message = action.payload;
36 | },
37 | addToPlaylistFail: (state, action) => {
38 | state.loading = false;
39 | state.error = action.payload;
40 | },
41 |
42 | clearError: state => {
43 | state.error = null;
44 | },
45 | clearMessage: state => {
46 | state.message = null;
47 | },
48 | }
49 | );
50 |
--------------------------------------------------------------------------------
/e-learning/src/redux/reducers/otherReducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit';
2 |
3 | export const otherReducer = createReducer(
4 | {},
5 | {
6 | contactRequest: state => {
7 | state.loading = true;
8 | },
9 | contactSuccess: (state, action) => {
10 | state.loading = false;
11 | state.message = action.payload;
12 | },
13 | contactFail: (state, action) => {
14 | state.loading = false;
15 | state.error = action.payload;
16 | },
17 | courseRequestRequest: state => {
18 | state.loading = true;
19 | },
20 | courseRequestSuccess: (state, action) => {
21 | state.loading = false;
22 | state.message = action.payload;
23 | },
24 | courseRequestFail: (state, action) => {
25 | state.loading = false;
26 | state.error = action.payload;
27 | },
28 |
29 | clearError: state => {
30 | state.error = null;
31 | },
32 | clearMessage: state => {
33 | state.message = null;
34 | },
35 | }
36 | );
37 |
--------------------------------------------------------------------------------
/e-learning/src/redux/reducers/recommendedCourseReducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit';
2 | export const recommendedCourseReducer = createReducer(
3 | { courses: [], lectures: [] },
4 | {
5 | RecommendedCoursesRequest: state => {
6 | state.loading = true;
7 | },
8 | RecommendedCoursesSuccess: (state, action) => {
9 | state.loading = false;
10 | state.courses = action.payload;
11 | },
12 | RecommendedCoursesFail: (state, action) => {
13 | state.loading = false;
14 | state.error = action.payload;
15 | },
16 | clearError: state => {
17 | state.error = null;
18 | },
19 | clearMessage: state => {
20 | state.message = null;
21 | },
22 | }
23 | );
24 |
--------------------------------------------------------------------------------
/e-learning/src/redux/reducers/userReducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit';
2 |
3 | export const userReducer = createReducer(
4 | {},
5 | {
6 | loginRequest: state => {
7 | state.loading = true;
8 | },
9 | loginSuccess: (state, action) => {
10 | state.loading = false;
11 | state.isAuthenticated = true;
12 | state.user = action.payload.user;
13 | state.message = action.payload.message;
14 | },
15 | loginFail: (state, action) => {
16 | state.loading = false;
17 | state.isAuthenticated = false;
18 | state.error = action.payload;
19 | },
20 |
21 | registerRequest: state => {
22 | state.loading = true;
23 | },
24 | registerSuccess: (state, action) => {
25 | state.loading = false;
26 | state.isAuthenticated = true;
27 | state.user = action.payload.user;
28 | state.message = action.payload.message;
29 | },
30 | registerFail: (state, action) => {
31 | state.loading = false;
32 | state.isAuthenticated = false;
33 | state.error = action.payload;
34 | },
35 |
36 | logoutRequest: state => {
37 | state.loading = true;
38 | },
39 | logoutSuccess: (state, action) => {
40 | state.loading = false;
41 | state.isAuthenticated = false;
42 | state.user = null;
43 | state.message = action.payload;
44 | },
45 | logoutFail: (state, action) => {
46 | state.loading = false;
47 | state.isAuthenticated = true;
48 | state.error = action.payload;
49 | },
50 |
51 | loadUserRequest: state => {
52 | state.loading = true;
53 | },
54 | loadUserSuccess: (state, action) => {
55 | state.loading = false;
56 | state.isAuthenticated = true;
57 | state.user = action.payload;
58 | },
59 | loadUserFail: (state, action) => {
60 | state.loading = false;
61 | state.isAuthenticated = false;
62 | state.error = action.payload;
63 | },
64 |
65 | clearError: state => {
66 | state.error = null;
67 | },
68 | clearMessage: state => {
69 | state.message = null;
70 | },
71 | }
72 | );
73 |
74 | export const profileReducer = createReducer(
75 | {},
76 | {
77 | updateProfileRequest: state => {
78 | state.loading = true;
79 | },
80 | updateProfileSuccess: (state, action) => {
81 | state.loading = false;
82 | state.message = action.payload;
83 | },
84 | updateProfileFail: (state, action) => {
85 | state.loading = false;
86 | state.error = action.payload;
87 | },
88 |
89 | updateProfilePictureRequest: state => {
90 | state.loading = true;
91 | },
92 | updateProfilePictureSuccess: (state, action) => {
93 | state.loading = false;
94 | state.message = action.payload;
95 | },
96 | updateProfilePictureFail: (state, action) => {
97 | state.loading = false;
98 | state.error = action.payload;
99 | },
100 |
101 | changePasswordRequest: state => {
102 | state.loading = true;
103 | },
104 | changePasswordSuccess: (state, action) => {
105 | state.loading = false;
106 | state.message = action.payload;
107 | },
108 | changePasswordFail: (state, action) => {
109 | state.loading = false;
110 | state.error = action.payload;
111 | },
112 |
113 | forgetPasswordRequest: state => {
114 | state.loading = true;
115 | },
116 | forgetPasswordSuccess: (state, action) => {
117 | state.loading = false;
118 | state.message = action.payload;
119 | },
120 | forgetPasswordFail: (state, action) => {
121 | state.loading = false;
122 | state.error = action.payload;
123 | },
124 |
125 | resetPasswordRequest: state => {
126 | state.loading = true;
127 | },
128 | resetPasswordSuccess: (state, action) => {
129 | state.loading = false;
130 | state.message = action.payload;
131 | },
132 | resetPasswordFail: (state, action) => {
133 | state.loading = false;
134 | state.error = action.payload;
135 | },
136 |
137 | removeFromPlaylistRequest: state => {
138 | state.loading = true;
139 | },
140 | removeFromPlaylistSuccess: (state, action) => {
141 | state.loading = false;
142 | state.message = action.payload;
143 | },
144 | removeFromPlaylistFail: (state, action) => {
145 | state.loading = false;
146 | state.error = action.payload;
147 | },
148 |
149 | clearError: state => {
150 | state.error = null;
151 | },
152 | clearMessage: state => {
153 | state.message = null;
154 | },
155 | }
156 | );
157 |
158 | export const subscriptionReducer = createReducer(
159 | {},
160 | {
161 | buySubscriptionRequest: state => {
162 | state.loading = true;
163 | },
164 | buySubscriptionSuccess: (state, action) => {
165 | state.loading = false;
166 | state.subscriptionId = action.payload;
167 | },
168 | buySubscriptionFail: (state, action) => {
169 | state.loading = false;
170 | state.error = action.payload;
171 | },
172 |
173 | cancelSubscriptionRequest: state => {
174 | state.loading = true;
175 | },
176 | cancelSubscriptionSuccess: (state, action) => {
177 | state.loading = false;
178 | state.message = action.payload;
179 | },
180 | cancelSubscriptionFail: (state, action) => {
181 | state.loading = false;
182 | state.error = action.payload;
183 | },
184 | clearError: state => {
185 | state.error = null;
186 | },
187 | clearMessage: state => {
188 | state.message = null;
189 | },
190 | }
191 | );
192 |
--------------------------------------------------------------------------------
/e-learning/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import {
3 | profileReducer,
4 | subscriptionReducer,
5 | userReducer,
6 | } from './reducers/userReducer';
7 | import { courseReducer } from './reducers/courseReducer';
8 | import { adminReducer } from './reducers/adminReducer';
9 | import { otherReducer } from './reducers/otherReducer';
10 | import { recommendedCourseReducer } from './reducers/recommendedCourseReducer';
11 | const store = configureStore({
12 | reducer: {
13 | user: userReducer,
14 | profile: profileReducer,
15 | course: courseReducer,
16 | subscription: subscriptionReducer,
17 | admin: adminReducer,
18 | other: otherReducer,
19 | recommendedCourse: recommendedCourseReducer
20 | },
21 | });
22 |
23 | export default store;
24 |
25 | // export const server = 'http://localhost:4000/api/v1';
26 | export const server = 'https://e-learning-2eqp.onrender.com/api/v1'
27 |
--------------------------------------------------------------------------------
/e-learningserver/app.js:
--------------------------------------------------------------------------------
1 | // Define "require"
2 | import { createRequire } from "module";
3 | const require = createRequire(import.meta.url)
4 |
5 | //Accessing .env variable using Dotenv
6 | if (process.env.NODE_ENV !== "PRODUCTION") {
7 | require("dotenv").config({
8 | path: "./config/config.env"
9 | })
10 | }
11 | import express from "express"
12 | import ErrorMiddleware from "./middlewares/Error.js"
13 | import cookieParser from "cookie-parser"
14 | import cors from "cors"
15 |
16 |
17 | const app = express()
18 |
19 | // Using Middlewares
20 | app.use(express.json());
21 | app.use(
22 | express.urlencoded({
23 | extended: true,
24 | })
25 | );
26 | app.use(cookieParser());
27 | app.use(
28 | cors({
29 | origin: process.env.FRONTEND_URL,
30 | credentials: true,
31 | methods: ["GET", "POST", "PUT", "DELETE"],
32 | })
33 | );
34 |
35 |
36 |
37 | import course from "./routes/courseRoutes.js"
38 | import user from "./routes/userRoutes.js"
39 | import payment from "./routes/paymentRoutes.js"
40 | import other from "./routes/otherRoutes.js"
41 |
42 | app.use("/api/v1", course)
43 | app.use("/api/v1", user)
44 | app.use("/api/v1", payment)
45 | app.use("/api/v1", other)
46 |
47 | const path = require("path")
48 | const __dirname = path.resolve()
49 | app.use(express.static(path.join(__dirname, "../e-learning/build")))
50 | app.get("*", (req, res) => {
51 | res.sendFile(path.resolve(__dirname, "../e-learning/build/index.html"))
52 | })
53 |
54 | app.use(ErrorMiddleware)
55 |
56 | export default app
--------------------------------------------------------------------------------
/e-learningserver/config/database.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose"
2 | export const connectDB = async () => {
3 | mongoose.set('strictQuery', true);
4 | const { connection } = await mongoose.connect(process.env.MONGO_URI)
5 | console.log(`MongoDB Connected with ${connection.host} `)
6 | }
--------------------------------------------------------------------------------
/e-learningserver/controllers/courseController.js:
--------------------------------------------------------------------------------
1 | import { catchAsyncError } from "../middlewares/catchAsyncError.js"
2 | import { Course } from "../models/Course.js"
3 | import getDataUri from "../utils/dataUri.js"
4 | import ErrorHandler from "../utils/errorHandler.js"
5 | import cloudinary from "cloudinary"
6 | import { Stats } from "../models/Stats.js"
7 |
8 | export const getAllCourses = catchAsyncError(async (req, res, next) => {
9 | const keyword = req.query.keyword || "";
10 | const category = req.query.category || "";
11 | const courses = await Course.find({
12 | title: {
13 | $regex: keyword,
14 | $options: "i",
15 | },
16 | category: {
17 | $regex: category,
18 | $options: "i",
19 | },
20 | }).select("-lectures");
21 | res.status(200).json({
22 | success: true,
23 | courses,
24 | });
25 | });
26 |
27 | export const createCourse = catchAsyncError(async (req, res, next) => {
28 | const { title, description, category, createdBy } = req.body;
29 | if (!title || !description || !category || !createdBy) {
30 | return next(new ErrorHandler("Please add all fields", 400))
31 | }
32 |
33 | const file = req.file
34 |
35 | const fileUri = getDataUri(file)
36 |
37 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content, { folder: "elearning", });
38 |
39 | await Course.create({
40 | title,
41 | description,
42 | category,
43 | createdBy,
44 | poster: {
45 | public_id: mycloud.public_id,
46 | url: mycloud.secure_url,
47 | },
48 | })
49 |
50 | res.status(201).json({
51 | success: true,
52 | message: "Course Created Successfully. You can add lectures now.",
53 | })
54 | })
55 |
56 | export const getCourseLectures = catchAsyncError(async (req, res, next) => {
57 | const course = await Course.findById(req.params.id);
58 |
59 | if (!course) return next(new ErrorHandler("Course not found", 404));
60 |
61 | course.views += 1;
62 |
63 | await course.save();
64 |
65 | res.status(200).json({
66 | success: true,
67 | lectures: course.lectures,
68 | });
69 | });
70 |
71 | // // Max video size 100mb
72 | export const addLecture = catchAsyncError(async (req, res, next) => {
73 | const { id } = req.params;
74 | const { title, description } = req.body;
75 |
76 | const course = await Course.findById(id);
77 |
78 | if (!course) return next(new ErrorHandler("Course not found", 404));
79 |
80 | const file = req.file;
81 | const fileUri = getDataUri(file);
82 |
83 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content, {
84 | resource_type: "video",
85 | folder: "elearning"
86 | });
87 |
88 | course.lectures.push({
89 | title,
90 | description,
91 | video: {
92 | public_id: mycloud.public_id,
93 | url: mycloud.secure_url,
94 | },
95 | });
96 |
97 | course.numOfVideos = course.lectures.length;
98 |
99 | await course.save();
100 |
101 | res.status(200).json({
102 | success: true,
103 | message: "Lecture added in Course",
104 | });
105 | });
106 |
107 | export const deleteCourse = catchAsyncError(async (req, res, next) => {
108 | const { id } = req.params;
109 |
110 | const course = await Course.findById(id);
111 |
112 | if (!course) return next(new ErrorHandler("Course not found", 404));
113 |
114 | await cloudinary.v2.uploader.destroy(course.poster.public_id);
115 |
116 | for (let i = 0; i < course.lectures.length; i++) {
117 | const singleLecture = course.lectures[i];
118 | await cloudinary.v2.uploader.destroy(singleLecture.video.public_id, {
119 | resource_type: "video",
120 | });
121 | }
122 |
123 | await course.remove();
124 |
125 | res.status(200).json({
126 | success: true,
127 | message: "Course Deleted Successfully",
128 | });
129 | });
130 |
131 | export const deleteLecture = catchAsyncError(async (req, res, next) => {
132 | const { courseId, lectureId } = req.query;
133 |
134 | const course = await Course.findById(courseId);
135 | if (!course) return next(new ErrorHandler("Course not found", 404));
136 |
137 | const lecture = course.lectures.find((item) => {
138 | if (item._id.toString() === lectureId.toString()) return item;
139 | });
140 | await cloudinary.v2.uploader.destroy(lecture.video.public_id, {
141 | resource_type: "video",
142 | });
143 |
144 | course.lectures = course.lectures.filter((item) => {
145 | if (item._id.toString() !== lectureId.toString()) return item;
146 | });
147 |
148 | course.numOfVideos = course.lectures.length;
149 |
150 | await course.save();
151 |
152 | res.status(200).json({
153 | success: true,
154 | message: "Lecture Deleted Successfully",
155 | });
156 | });
157 |
158 | Course.watch().on("change", async () => {
159 | let stat_size = await Stats.countDocuments();
160 | if (stat_size === 0)
161 | await Stats.create({});
162 |
163 |
164 | const stats = await Stats.find({}).sort({ createdAt: "desc" }).limit(1);
165 |
166 | const courses = await Course.find({});
167 |
168 | let totalViews = 0;
169 |
170 | for (let i = 0; i < courses.length; i++) {
171 | totalViews += courses[i].views;
172 | }
173 | stats[0].views = totalViews;
174 | stats[0].createdAt = new Date(Date.now());
175 |
176 | await stats[0].save();
177 | });
178 |
--------------------------------------------------------------------------------
/e-learningserver/controllers/otherController.js:
--------------------------------------------------------------------------------
1 | import { catchAsyncError } from "../middlewares/catchAsyncError.js";
2 | import ErrorHandler from "../utils/errorHandler.js";
3 | import { sendEmail } from "../utils/sendEmail.js";
4 | import { Stats } from "../models/Stats.js";
5 |
6 | export const contact = catchAsyncError(async (req, res, next) => {
7 | const { name, email, message } = req.body;
8 |
9 | if (!name || !email || !message)
10 | return next(new ErrorHandler("All fields are mandatory", 400));
11 |
12 | const to = process.env.MY_MAIL;
13 | const subject = "Contact from E-learning Platform";
14 | const text = `I am ${name} and my Email is ${email}. \n${message}`;
15 |
16 | await sendEmail(to, subject, text);
17 |
18 | res.status(200).json({
19 | success: true,
20 | message: "Your Message Has Been Sent.",
21 | });
22 | });
23 |
24 | export const courseRequest = catchAsyncError(async (req, res, next) => {
25 | const { name, email, course } = req.body;
26 | if (!name || !email || !course)
27 | return next(new ErrorHandler("All fields are mandatory", 400));
28 |
29 | const to = process.env.MY_MAIL;
30 | const subject = "Requesting for a course on E-learning Platform";
31 | const text = `I am ${name} and my Email is ${email}. \n${course}`;
32 |
33 | await sendEmail(to, subject, text);
34 |
35 | res.status(200).json({
36 | success: true,
37 | message: "Your Request Has Been Sent.",
38 | });
39 | });
40 |
41 | export const getDashboardStats = catchAsyncError(async (req, res, next) => {
42 | const stats = await Stats.find({}).sort({ createdAt: "desc" }).limit(12);
43 |
44 | const statsData = [];
45 |
46 | for (let i = 0; i < stats.length; i++) {
47 | statsData.unshift(stats[i]);
48 | }
49 | const requiredSize = 12 - stats.length;
50 |
51 | for (let i = 0; i < requiredSize; i++) {
52 | statsData.unshift({
53 | users: 0,
54 | subscription: 0,
55 | views: 0,
56 | });
57 | }
58 |
59 | const usersCount = statsData[11].users;
60 | const subscriptionCount = statsData[11].subscription;
61 | const viewsCount = statsData[11].views;
62 |
63 | let usersPercentage = 0,
64 | viewsPercentage = 0,
65 | subscriptionPercentage = 0;
66 | let usersProfit = true,
67 | viewsProfit = true,
68 | subscriptionProfit = true;
69 |
70 | if (statsData[10].users === 0) usersPercentage = usersCount * 100;
71 | if (statsData[10].views === 0) viewsPercentage = viewsCount * 100;
72 | if (statsData[10].subscription === 0)
73 | subscriptionPercentage = subscriptionCount * 100;
74 | else {
75 | const difference = {
76 | users: statsData[11].users - statsData[10].users,
77 | views: statsData[11].views - statsData[10].views,
78 | subscription: statsData[11].subscription - statsData[10].subscription,
79 | };
80 |
81 | usersPercentage = (difference.users / statsData[10].users) * 100;
82 | viewsPercentage = (difference.views / statsData[10].views) * 100;
83 | subscriptionPercentage =
84 | (difference.subscription / statsData[10].subscription) * 100;
85 | if (usersPercentage < 0) usersProfit = false;
86 | if (viewsPercentage < 0) viewsProfit = false;
87 | if (subscriptionPercentage < 0) subscriptionProfit = false;
88 | }
89 |
90 | res.status(200).json({
91 | success: true,
92 | stats: statsData,
93 | usersCount,
94 | subscriptionCount,
95 | viewsCount,
96 | subscriptionPercentage,
97 | viewsPercentage,
98 | usersPercentage,
99 | subscriptionProfit,
100 | viewsProfit,
101 | usersProfit,
102 | });
103 | });
104 |
--------------------------------------------------------------------------------
/e-learningserver/controllers/paymentController.js:
--------------------------------------------------------------------------------
1 | import { catchAsyncError } from "../middlewares/catchAsyncError.js";
2 | import { User } from "../models/User.js";
3 | import ErrorHandler from "../utils/errorHandler.js";
4 | import { instance } from "../server.js";
5 | import crypto from "crypto";
6 | import { Payment } from "../models/Payment.js";
7 |
8 | export const buySubscription = catchAsyncError(async (req, res, next) => {
9 | const user = await User.findById(req.user._id);
10 |
11 | if (user.role === "admin")
12 | return next(new ErrorHandler("Admin can't buy subscription", 400));
13 |
14 | const plan_id = process.env.PLAN_ID || "plan_QVtZCB6T0hhISz";
15 |
16 | const subscription = await instance.subscriptions.create({
17 | plan_id,
18 | customer_notify: 1,
19 | total_count: 12,
20 | });
21 |
22 | user.subscription.id = subscription.id;
23 |
24 | user.subscription.status = subscription.status;
25 |
26 | await user.save();
27 |
28 | res.status(201).json({
29 | success: true,
30 | subscriptionId: subscription.id,
31 | });
32 | });
33 |
34 | export const paymentVerification = catchAsyncError(async (req, res, next) => {
35 | const { razorpay_signature, razorpay_payment_id, razorpay_subscription_id } =
36 | req.body;
37 |
38 | const user = await User.findById(req.user._id);
39 |
40 | const subscription_id = user.subscription.id;
41 |
42 | const generated_signature = crypto
43 | .createHmac("sha256", process.env.RAZORPAY_API_SECRET)
44 | .update(razorpay_payment_id + "|" + subscription_id, "utf-8")
45 | .digest("hex");
46 |
47 | const isAuthentic = generated_signature === razorpay_signature;
48 |
49 | if (!isAuthentic)
50 | return res.redirect(`${process.env.FRONTEND_URL}/paymentfail`);
51 |
52 | // database comes here
53 | await Payment.create({
54 | razorpay_signature,
55 | razorpay_payment_id,
56 | razorpay_subscription_id,
57 | });
58 |
59 | user.subscription.status = "active";
60 |
61 | await user.save();
62 |
63 | res.redirect(
64 | `${process.env.FRONTEND_URL}/paymentsuccess?reference=${razorpay_payment_id}`
65 | );
66 | });
67 |
68 | export const getRazorPayKey = catchAsyncError(async (req, res, next) => {
69 | res.status(200).json({
70 | success: true,
71 | key: process.env.RAZORPAY_API_KEY,
72 | });
73 | });
74 |
75 | export const cancelSubscription = catchAsyncError(async (req, res, next) => {
76 | const user = await User.findById(req.user._id);
77 |
78 | const subscriptionId = user.subscription.id;
79 | let refund = false;
80 |
81 | await instance.subscriptions.cancel(subscriptionId);
82 |
83 | const payment = await Payment.findOne({
84 | razorpay_subscription_id: subscriptionId,
85 | });
86 |
87 | const gap = Date.now() - payment.createdAt;
88 |
89 | const refundTime = process.env.REFUND_DAYS * 24 * 60 * 60 * 1000;
90 |
91 | if (refundTime > gap) {
92 | await instance.payments.refund(payment.razorpay_payment_id);
93 | refund = true;
94 | }
95 |
96 | await payment.remove();
97 | user.subscription.id = undefined;
98 | user.subscription.status = undefined;
99 | await user.save();
100 |
101 | res.status(200).json({
102 | success: true,
103 | message: refund
104 | ? "Subscription cancelled, You will receive full refund within 7 days."
105 | : "Subscription cancelled, Now refund initiated as subscription was cancelled after 7 days.",
106 | });
107 | });
108 |
--------------------------------------------------------------------------------
/e-learningserver/controllers/recommendationController.js:
--------------------------------------------------------------------------------
1 | import { Course } from "../models/Course.js"
2 | import { User } from "../models/User.js";
3 |
4 | import jwt from "jsonwebtoken";
5 | import { catchAsyncError } from "../middlewares/catchAsyncError.js"
6 | import ContentBasedRecommender from "content-based-recommender"
7 |
8 | export const createRecommendation = catchAsyncError(async (req, res, next) => {
9 | const limit = 3
10 | const { token } = req.cookies;
11 | //if not logged in then recommend topmost viewed lecture
12 | if (!token) {
13 | const courses = await Course.find({}).sort({ "views": -1 }).limit(limit)
14 | res.status(201).json({
15 | success: true,
16 | message: "Not Logged In",
17 | courses
18 | })
19 | return
20 | }
21 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
22 | const user = await User.findById(decoded._id)
23 | if (user === undefined || user.playlist.length === 0) {
24 | const courses = await Course.find({}).sort({ "views": -1 }).limit(limit)
25 | res.status(201).json({
26 | success: true,
27 | message: "Empty Playlist",
28 | courses
29 | })
30 | return
31 | }
32 | const allCourses = await Course.find()
33 |
34 | const documents = []
35 | allCourses.forEach((ele) => {
36 | documents.push({ id: ele._id.valueOf(), content: ele.title + " " + ele.category + " " + ele.description + " " + ele.createdBy })
37 | })
38 | const recommender = new ContentBasedRecommender({
39 | maxSimilarDocuments: 100,
40 | })
41 | await recommender.train(documents)
42 |
43 | //content based recommendation based on courses placed in user playlist
44 | var score = new Map()
45 | user.playlist.forEach((ele) => {
46 | const id = ele.course.valueOf()
47 | const similarDocuments = recommender.getSimilarDocuments(id, 0, 10)
48 | similarDocuments.forEach((row) => {
49 | const rid = row.id.valueOf()
50 | score.set(rid, (score.get(rid) || 0) + row.score)
51 | // score[row.id] = (score[row.id] || 0) + row.score
52 | })
53 | })
54 | //not recommending which are already in playlist
55 | user.playlist.forEach(ele => {
56 | const id = ele.course.valueOf()
57 | score.delete(id)
58 | })
59 | //sorting based on value of Score
60 | var sortedScore = new Map([...score].sort((a, b) => b[1] - a[1]));
61 | //extracting topmost based on Score
62 | var topSortedScore = new Map([...sortedScore.entries()].slice(0, Math.min(...[sortedScore.size, limit])))
63 | const ids = [...topSortedScore.keys()]
64 | const courses = await Course.find({ _id: { $in: ids } }).sort({ "views": -1 })
65 | res.status(201).json({
66 | success: true,
67 | message: "Based on playlist",
68 | courses
69 | })
70 |
71 | })
72 |
73 | //route are implemented in courseRoutes.js
--------------------------------------------------------------------------------
/e-learningserver/controllers/userController.js:
--------------------------------------------------------------------------------
1 | import { catchAsyncError } from "../middlewares/catchAsyncError.js";
2 | import ErrorHandler from "../utils/errorHandler.js";
3 | import { User } from "../models/User.js";
4 | import { sendToken } from "../utils/sendToken.js";
5 | import { sendEmail } from "../utils/sendEmail.js";
6 | import crypto from "crypto";
7 | import { Course } from "../models/Course.js";
8 | import cloudinary from "cloudinary";
9 | import getDataUri from "../utils/dataUri.js";
10 | import { Stats } from "../models/Stats.js";
11 |
12 | export const register = catchAsyncError(async (req, res, next) => {
13 | const { name, email, password } = req.body;
14 | const file = req.file;
15 |
16 | if (!name || !email || !password || !file)
17 | return next(new ErrorHandler("Please enter all field", 400));
18 |
19 | let user = await User.findOne({ email });
20 |
21 | if (user) return next(new ErrorHandler("User Already Exist", 409));
22 |
23 | const fileUri = getDataUri(file);
24 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content, { folder: "elearning" });
25 |
26 | user = await User.create({
27 | name,
28 | email,
29 | password,
30 | avatar: {
31 | public_id: mycloud.public_id,
32 | url: mycloud.secure_url,
33 | },
34 | });
35 |
36 | sendToken(res, user, "Registered Successfully", 201);
37 | });
38 |
39 | export const login = catchAsyncError(async (req, res, next) => {
40 | const { email, password } = req.body;
41 |
42 | if (!email || !password)
43 | return next(new ErrorHandler("Please enter all field", 400));
44 |
45 | const user = await User.findOne({ email }).select("+password");
46 |
47 | if (!user) return next(new ErrorHandler("Incorrect Email or Password", 401));
48 |
49 | const isMatch = await user.comparePassword(password);
50 |
51 | if (!isMatch)
52 | return next(new ErrorHandler("Incorrect Email or Password", 401));
53 |
54 | sendToken(res, user, `Welcome back, ${user.name}`, 200);
55 | });
56 |
57 | export const logout = catchAsyncError(async (req, res, next) => {
58 | res
59 | .status(200)
60 | .cookie("token", null, {
61 | expires: new Date(Date.now()),
62 | httpOnly: true,
63 | secure: true,
64 | sameSite: "none",
65 | })
66 | .json({
67 | success: true,
68 | message: "Logged Out Successfully",
69 | });
70 | });
71 |
72 | export const getMyProfile = catchAsyncError(async (req, res, next) => {
73 | const user = await User.findById(req.user._id);
74 |
75 | res.status(200).json({
76 | success: true,
77 | user,
78 | });
79 | });
80 |
81 | export const changePassword = catchAsyncError(async (req, res, next) => {
82 | const { oldPassword, newPassword } = req.body;
83 | if (!oldPassword || !newPassword)
84 | return next(new ErrorHandler("Please enter all field", 400));
85 |
86 | const user = await User.findById(req.user._id).select("+password");
87 |
88 | const isMatch = await user.comparePassword(oldPassword);
89 |
90 | if (!isMatch) return next(new ErrorHandler("Incorrect Old Password", 400));
91 |
92 | user.password = newPassword;
93 |
94 | await user.save();
95 |
96 | res.status(200).json({
97 | success: true,
98 | message: "Password Changed Successfully",
99 | });
100 | });
101 |
102 | export const updateProfile = catchAsyncError(async (req, res, next) => {
103 | const { name, email } = req.body;
104 |
105 | const user = await User.findById(req.user._id);
106 |
107 | if (name) user.name = name;
108 | if (email) user.email = email;
109 |
110 | await user.save();
111 |
112 | res.status(200).json({
113 | success: true,
114 | message: "Profile Updated Successfully",
115 | });
116 | });
117 |
118 | export const updateprofilepicture = catchAsyncError(async (req, res, next) => {
119 | const file = req.file;
120 |
121 | const user = await User.findById(req.user._id);
122 |
123 | const fileUri = getDataUri(file);
124 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content, { folder: "elearning" });
125 |
126 | await cloudinary.v2.uploader.destroy(user.avatar.public_id);
127 |
128 | user.avatar = {
129 | public_id: mycloud.public_id,
130 | url: mycloud.secure_url,
131 | };
132 |
133 | await user.save();
134 |
135 | res.status(200).json({
136 | success: true,
137 | message: "Profile Picture Updated Successfully",
138 | });
139 | });
140 |
141 | export const forgetPassword = catchAsyncError(async (req, res, next) => {
142 | const { email } = req.body;
143 |
144 | const user = await User.findOne({ email });
145 |
146 | if (!user) return next(new ErrorHandler("User not found", 400));
147 |
148 | const resetToken = await user.getResetToken();
149 |
150 | await user.save();
151 |
152 | const url = `${process.env.FRONTEND_URL}/resetpassword/${resetToken}`;
153 |
154 | const message = `Click on the link to reset your password. ${url}. If you have not request then please ignore.`;
155 |
156 | // Send token via email
157 | await sendEmail(user.email, "E-Learning Reset Password", message);
158 |
159 | res.status(200).json({
160 | success: true,
161 | message: `Reset Token has been sent to ${user.email}`,
162 | });
163 | });
164 |
165 | export const resetPassword = catchAsyncError(async (req, res, next) => {
166 | const { token } = req.params;
167 |
168 | const resetPasswordToken = crypto
169 | .createHash("sha256")
170 | .update(token)
171 | .digest("hex");
172 |
173 | const user = await User.findOne({
174 | resetPasswordToken,
175 | resetPasswordExpire: {
176 | $gt: Date.now(),
177 | },
178 | });
179 |
180 | if (!user)
181 | return next(new ErrorHandler("Token is invalid or has been expired", 401));
182 |
183 | user.password = req.body.password;
184 | user.resetPasswordToken = undefined;
185 | user.resetPasswordExpire = undefined;
186 |
187 | await user.save();
188 |
189 | res.status(200).json({
190 | success: true,
191 | message: "Password Changed Successfully",
192 | });
193 | });
194 |
195 | export const addToPlaylist = catchAsyncError(async (req, res, next) => {
196 | const user = await User.findById(req.user._id);
197 |
198 | const course = await Course.findById(req.body.id);
199 |
200 | if (!course) return next(new ErrorHandler("Invalid Course Id", 404));
201 |
202 | const itemExist = user.playlist.find((item) => {
203 | if (item.course.toString() === course._id.toString()) return true;
204 | });
205 |
206 | if (itemExist) return next(new ErrorHandler("Item Already Exist", 409));
207 |
208 | user.playlist.push({
209 | course: course._id,
210 | poster: course.poster.url,
211 | });
212 |
213 | await user.save();
214 |
215 | res.status(200).json({
216 | success: true,
217 | message: "Added to playlist",
218 | });
219 | });
220 |
221 | export const removeFromPlaylist = catchAsyncError(async (req, res, next) => {
222 | const user = await User.findById(req.user._id);
223 | const course = await Course.findById(req.query.id);
224 | if (!course) return next(new ErrorHandler("Invalid Course Id", 404));
225 |
226 | const newPlaylist = user.playlist.filter((item) => {
227 | if (item.course.toString() !== course._id.toString()) return item;
228 | });
229 |
230 | user.playlist = newPlaylist;
231 | await user.save();
232 | res.status(200).json({
233 | success: true,
234 | message: "Removed From Playlist",
235 | });
236 | });
237 |
238 | // // Admin Controllers
239 |
240 | export const getAllUsers = catchAsyncError(async (req, res, next) => {
241 | const users = await User.find({});
242 |
243 | res.status(200).json({
244 | success: true,
245 | users,
246 | });
247 | });
248 |
249 | export const updateUserRole = catchAsyncError(async (req, res, next) => {
250 | const user = await User.findById(req.params.id);
251 |
252 | if (!user) return next(new ErrorHandler("User not found", 404));
253 |
254 | if (user.role === "user") user.role = "admin";
255 | else user.role = "user";
256 |
257 | await user.save();
258 |
259 | res.status(200).json({
260 | success: true,
261 | message: "Role Updated",
262 | });
263 | });
264 |
265 | export const deleteUser = catchAsyncError(async (req, res, next) => {
266 | const user = await User.findById(req.params.id);
267 |
268 | if (!user) return next(new ErrorHandler("User not found", 404));
269 |
270 | await cloudinary.v2.uploader.destroy(user.avatar.public_id);
271 |
272 | // Cancel Subscription
273 |
274 | await user.remove();
275 |
276 | res.status(200).json({
277 | success: true,
278 | message: "User Deleted Successfully",
279 | });
280 | });
281 |
282 | export const deleteMyProfile = catchAsyncError(async (req, res, next) => {
283 | const user = await User.findById(req.user._id);
284 |
285 | await cloudinary.v2.uploader.destroy(user.avatar.public_id);
286 |
287 | // Cancel Subscription
288 |
289 | await user.remove();
290 |
291 | res.status(200)
292 | .cookie("token", null, {
293 | expires: new Date(Date.now()),
294 | })
295 | .json({
296 | success: true,
297 | message: "User Deleted Successfully",
298 | });
299 | });
300 |
301 | User.watch().on("change", async () => {
302 | let stat_size = await Stats.countDocuments();
303 | if (stat_size === 0) {
304 | await Stats.create({});
305 | }
306 | const stats = await Stats.find({}).sort({ createdAt: "desc" }).limit(1);
307 | const subscription = await User.find({ "subscription.status": "active" });
308 | const users = await User.countDocuments();
309 | stats[0].users = users;
310 | stats[0].subscription = subscription.length;
311 | stats[0].createdAt = new Date(Date.now());
312 |
313 | await stats[0].save();
314 | });
315 |
--------------------------------------------------------------------------------
/e-learningserver/middlewares/Error.js:
--------------------------------------------------------------------------------
1 | const ErrorMiddleware = (err, req, res, next) => {
2 | err.statusCode = err.statusCode || 500
3 | err.message = err.message || "Internal Server Error"
4 | res.status(err.statusCode).json({
5 | success: false,
6 | message: err.message
7 | })
8 | }
9 | export default ErrorMiddleware
--------------------------------------------------------------------------------
/e-learningserver/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import ErrorHandler from "../utils/errorHandler.js";
3 | import { catchAsyncError } from "./catchAsyncError.js";
4 | import { User } from "../models/User.js";
5 |
6 | export const isAuthenticated = catchAsyncError(async (req, res, next) => {
7 | const { token } = req.cookies;
8 |
9 | if (!token) return next(new ErrorHandler("Not Logged In", 401));
10 |
11 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
12 |
13 | req.user = await User.findById(decoded._id);
14 |
15 | next();
16 | });
17 |
18 | export const authorizeSubscribers = (req, res, next) => {
19 | if (req.user.subscription.status !== "active" && req.user.role !== "admin")
20 | return next(
21 | new ErrorHandler(`Only Subscribers can access this resource`, 403)
22 | );
23 |
24 | next();
25 | };
26 |
27 | export const authorizeAdmin = (req, res, next) => {
28 | if (req.user.role !== "admin")
29 | return next(
30 | new ErrorHandler(
31 | `${req.user.role} is not allowed to access this resource`,
32 | 403
33 | )
34 | );
35 |
36 | next();
37 | };
38 |
--------------------------------------------------------------------------------
/e-learningserver/middlewares/catchAsyncError.js:
--------------------------------------------------------------------------------
1 | export const catchAsyncError = (passedFunction) => (req, res, next) => {
2 | Promise.resolve(passedFunction(req, res, next)).catch(next)
3 | }
--------------------------------------------------------------------------------
/e-learningserver/middlewares/multer.js:
--------------------------------------------------------------------------------
1 | import multer from "multer";
2 |
3 | const storage = multer.memoryStorage();
4 |
5 | const singleUpload = multer({ storage }).single("file");
6 |
7 | export default singleUpload;
8 |
--------------------------------------------------------------------------------
/e-learningserver/models/Course.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose"
2 |
3 | const Schema = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: [true, "Please Enter Course Title"],
7 | minlength: [4, "Title must be at least 4 characters"],
8 | maxlength: [80, "Title can't exceeds 80 characters"]
9 | },
10 | description: {
11 | type: String,
12 | required: [true, "Please Enter Course Description"],
13 | minlength: [4, "Description must be at least 4 characters"]
14 | },
15 | lectures: [
16 | {
17 | title: {
18 | type: String,
19 | required: true,
20 | },
21 | description: {
22 | type: String,
23 | required: true,
24 | },
25 | video: {
26 | public_id: {
27 | type: String,
28 | required: true,
29 | },
30 | url: {
31 | type: String,
32 | required: true,
33 | },
34 | }
35 | }
36 | ],
37 | poster: {
38 | public_id: {
39 | type: String,
40 | required: true,
41 | },
42 | url: {
43 | type: String,
44 | required: true,
45 | },
46 | },
47 | views: {
48 | type: Number,
49 | default: 0
50 | },
51 | numOfVideos: {
52 | type: Number,
53 | default: 0
54 | },
55 | category: {
56 | type: String,
57 | required: true,
58 | },
59 | createdBy: {
60 | type: String,
61 | required: [true, "Enter Course Creator's Name"]
62 | },
63 | createdAt: {
64 | type: Date,
65 | default: Date.now
66 | }
67 | })
68 |
69 | export const Course = mongoose.model("Course", Schema)
--------------------------------------------------------------------------------
/e-learningserver/models/Payment.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const schema = new mongoose.Schema({
4 | razorpay_signature: {
5 | type: String,
6 | required: true,
7 | },
8 | razorpay_payment_id: {
9 | type: String,
10 | required: true,
11 | },
12 | razorpay_subscription_id: {
13 | type: String,
14 | required: true,
15 | },
16 |
17 | createdAt: {
18 | type: Date,
19 | default: Date.now,
20 | },
21 | });
22 |
23 | export const Payment = mongoose.model("Payment", schema);
24 |
--------------------------------------------------------------------------------
/e-learningserver/models/Stats.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const schema = new mongoose.Schema({
4 | users: {
5 | type: Number,
6 | default: 0,
7 | },
8 |
9 | subscription: {
10 | type: Number,
11 | default: 0,
12 | },
13 |
14 | views: {
15 | type: Number,
16 | default: 0,
17 | },
18 |
19 | createdAt: {
20 | type: Date,
21 | default: Date.now,
22 | },
23 | });
24 |
25 | export const Stats = mongoose.model("Stats", schema);
26 |
--------------------------------------------------------------------------------
/e-learningserver/models/User.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import validator from "validator";
3 | import jwt from "jsonwebtoken";
4 | import bcrypt from "bcrypt";
5 | import crypto from "crypto";
6 |
7 | const schema = new mongoose.Schema({
8 | name: {
9 | type: String,
10 | required: [true, "Please enter your name"],
11 | },
12 | email: {
13 | type: String,
14 | required: [true, "Please enter your email"],
15 | unique: true,
16 | validate: validator.isEmail,
17 | },
18 |
19 | password: {
20 | type: String,
21 | required: [true, "Please enter your password"],
22 | minLength: [6, "Password must be at least 6 characters"],
23 | select: false,
24 | },
25 | role: {
26 | type: String,
27 | enum: ["admin", "user"],
28 | default: "user",
29 | },
30 |
31 | subscription: {
32 | id: String,
33 | status: String,
34 | },
35 |
36 | avatar: {
37 | public_id: {
38 | type: String,
39 | required: true,
40 | },
41 | url: {
42 | type: String,
43 | required: true,
44 | },
45 | },
46 |
47 | playlist: [
48 | {
49 | course: {
50 | type: mongoose.Schema.Types.ObjectId,
51 | ref: "Course",
52 | },
53 | poster: String,
54 | },
55 | ],
56 |
57 | createdAt: {
58 | type: Date,
59 | default: Date.now,
60 | },
61 |
62 | resetPasswordToken: String,
63 | resetPasswordExpire: String,
64 | });
65 |
66 | schema.pre("save", async function (next) {
67 | if (!this.isModified("password")) return next();
68 | this.password = await bcrypt.hash(this.password, 10);
69 | next();
70 | });
71 |
72 | schema.methods.getJWTToken = function () {
73 | return jwt.sign({ _id: this._id }, process.env.JWT_SECRET, {
74 | expiresIn: "15d",
75 | });
76 | };
77 |
78 | schema.methods.comparePassword = async function (password) {
79 | return await bcrypt.compare(password, this.password);
80 | };
81 |
82 | schema.methods.getResetToken = function () {
83 | const resetToken = crypto.randomBytes(20).toString("hex");
84 |
85 | this.resetPasswordToken = crypto
86 | .createHash("sha256")
87 | .update(resetToken)
88 | .digest("hex");
89 |
90 | this.resetPasswordExpire = Date.now() + 15 * 60 * 1000;
91 |
92 | return resetToken;
93 | };
94 |
95 | export const User = mongoose.model("User", schema);
96 |
--------------------------------------------------------------------------------
/e-learningserver/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-learningserver",
3 | "version": "1.0.0",
4 | "description": "",
5 | "type": "module",
6 | "main": "server.js",
7 | "scripts": {
8 | "dev": "nodemon server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "bcrypt": "^5.1.0",
14 | "cloudinary": "^1.33.0",
15 | "content-based-recommender": "^1.5.0",
16 | "cookie-parser": "^1.4.6",
17 | "cors": "^2.8.5",
18 | "datauri": "^4.1.0",
19 | "dotenv": "^16.0.3",
20 | "express": "^4.18.2",
21 | "jsonwebtoken": "^9.0.0",
22 | "mongoose": "^6.9.1",
23 | "multer": "^1.4.5-lts.1",
24 | "node-cron": "^3.0.2",
25 | "nodemailer": "^6.9.1",
26 | "razorpay": "^2.8.4",
27 | "validator": "^13.9.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/e-learningserver/routes/courseRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express"
2 | import { addLecture, deleteLecture, getAllCourses, createCourse, getCourseLectures, deleteCourse } from "../controllers/courseController.js"
3 |
4 | import {
5 | authorizeAdmin,
6 | isAuthenticated,
7 | authorizeSubscribers,
8 | } from "../middlewares/auth.js"
9 |
10 | import singleUpload from "../middlewares/multer.js"
11 | import { createRecommendation } from "../controllers/recommendationController.js"
12 |
13 | const router = express.Router()
14 |
15 | // Get All courses without lectures
16 | router.route("/courses").get(getAllCourses)
17 |
18 | // create new course - only admin
19 | router
20 | .route("/createcourse")
21 | .post(isAuthenticated, authorizeAdmin, singleUpload, createCourse);
22 |
23 | // Add lecture, Delete Course, Get Course Details
24 | router
25 | .route("/course/:id")
26 | .get(isAuthenticated, authorizeSubscribers, getCourseLectures)
27 | .post(isAuthenticated, authorizeAdmin, singleUpload, addLecture)
28 | .delete(isAuthenticated, authorizeAdmin, deleteCourse);
29 |
30 | // Create Recommendation based on User's Playlist
31 | router.route("/recommend")
32 | .post(createRecommendation)
33 |
34 | // Delete Lecture
35 | router.route("/lecture").delete(isAuthenticated, authorizeAdmin, deleteLecture)
36 |
37 | export default router
38 |
--------------------------------------------------------------------------------
/e-learningserver/routes/otherRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | contact,
4 | courseRequest,
5 | getDashboardStats,
6 | } from "../controllers/otherController.js";
7 |
8 | import { authorizeAdmin, isAuthenticated } from "../middlewares/auth.js";
9 |
10 | const router = express.Router();
11 |
12 | // contact form
13 | router.route("/contact").post(contact);
14 |
15 | // Request form
16 | router.route("/courserequest").post(courseRequest);
17 |
18 | // Get Admin Dashboard Stats
19 | router
20 | .route("/admin/stats")
21 | .get(isAuthenticated, authorizeAdmin, getDashboardStats);
22 |
23 | export default router;
24 |
--------------------------------------------------------------------------------
/e-learningserver/routes/paymentRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | buySubscription,
4 | cancelSubscription,
5 | getRazorPayKey,
6 | paymentVerification,
7 | } from "../controllers/paymentController.js";
8 | import { isAuthenticated } from "../middlewares/auth.js";
9 |
10 | const router = express.Router();
11 |
12 | // Buy Subscription
13 | router.route("/subscribe").get(isAuthenticated, buySubscription);
14 |
15 | // Verify Payment and save reference in database
16 | router.route("/paymentverification").post(isAuthenticated, paymentVerification);
17 |
18 | // Get Razorpay key
19 | router.route("/razorpaykey").get(getRazorPayKey);
20 |
21 | // Cancel Subscription
22 | router.route("/subscribe/cancel").delete(isAuthenticated, cancelSubscription);
23 |
24 | export default router;
25 |
--------------------------------------------------------------------------------
/e-learningserver/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | addToPlaylist,
4 | changePassword,
5 | deleteMyProfile,
6 | deleteUser,
7 | forgetPassword,
8 | getAllUsers,
9 | getMyProfile,
10 | login,
11 | logout,
12 | register,
13 | removeFromPlaylist,
14 | resetPassword,
15 | updateProfile,
16 | updateprofilepicture,
17 | updateUserRole,
18 | } from "../controllers/userController.js";
19 | import { authorizeAdmin, isAuthenticated } from "../middlewares/auth.js";
20 | import singleUpload from "../middlewares/multer.js";
21 |
22 | const router = express.Router();
23 |
24 | // To register a new user
25 | router.route("/register").post(singleUpload, register);
26 |
27 | // Login
28 | router.route("/login").post(login);
29 |
30 | // // logout
31 | router.route("/logout").get(logout);
32 |
33 | // // Get my profile
34 | router.route("/me").get(isAuthenticated, getMyProfile)
35 |
36 | // // Delete my profile
37 | router.route("/me").delete(isAuthenticated, deleteMyProfile);
38 |
39 | // ChangePassword
40 | router.route("/changepassword").put(isAuthenticated, changePassword);
41 |
42 | // UpdateProfile
43 | router.route("/updateprofile").put(isAuthenticated, updateProfile);
44 |
45 | // UpdateProfilePicture
46 | router
47 | .route("/updateprofilepicture")
48 | .put(isAuthenticated, singleUpload, updateprofilepicture);
49 |
50 | // ForgetPassword
51 | router.route("/forgetpassword").post(forgetPassword);
52 |
53 | // ResetPassword
54 | router.route("/resetpassword/:token").put(resetPassword);
55 |
56 | // AddtoPlaylist
57 | router.route("/addtoplaylist").post(isAuthenticated, addToPlaylist);
58 |
59 | // RemoveFromPlaylist
60 | router.route("/removefromplaylist").delete(isAuthenticated, removeFromPlaylist);
61 |
62 | // Admin Routes
63 | router.route("/admin/users").get(isAuthenticated, authorizeAdmin, getAllUsers);
64 |
65 | router
66 | .route("/admin/user/:id")
67 | .put(isAuthenticated, authorizeAdmin, updateUserRole)
68 | .delete(isAuthenticated, authorizeAdmin, deleteUser);
69 |
70 | export default router;
71 |
--------------------------------------------------------------------------------
/e-learningserver/server.js:
--------------------------------------------------------------------------------
1 | import app from "./app.js"
2 | import { connectDB } from "./config/database.js"
3 | import cloudinary from "cloudinary"
4 | import RazorPay from "razorpay"
5 | import nodeCron from "node-cron"
6 | import { Stats } from "./models/Stats.js"
7 |
8 | import { createRequire } from "module";
9 | const require = createRequire(import.meta.url)
10 |
11 | //Accessing .env variable using Dotenv
12 | if (process.env.NODE_ENV !== "PRODUCTION") {
13 | require("dotenv").config({
14 | path: "./config/config.env"
15 | })
16 | }
17 |
18 | connectDB()
19 |
20 | cloudinary.v2.config({
21 | cloud_name: process.env.CLOUDINARY_CLIENT_NAME,
22 | api_key: process.env.CLOUDINARY_CLIENT_API,
23 | api_secret: process.env.CLOUDINARY_CLIENT_SECRET,
24 | })
25 |
26 | export const instance = new RazorPay({
27 | key_id: process.env.RAZORPAY_API_KEY,
28 | key_secret: process.env.RAZORPAY_API_SECRET,
29 | });
30 |
31 | //1st day of every month
32 | nodeCron.schedule("0 0 0 1 * *", async () => {
33 | try {
34 | await Stats.create({});
35 | } catch (error) {
36 | console.log(error);
37 | }
38 | });
39 |
40 | app.listen(process.env.PORT, () => {
41 | console.log(`Server is working on port: ${process.env.PORT}`);
42 | })
--------------------------------------------------------------------------------
/e-learningserver/utils/dataUri.js:
--------------------------------------------------------------------------------
1 | import DataUriParser from "datauri/parser.js";
2 | import path from "path";
3 | const getDataUri = (file) => {
4 | const parser = new DataUriParser();
5 | const extName = path.extname(file.originalname).toString();
6 | return parser.format(extName, file.buffer);
7 | };
8 |
9 | export default getDataUri;
10 |
--------------------------------------------------------------------------------
/e-learningserver/utils/errorHandler.js:
--------------------------------------------------------------------------------
1 | class ErrorHandler extends Error {
2 | constructor(message, statusCode) {
3 | super(message)
4 | this.statusCode = statusCode
5 | }
6 | }
7 |
8 | export default ErrorHandler
--------------------------------------------------------------------------------
/e-learningserver/utils/sendEmail.js:
--------------------------------------------------------------------------------
1 | import { createTransport } from "nodemailer";
2 |
3 | export const sendEmail = async (to, subject, text) => {
4 | const transporter = createTransport({
5 | host: process.env.SMTP_HOST,
6 | port: process.env.SMTP_PORT,
7 | auth: {
8 | user: process.env.SMTP_USER,
9 | pass: process.env.SMTP_PASS,
10 | },
11 | });
12 |
13 | await transporter.sendMail({
14 | to,
15 | subject,
16 | text,
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/e-learningserver/utils/sendToken.js:
--------------------------------------------------------------------------------
1 | export const sendToken = (res, user, message, statusCode = 200) => {
2 | const token = user.getJWTToken();
3 | const options = {
4 | expires: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000),
5 | httpOnly: true,
6 | secure: true,
7 | sameSite: "none",
8 | };
9 |
10 | res.status(statusCode).cookie("token", token, options).json({
11 | success: true,
12 | message,
13 | user,
14 | });
15 | };
16 |
--------------------------------------------------------------------------------